{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"TiTiler","text":"
A modern dynamic tile server built on top of FastAPI and Rasterio/GDAL.
Documentation: devseed.com/titiler/
Source Code: developmentseed/titiler
Titiler
, pronounced tee-tiler (ti is the diminutive version of the french petit which means small), is a set of python modules that focus on creating FastAPI application for dynamic tiling.
Note: This project is the descendant of cogeo-tiler
and cogeo-mosaic-tiler
.
morecantile
.Starting with version 0.3.0
, the TiTiler
python module has been split into a set of python namespace packages: titiler.{package}
.
Core
package contains libraries to help create a dynamic tiler for COG and STAC titiler.extensions TiTiler's extensions package. Contains extensions for Tiler Factories. titiler.mosaic The mosaic
package contains libraries to help create a dynamic tiler for MosaicJSON (adds cogeo-mosaic
requirement) titiler.application TiTiler's demo
package. Contains a FastAPI application with full support of COG, STAC and MosaicJSON"},{"location":"#installation","title":"Installation","text":"To install from PyPI and run:
# Make sure you have pip up to date\npython -m pip install -U pip\n\npython -m pip install titiler.{package}\n# e.g.,\n# python -m pip install titiler.core\n# python -m pip install titiler.extensions\n# python -m pip install titiler.mosaic\n# python -m pip install titiler.application (also installs core, extensions and mosaic)\n\n# Install uvicorn to run the FastAPI application locally\npython -m pip install uvicorn\n\n# Launch application locally\nuvicorn titiler.application.main:app\n
To install from sources and run for development:
git clone https://github.com/developmentseed/titiler.git\ncd titiler\n\npython -m pip install -U pip\npython -m pip install -e src/titiler/core -e src/titiler/extensions -e src/titiler/mosaic -e src/titiler/application\npython -m pip install uvicorn\n\nuvicorn titiler.application.main:app --reload\n
"},{"location":"#docker","title":"Docker","text":"Ready to use/deploy images can be found on Github registry.
docker run --name titiler \\\n -p 8000:8000 \\\n --env PORT=8000 \\\n --env WORKERS_PER_CORE=1 \\\n --rm -it ghcr.io/developmentseed/titiler:latest\n
git clone https://github.com/developmentseed/titiler.git\ncd titiler\n\ndocker-compose up --build titiler # or titiler-uvicorn\n
Some options can be set via environment variables, see: tiangolo/uvicorn-gunicorn-docker#advanced-usage
"},{"location":"#project-structure","title":"Project structure","text":"src/titiler/ - titiler modules.\n \u251c\u2500\u2500 application/ - Titiler's `Application` package\n \u251c\u2500\u2500 extensions/ - Titiler's `Extensions` package\n \u251c\u2500\u2500 core/ - Titiler's `Core` package\n \u2514\u2500\u2500 mosaic/ - Titiler's `Mosaic` package\n
"},{"location":"#contribution-development","title":"Contribution & Development","text":"See CONTRIBUTING.md
"},{"location":"#license","title":"License","text":"See LICENSE
"},{"location":"#authors","title":"Authors","text":"Created by Development Seed
See contributors for a listing of individual contributors.
"},{"location":"#changes","title":"Changes","text":"See CHANGES.md.
"},{"location":"contributing/","title":"Development - Contributing","text":"Issues and pull requests are more than welcome: github.com/developmentseed/titiler/issues
dev install
git clone https://github.com/developmentseed/titiler.git\ncd titiler\n\npython -m pip install \\\n pre-commit \\\n -e src/titiler/core[\"test\"] \\\n -e src/titiler/extensions[\"test,cogeo,stac\"] \\\n -e src/titiler/mosaic[\"test\"] \\\n -e src/titiler/application[\"test\"]\n
pre-commit
This repo is set to use pre-commit
to run isort, flake8, pydocstring, black (\"uncompromising Python code formatter\") and mypy when committing new code.
pre-commit install\n
"},{"location":"contributing/#run-tests","title":"Run tests","text":"Each titiler
's modules has its own test suite which can be ran independently
# titiler.core\npython -m pytest src/titiler/core --cov=titiler.core --cov-report=xml --cov-append --cov-report=term-missing\n\n# titiler.extensions\npython -m pytest src/titiler/extensions --cov=titiler.extensions --cov-report=xml --cov-append --cov-report=term-missing\n\n# titiler.mosaic\npython -m pytest src/titiler/mosaic --cov=titiler.mosaic --cov-report=xml --cov-append --cov-report=term-missing\n\n# titiler.application\npython -m pytest src/titiler/application --cov=titiler.application --cov-report=xml --cov-append --cov-report=term-missing\n
"},{"location":"contributing/#docs","title":"Docs","text":"git clone https://github.com/developmentseed/titiler.git\ncd titiler\npython -m pip install -r requirements/requirements-docs.txt\n
Hot-reloading docs:
mkdocs serve -f docs/mkdocs.yml\n
To manually deploy docs (note you should never need to do this because Github Actions deploys automatically for new commits.):
mkdocs gh-deploy -f docs/mkdocs.yml\n
"},{"location":"dynamic_tiling/","title":"Dynamic Tiling","text":"TiTiler's first goal is to create a lightweight but performant dynamic tile server... but what do we mean by this?
When you zoom/pan on a web map, you are visualizing either vector or raster data that is loaded by your web client (e.g Chrome). Vector Tiles are rendered On the Fly, meaning the map library (e.g MapboxGL) will apply styling on the vector it receives to create a visual representation on the map. This is possible because vector data can be encoded and compressed very efficiently and result in each tile being only couple of kilo octets.
On the other side, raster data is a really dense format, a 256 x 256 x 3
tile (True color image) needs to encode 196 608
values, and depending on the data type (Integer, Float, Complex), a raster tile can be really heavy. Depending on the dataset data type, some operations might be needed in order to obtain a visual representation (e.g. rescaling, colormap, ... ). Map library will almost only accept Uint8 RGB(A) tile encoded as PNG, JPEG or Webp.
Static
tiling is referring to static
tiles (file on storage) that are pre-rendered from input dataset. Here are the steps needed to create those tiles:
When the tiles are available on a storage, you can either put a web server (e.g. tilecache, mapserver) or maybe directly put a CDN, which will allow map client to fetch
the tiles.
gdal2tiles.py
)The goal of the Dynamic Tiling
process is to get rid of all the pre-processing steps, by creating a tile server which can access the raw data (COG) and apply operations (rescaling, reprojection, image encoding) to create the visual tiles on the fly.
With Static
tile generation you are often limited because you are visualizing data that is fixed and stored somewhere on a disk. With Dynamic tiling
, users have the possibility to apply their own choice of processing (e.g rescaling, masking) before creating the image
.
Static tiling will always be faster than dynamic tiling, but a cache layer can be set up in front of the dynamic tiler, but using a dynamic tiler often means that same tile won't be serve twice (because users can set multiple options).
"},{"location":"dynamic_tiling/#links","title":"Links","text":"https://medium.com/devseed/cog-talk-part-1-whats-new-941facbcd3d1
https://kylebarron.dev/blog/cog-mosaic/overview
https://mapdataservices.wordpress.com/2014/05/05/digital-mappings-dynamic-makeover/
https://medium.com/indigoag-eng/more-and-better-satellite-imagery-through-dynamic-tiling-60dcd7ce66ce
https://sparkgeo.com/blog/terradactile-generate-cogs-from-aws-terrain-tiles/
https://www.azavea.com/blog/2019/04/23/using-cloud-optimized-geotiffs-cogs/
https://hi.stamen.com/stamen-aws-lambda-tiler-blog-post-76fc1138a145
"},{"location":"external_links/","title":"External links","text":"Tip
If you have an article, project, tool, or anything related to TiTiler that is not yet listed here, create a Pull Request adding it.
"},{"location":"external_links/#mentions","title":"Mentions","text":"Sparkgeo's Terradactile
Chris Holmes Cloud Native Geospatial Sprint Awards & Bounties
Tobin Bradley awesome video about COGs Youtube
David McCracken's Blog on Plotly Dash Interactive Mapping - Dash Leaflet & TiTiler
stac-utils/titiler-pgstac: TiTiler extension which connects to a PgSTAC database to create dynamic mosaics based on search queries.
developmentseed/titiler-xarray: TiTiler extension for xarray
developmentseed/titiler-images: TiTiler demo application for Sentinel-2 Digital Twin dataset
ESA Charter Mapper geobrowser
developmentseed/titiler-digitaltwin: TiTiler demo application for Sentinel-2 Digital Twin dataset
developmentseed/titiler-mvt: TiTiler demo application to create Mapbox Vector Tiles from COG
developmentseed/titiler-pds: TiTiler demo application for Sentinel-2 and Landsat-8 AWS Public Datasets
stac-utils/stac-fastapi: STAC API implementation with FastAPI.
c-core-labs/stac-api: STAC compliant API implementation (built from stac-fastapi)
developmentseed/titiler-lambda-layer: TiTiler Lambda layers for easy deployment on AWS
Terradue/Stars: Spatio Temporal Asset Runtime Services
developmentseed/rio-viz: Visualize Cloud Optimized GeoTIFF in browser
developmentseed/pearl-backend: PEARL (Planetary Computer Land Cover Mapping) Platform API and Infrastructure
microsoft/planetary-computer-apis: Microsoft Planetary Computer APIs
TiTiler
is a set of python modules whose goal are to help users in creating a dynamic tile server. To learn more about dynamic tiling
please refer to the docs.
Users can choose to extend or use TiTiler
as it is.
TiTiler
comes with a default (complete) application with support for COG, STAC, and MosaicJSON. You can install and start the application locally by doing:
# Update pip\npython -m pip install -U pip\n\n# Install titiler packages\npython -m pip install uvicorn titiler.application\n\n# Start application using uvicorn\nuvicorn titiler.application.main:app\n\n> INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n
See default endpoints documentation pages:
/cog
- Cloud Optimized GeoTIFF/mosaicjson
- MosaicJSON/stac
- Spatio Temporal Asset Catalog/tileMatrixSets
- Tiling Schemes/algorithms
- Algorithms/colorMaps
- ColorMapsThe default application can be customized using environment variables defined in titiler.application.settings.ApiSettings
class. Each variable needs to be prefixed with TITILER_API_
.
NAME
(str): name of the application. Defaults to titiler
.CORS_ORIGINS
(str, ,
delimited origins): allowed CORS origin. Defaults to *
.CORS_ALLOW_METHODS
(str, ,
delimited methods): allowed CORS methods. Defaults to GET
.CACHECONTROL
(str): Cache control header to add to responses. Defaults to \"public, max-age=3600\"
.ROOT_PATH
(str): path behind proxy.DEBUG
(str): adds LoggerMiddleware
and TotalTimeMiddleware
in the middleware stack.DISABLE_COG
(bool): disable /cog
endpoints.DISABLE_STAC
(bool): disable /stac
endpoints.DISABLE_MOSAIC
(bool): disable /mosaic
endpoints.LOWER_CASE_QUERY_PARAMETERS
(bool): transform all query-parameters to lower case (see developmentseed/titiler!321).GLOBAL_ACCESS_TOKEN
(str | None): a string which is required in the ?access_token=
query param with every request.TiTiler
has been developed so users can build their own application with only the endpoints they need. Using Factories, users can create a fully customized application with only a defined set of endpoints.
When building a custom application, you may wish to only install the core
and/or mosaic
modules. To install these from PyPI:
# Update pip\npython -m pip install -U pip\n\n# Install titiler.core and uvicorn packages\npython -m pip install titiler.core uvicorn\n
These can then be used like:
# app.py\nimport uvicorn\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\napp = FastAPI()\ncog = TilerFactory()\napp.include_router(cog.router)\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\nif __name__ == '__main__':\n uvicorn.run(app=app, host=\"127.0.0.1\", port=8080, log_level=\"info\")\n
"},{"location":"intro/#extending-titilers-app","title":"Extending TiTiler's app","text":"If you want to include all of Titiler's built-in endpoints, but also include customized endpoints, you can import and extend the app directly.
python -m pip install titiler.application uvicorn # also installs titiler.core and titiler.mosaic\n
These can then be used like:
# Add private COG endpoints requiring token validation\nfrom fastapi import APIRouter, Depends, HTTPException, Security\nfrom fastapi.security.api_key import APIKeyQuery\n\nfrom titiler.application.main import app\nfrom titiler.core.factory import TilerFactory\n\nimport uvicorn\n\napi_key_query = APIKeyQuery(name=\"access_token\", auto_error=False)\n\n\ndef token_validation(access_token: str = Security(api_key_query)):\n \"\"\"stupid token validation.\"\"\"\n if not access_token:\n raise HTTPException(status_code=401, detail=\"Missing `access_token`\")\n\n # if access_token == `token` then OK\n if not access_token == \"token\":\n raise HTTPException(status_code=401, detail=\"Invalid `access_token`\")\n\n return True\n\n\n# Custom router with token dependency\nrouter = APIRouter(dependencies=[Depends(token_validation)])\ntiler = TilerFactory(router_prefix=\"private/cog\", router=router)\n\napp.include_router(tiler.router, prefix=\"/private/cog\", tags=[\"Private\"])\n\n\nif __name__ == '__main__':\n uvicorn.run(app=app, host=\"127.0.0.1\", port=8080, log_level=\"info\")\n
More on customization
"},{"location":"mosaics/","title":"Mosaics","text":"[Work in Progress]
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: developmentseed/mosaicjson-spec
"},{"location":"mosaics/#links","title":"Links","text":"TiTiler
supports the common output format for map tiles: JPEG, PNG and WEBP.
While some formats (e.g PNG) are able to encode Uint16 or Float datatypes, most web browsers only supports 8 bit data (meaning that it has to be between 0 and 255). It's on the user to know what datatype is the input source (COG), and what kind of post processing
there is to do to create a valid web map tile.
TiTiler
also has support for more complex output data formats, such as JPEG2000 or GeoTIFF. While it might not be useful for FrontEnd display (most browsers can't decode GeoTIFF natively), some users could want to transmit the data as raw
values to some applications (non-web display).
Default output types/extensions are:
.tif
: image/tiff; application=geotiff.jp2
: image/jp2.png
: image/png.pngraw
: image/png.jpeg
: image/jpeg.jpg
: image/jpg.webp
: image/webp.npy
: application/x-binaryWhile .tif
could be interesting, decoding the GeoTIFF
format requires non-native/default libraries. Recently, in collaboration with Planet, we started exploring the use of a Numpy-native format
to encode the data array.
planetlabs/numpytiles-spec
This specification attempts to create a standard for representing uncompressed, full bit-depth, raster imagery that can be easily communicated between a server and a client.
Example:
import numpy\nimport requests\nfrom io import BytesIO\n\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\n\nr = requests.get(\"http://127.0.0.1:8000/cog/tiles/14/10818/9146.npy\",\n params = {\n \"url\": url,\n }\n)\ndata = numpy.load(BytesIO(r.content))\nprint(data.shape)\n>>> (4, 256, 256)\n\n# By default titiler will return a concatenated data,mask array.\ndata, mask = data[0:-1], data[-1]\n
Notebook: Working_with_NumpyTile
"},{"location":"output_format/#jsonresponse","title":"JSONResponse","text":"Sometimes rio-tiler's responses can contain NaN
, Infinity
or -Infinity
values (e.g for Nodata). Sadly there is no proper ways to encode those values in JSON or at least not all web client supports it.
In order to allow TiTiler to return valid responses we added a custom JSONResponse
in v0.3.10
which will automatically translate float('nan')
, float('inf')
and float('-inf')
to null
and thus avoid in valid JSON response.
from fastapi import FastAPI\nfrom titiler.core.resources.responses import JSONResponse\n\napp = FastAPI(default_response_class=JSONResponse,)\n\n@app.get(\"/something\")\ndef return_something():\n return float('nan')\n
This JSONResponse
is used by default in titiler
Tiler Factories where NaN
are expected (info
, statistics
and point
endpoints).
Remove all default values to the dependencies
DatasetParams.unscale
: False
-> None
(default to False
in rio-tiler)DatasetParams.resampling_method
: nearest
-> None
(default to nearest
in rio-tiler)DatasetParams.reproject_method
: nearest
-> None
(default to nearest
in rio-tiler)ImageRenderingParams.add_mask
: True
-> None
(default to True
in rio-tiler)StatisticsParams.categorical
: False
-> None
(default to False
in rio-tiler)Add as_dict(exclude_none=True/False)
method to the DefaultDependency
class.
from typing import Optional\nfrom titiler.core.dependencies import DefaultDependency\nfrom dataclasses import dataclass\n\n@dataclass\nclass Deps(DefaultDependency):\n value: Optional[int] = None\n\nprint({**Deps().__dict__.items()})\n>> {'value': None}\n\nDeps().as_dict() # `exclude_none` defaults to True\n>> {}\n\nDeps(value=1).as_dict()\n>> {'value': 1}\n
Use .as_dict()
method when passing option to rio-tiler Reader's methods to avoid parameter conflicts when using custom Readers.
>=0.111.0
WMTSCapabilities.xml
response for ArcMap compatibilityCloud Optimized GeoTIFF
with dataset URL or TiTiler
for the ows:ServiceIdentification titlecogeo
with Dataset
for the layer
ows:Identifierfastapi-slim
to avoid unwanted dependencies (author @n8sty, developmentseed/titiler!815)TerrainRGB
algorithm name (author @JinIgarashi, developmentseed/titiler!804)RescalingParams
and HistogramParams
dependencies204
Error codeColorMapFactory
to create colorMap metadata endpoints (developmentseed/titiler!796)Deprecation remove default WebMercatorQuad
tile matrix set in /tiles
, /tilesjson.json
, /map
and /WMTSCapabilities.xml
endpoints (developmentseed/titiler!802)
# Before\n/tiles/{z}/{x}/{y}\n/tilejson.json\n/map\n/WMTSCapabilities.xml\n\n# Now\n/tiles/WebMercatorQuad/{z}/{x}/{y}\n/WebMercatorQuad/tilejson.json\n/WebMercatorQuad/map\n/WebMercatorQuad/WMTSCapabilities.xml\n
Deprecation default_tms
attribute in BaseTilerFactory
(because tileMatrixSetId
is now required in endpoints).
Deprecation remove default WebMercatorQuad
tile matrix set in /tiles
, /tilesjson.json
, /map
and /WMTSCapabilities.xml
endpoints (developmentseed/titiler!802)
# Before\n/tiles/{z}/{x}/{y}\n/tilejson.json\n/map\n/WMTSCapabilities.xml\n\n# Now\n/tiles/WebMercatorQuad/{z}/{x}/{y}\n/WebMercatorQuad/tilejson.json\n/WebMercatorQuad/map\n/WebMercatorQuad/WMTSCapabilities.xml\n
Deprecation default_tms
attribute in MosaicTilerFactory
(because tileMatrixSetId
is now required in endpoints).
request
as first argument in TemplateResponse
to adapt with latest starlette versionextra=\"ignore\"
option ApiSettings
to fix pydantic issue when using .env
file (author @imanshafiei540, developmentseed/titiler!800)use_epsg
parameter to WMTS endpoint to resolve ArcMAP issues and fix XML formating (author @gadomski, developmentseed/titiler!782)rio-tiler
version to >6.3.0
align_bounds_with_dataset=True
rio-tiler option in GeoJSON statistics methods for more precise calculation/map
endpoint (author @Firefishy, developmentseed/titiler!749)TileMatrixSet.cellSize
property instead of deprecated/private TileMatrixSet._resolution
methodTileMatrixSet.cellSize
property instead of deprecated/private TileMatrixSet._resolution
method>=0.107.0
jinja2.Environment
argument (author @jasongi, developmentseed/titiler!744)jinja2.Environment
argument (author @jasongi, developmentseed/titiler!744)jinja2.Environment
argument (author @jasongi, developmentseed/titiler!744)TileMatrixSet.cellSize
property instead of deprecated/private TileMatrixSet._resolution
method [backported from 0.16.1]TileMatrixSet.cellSize
property instead of deprecated/private TileMatrixSet._resolution
method [backported from 0.16.1]<0.107.0
to avoid starlette breaking change (0.28
)global_access_token
string, set with TITILER_API_GLOBAL_ACCESS_TOKEN
environment variable (author @DeflateAwning, developmentseed/titiler!735)/map
HTML response, add Lat/Lon buffer to AOI to avoid creating wrong AOI (when data covers the whole world).add algorithm
options for /statistics
endpoints
switch from BaseReader.statistics()
method to a combination of BaseReader.preview()
and ImageData.statistics()
methods to get the statistics
update rio-tiler
requirement to >=6.2.5,<7.0
allow bidx
option in titiler.core.dependencies.AssetsBidxExprParams
and titiler.core.dependencies.AssetsBidxParams
# merge band 1 form asset1 and asset2\n# before\nhttpx.get(\n \"/stac/preview\",\n params=(\n (\"url\", \"stac.json\"),\n (\"assets\", \"asset1\"),\n (\"assets\", \"asset2\"),\n (\"asset_bidx\", \"asset1|1\"),\n (\"asset_bidx\", \"asset2|1\"),\n )\n)\n\n# now\nhttpx.get(\n \"/stac/preview\",\n params=(\n (\"url\", \"stac.json\"),\n (\"assets\", \"asset1\"),\n (\"assets\", \"asset2\"),\n (\"bidx\", 1),\n )\n)\n
fix openapi examples
dst_crs
options in /statistics [POST]
and /feature [POST]
endpointsdependencies.TileParams
dependency with buffer
and padding
optionstile_dependency
attribute in TilerFactory
class (defaults to TileParams
)reproject
(alias to reproject_method
) option in DatasetParams
dependencyHTTP_404_NOT_FOUND
to HTTP_204_NO_CONTENT
when no asset is found or tile is empty (author @simouel, developmentseed/titiler!713)tile_dependency
attribute in MosaicTilerFactory
class (defaults to TileParams
)color_formula
parameter to be set via a dependency (author @samn, developmentseed/titiler!707)titiler.core.dependencies.create_colormap_dependency
to create ColorMapParams dependency from rio_tiler.colormap.ColorMaps
objectpy.typed
files in titiler submodules (peps.python.org/pep-0561)PartFeatureParams
dependencybreaking changes
max_size
is now set to None
for /statistics [POST]
, /bbox
and /feature
endpoints, meaning the tiler will create image from the highest resolution.
renamed titiler.core.dependencies.ImageParams
to PreviewParams
split TileFactory img_dependency
attribute in two:
img_preview_dependency
: used in /preview
and /statistics [GET]
, default to PreviewParams
(with max_size=1024
)
img_part_dependency
: used in /bbox
, /feature
and /statistics [POST]
, default to PartFeatureParams
(with max_size=None
)
renamed /crop
endpoints to /bbox/...
or /feature/...
/crop/{minx},{miny},{maxx},{maxy}.{format}
-> /bbox/{minx},{miny},{maxx},{maxy}.{format}
/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}
-> /bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}
/crop [POST]
-> /feature [POST]
/crop.{format} [POST]
-> /feature.{format} [POST]
/crop/{width}x{height}.{format} [POST]
-> /feature/{width}x{height}.{format} [POST]
update rio-tiler
requirement to >=6.2.1
Take coverage weights in account when generating statistics from GeoJSON features
GetFeatureInfo
capability in wmsExtension
(author @benjaminleighton, developmentseed/titiler!698)-
by _
in query parameters breaking changecoord-crs
-> coord_crs
dst-crs
-> dst_crs
replace buffer
and color_formula
endpoint parameters by external dependencies (BufferParams
and ColorFormulaParams
)
add titiler.core.utils.render_image
which allow non-binary alpha band created with custom colormap. render_image
replace ImageData.render
method.
# before\nif cmap := colormap or dst_colormap:\n image = image.apply_colormap(cmap)\n\nif not format:\n format = ImageType.jpeg if image.mask.all() else ImageType.png\n\ncontent = image.render(\n img_format=format.driver,\n **format.profile,\n **render_params,\n)\n\n# now\n# render_image will:\n# - apply the colormap\n# - choose the right output format if `None`\n# - create the binary data\ncontent, media_type = render_image(\n image,\n output_format=format,\n colormap=colormap or dst_colormap,\n **render_params,\n)\n
geom-densify-pts
to geometry_densify
breaking changegeom-precision
to geometry_precision
breaking changeurl_for
method and avoid changing Request.path_params
objectLowerCaseQueryStringMiddleware
unexpectedly truncating query parameters (authors @jthetzel and @jackharrhy, @developmentseed/titiler!677)cors_allow_methods
in ApiSettings
to control the CORS allowed methods (author @ubi15, developmentseed/titiler!684)>=0.95.1
\u2192 >=0.100.0
~=1.0
\u2192 ~=2.0
>=5.0,<6.0
\u2192 >=6.0,<7.0
>=4.3,<5.0
\u2192 >=5.0,<6.0
>=0.4,<0.7
\u2192 >=1.0,<2.0
>=4.6.1
>=4.0,<5.0\"
\u2192 >=5.0,<6.0\"
>=6.0,<7.0
\u2192 >=7.0,<8.0
/api
and /api.html
for documentation (instead of /openapi.json
and /docs
)Annotated
Type for Query/Path parametersTileMatrixSetId
by tileMatrixSetId
>=0.95.1
pydantic
dependency to ~=1.0
rio-tiler
dependency to >=5.0,<6.0
/validate
endpoint to avoid issue when COG has NaN
nodata valuerio-cogeo
dependency to >=4.0,<5.0
rio-stac
requirement to >=0.8,<0.9
and add geom-densify-pts
and geom-precision
optionscogeo-mosaic
dependency to >=6.0,<7.0
titiler.mosaic.resources.enum.PixelSelectionMethod
and use rio_tiler.mosaic.methods.PixelSelectionMethod
WebMercatorQuad
)templates
configurable in the factoriesindex.html
to map.html
dependencies.CRSParams
to dependencies.CoordCRSParams
dst-crs
option for /preview
and /crop
endpoints to specify the output Coordinate Reference System.templates
configurable in the factoriestemplates
configurable in the factoriescog_index.html
to cog_viewer.html
stac_index.html
to stac_viewer.html
zoom to point
in stac
and cog
viewers (author @dchirst, developmentseed/titiler!614)rescale
parameter to be set via a dependency (author @samn, developmentseed/titiler!619)coord-crs
parameter for /point
, /part
and /feature
endpointsTerrainRGB
(change interval from 1.0
to 0.1
)url_for
method to make sure we return a string (developmentseed/titiler!607)/map
viewer when using WebMercator TMShatch
and pdm-pep517
as build system and use pyproject.toml
for python module metadataruff
for python lintingasset_bidx
rio-tiler
minimum version to 4.1.6
titiler.extensions
package (cogValidateExtension
, stacExtension
, cogViewerExtension
, stacViewerExtension
, wmsExtension
)cogeo-mosaic
version requirement to >=5.0,<5.2
(allow using az://
prefix from uri)MOSAIC_STRICT_ZOOM
environment variable to control if the application should allow fetching tiles outside mosaic min/max zoomsbreaking change
"},{"location":"release-notes/#titilercore_21","title":"titiler.core","text":"extensions
option to the BaseTilerFactory
to specify a list of extension we want to register. Each extension will be then registered in the __post_init__
object creation step.BaseHTTPMiddleware
class inheritance for middleware (write pure ASGI middleware)titiler.core
factoriesviewer
code into titiler.extensions
/cog/stac
endpoint from titiler.extension.stacExtension
to create STAC Items from raster dataset/
is the correct route path, which enable prefixed and non-prefixed mosaic application)router_prefix
in BaseTilerFactory.url_for
/map
endpoint and template to support multiple TMS (developmentseed/titiler!560)breaking change
=={currentVersion}
wmts.xml
template to work with non-epsg based CRSdefault_tms
in BaseTilerFactory
to set the default TMS identifier supported by the tiler (e.g WebMercatorQuad
)titiler.core.version
file>=0.87
>=4.1,<4.2
rescale
and color_formula
from the post_process
dependencyalgorithm
support and introduce new algorithm
and algorithm_params
query parametersbreaking changes
timing headers
and titiler.core.utils
submoduleasset_expression
(except in /asset_statistics
endpoint) (see cogeotiff.github.io/rio-tiler/v4_migration/#multibasereader-expressions)band_names
b
(e.g b1
) (ref: cogeotiff.github.io/rio-tiler/v4_migration/#band-names)/map
endpoint in TilerFactory to display tiles given query-parametersTMSParams
and WebMercatorTMSParams
dependencies.TilerFactory.tms_dependency
attribute by TilerFactory.supported_tms
. This attribute gets a morecantile.defaults.TileMatrixSets
store and will create the tms dependencies dynamicallyTMSFactory.tms_dependency
attribute by TMSFactory.supported_tms
. This attribute gets a morecantile.defaults.TileMatrixSets
store and will create the tms dependencies dynamicallystats_dependency
and histogram_dependency
from BaseTilerFactory
to TilerFactory
;
has be to used in expression to indicate multiple bands. b1*2,b2+b3,b1/b3
-> b1*2;b2+b3;b1/b3
>=4.2,<4.3
breaking changes
timing headers
MosaicTilerFactory.tms_dependency
attribute by MosaicTilerFactory.supported_tms
. This attribute gets a morecantile.defaults.TileMatrixSets
store and will create the tms dependencies dynamicallybreaking changes
pixel_selection_dependency
options in MosaicTilerFactory
to allow default method override (author @samn, developmentseed/titiler!495)interval
colormaps in titiler.applicationenvironment_dependency
option in BaseTilerFactory
to define GDAL environment at runtime.gdal_config
option in BaseTilerFactory
breaking# before\nrouter = TilerFactory(gdal_config={\"GDAL_DISABLE_READDIR_ON_OPEN\": \"FALSE\"}).router\n\n# now\nrouter = TilerFactory(environment_dependency=lambda: {\"GDAL_DISABLE_READDIR_ON_OPEN\": \"FALSE\"}).router\n\n\nclass ReaddirType(str, Enum):\n\n false = \"false\"\n true = \"true\"\n empty_dir = \"empty_dir\"\n\n\n# or at endpoint call. The user could choose between false/true/empty_dir\ndef gdal_env(disable_read: ReaddirType = Query(ReaddirType.false)):\n return {\"GDAL_DISABLE_READDIR_ON_OPEN\": disable_read.value.upper()}\n\nrouter = TilerFactory(environment_dependency=gdal_env).router\n
"},{"location":"release-notes/#titilerapplication_10","title":"titiler.application","text":"starlette-cramjam
requirement0.6.0a2
setup.py
+ setuptools
instead of pyproject.toml
+ flit
because it broke namespace packages (developmentseed/titiler!472)exception_handler_factory
reader_dependency
to enable passing Reader
's option defined by Query/Header/Path parameters.pyproject.toml
titiler.core.__version__
breakingLowerCaseQueryStringMiddleware
(author @samn, developmentseed/titiler!464)backend_dependency
to enable passing Backend
's option defined by Query/Header/Path parameters.backend_options
MosaicTilerFactory argument in favor of the use of backend_dependency
breakingpyproject.toml
titiler.mosaic.__version__
breakingpyproject.toml
titiler.application.__version__
breakingcachecontrol_max_http_code
option to CacheControlMiddleware
to avoid adding cache-control headers for API errors (Author @sharkinsspatial, developmentseed/titiler!444)/asset_statistics
which will return per asset statistics. Returns response in form of Dict[{asset name}, Dict[{band name}, BandStatistics]]
breaking change
;
instead of colon (,
) as separator. Note: proper urlencoding might be needed.# before\nexpression = \"b1+b2,b2\"\n\n# new\nexpression = \"b1+b2;b2\"\n
/statistics
now returns merged statistics in form of Dict[{asset_band or expression}, BandStatistics]
(instead of Dict[{asset name}, Dict[{band name}, BandStatistics]]
)# before\nresponse = httpx.get(f\"/stac/statistics?url=item.json\").json()\nprint(response)\n>>> {\n \"asset1\": {\n \"1\": {\n \"min\": ...,\n \"max\": ...,\n ...\n },\n \"2\": {\n \"min\": ...,\n \"max\": ...,\n ...\n }\n }\n}\n\n# now\nresponse = httpx.get(f\"/stac/statistics?url=item.json\").json()\nprint(response)\n>>> {\n \"asset1_1\": {\n \"min\": ...,\n \"max\": ...,\n ...\n },\n \"asset1_2\": {\n \"min\": ...,\n \"max\": ...,\n ...\n },\n}\n
"},{"location":"release-notes/#043-2022-02-08","title":"0.4.3 (2022-02-08)","text":"buffer
option to match rio-tiler tile options (developmentseed/titiler!427)>=0.73
(developmentseed/titiler!425)colormap_dependency
Dockerfile
to Dockerfile.gunicorn
Dockerfile.uvicorn
rio-tiler
version requirement to >=3.0
cogeo-mosaic
version to >=4.0
rio-tiler
version (>=3.0.0a6) with new colormap types information and base classesadditional_dependency
attribute in BaseTileFactory
. This also remove **kwargs
in endpoints breakingreader_options
attribute in BaseTileFactory
breakingtms_dependency
default to titiler.core.dependencies.TMSParams
which should supports all morecantile's TMS.route_dependencies
attribute to BaseTilerFactory
to allow customizing route dependencies (author @alukach, developmentseed/titiler!406)cogeo-mosaic
version (>=4.0.0a2) with updated Backend type hints informationrio-tiler/morecantile
requirement (>=3.0)utils.bbox_to_feature
(replaced by geojson_pydantic native function Feature(geometry=Polygon.from_bounds(*bounds), properties=info)
)utils.data_stats
(replaced by rio-tiler new statistics method)metadata
endpoints breaking APIstatistics
endpoints with histogram optionsstatistics
endpoint responses breaking APIband_expression
in BandsExprParams
dependency breaking APImorecantile
requirement definition in setup.py and defers to rio-tiler supported versiontitiler.core.dependencies.DefaultDependency
(allows dict unpacking and remove .kwargs
) breaking APIbidx=1&bidx=2&bidx
instead of bidx=1,2,3
) breaking APIasset_bidx
query parameter in replacement of bidx
in MultiBaseFactory dependencies and switch to new format: {asset name}|{bidx,bidx,bidx}
breaking APIasset_expression
to the new format: {asset name}|{expression}
(e.g data|b1+b2
) breaking APIassets
QueryParameter to List (e.g assets=COG&assets=Data
) breaking APIbands
QueryParameter to List (e.g bands=B01&bands=B02
) breaking APIRenderParams
dependency into:PostProcessParams
: rescale
and color_formula
parametersImageRenderingParams
: return_mask
process_dependency
attribute in BaseTilerFactory
(defaults to PostProcessParams
)resampling
alias instead of resampling_method
for QueryParameter breaking APIassets
option is not provided for MultiBaseTilerFactory
info and statistics endpoints.bands
option is not provided for MultiBandsTilerFactory
info and statistics endpoints.bounds
, minzoom
and maxzoom
in /info
responsedataset
in /info
response to better follow the Info model/statistics
endpoint by defaultcogeo-mosaic
requirement (>=4.0)/info
endpoint to match the model.3.0
(author @robintw, developmentseed/titiler!389)3.0
titiler.core.resources.responses.JSONResponse
as default response for info
, metadata
, statistics
and point
endpoints (ref: developmentseed/titiler!374)starlette_cramjam
compression middleware (ref: developmentseed/titiler#369)>=0.65,<0.68
(ref: developmentseed/titiler#366)asset_expression
and band_expression
in Multi*TilerFactory (ref: developmentseed/titiler#367)titiler.application.middleware
to titiler.core.middleware
(developmentseed/titiler!365)/{z}/{x}/{y}/assets
, /{lon},{lat}/assets
, /{minx},{miny},{maxx},{maxy}/assets
GET endpoints to return a list of assets that intersect a given geometry (author @mackdelany, developmentseed/titiler!351)/crop
POST endpoint to return an image from a GeoJSON feature (developmentseed/titiler!339)/statistics
(GET and POST) endpoints to return advanced images statistics (developmentseed/titiler!347)root_path
setting to specify a url path prefix to use when running the app behind a reverse proxy (developmentseed/titiler!343)tilejson
and WMTSCapabilities.xml
endpoints to allow list querystrings (as done previously in developmentseed/titiler#319)titiler.application.middleware.LowerCaseQueryStringMiddleware
to cast all query string parameter to lowercase (author @lorenzori, developmentseed/titiler!321)titiler
code to src/titiler
>=2.1
version and update rescale
query-parameter (developmentseed/titiler#319)# before\n# previously, rio-tiler was splitting a list of input range in tuple of 2\nrescale=0,1000,0,1000,0,1000\n\n# now\n# rio-tiler 2.1 now expect sequence of tuple in form of Sequence[Tuple[Num, Num]]\nrescale=0,1000&rescale=0,1000&rescale=0,1000\n
"},{"location":"release-notes/#titilermosaic_18","title":"titiler.mosaic","text":"cogeo-mosaic
version to >=3.0,<3.1
.>=2.2
and use rio_cogeo.models
instead of custom ones.exclude_path
options in titiler.application.middleware.CacheControlMiddleware
to avoid adding cache-control headers to specific paths.histogram_bins
to be a single value or a ,
delimited scalar (developmentseed/titiler!307)change error status from 404
to 500
for RasterioIOError
exception (author @kylebarron, developmentseed/titiler!300)
Sometimes GDAL/Rasterio can lose track of the file handler (might be related to cache issue + threading) and raise RasterioIOError: file not found
, while the file exists for real. To avoid caching this, we changed the error code to 500 (errors >= 500 do not get cache-control
header on titiler.application).
.jpg
and .jpeg
extensions (developmentseed/titiler!271)breaking change
split titiler
into a set of namespaces packages (developmentseed/titiler!284)
titiler.core
The core
package host the low level tiler factories.
# before\nfrom titiler.endpoints.factory import TilerFactory\n\n# now\nfrom titiler.core.factory import TilerFactory\n
titiler.mosaic
The mosaic
package is a plugin to titiler.core
which adds support for MosaicJSON
# before\nfrom titiler.endpoints.factory import MosaicTilerFactory\n\n# now\nfrom titiler.mosaic.factory import MosaicTilerFactory\n
titiler.application
The application
package is a full ready to use
FastAPI application with support of STAC, COG and MosaicJSON.
# before\n$ pip install titiler\n$ uvicorn titiler.main:app --reload\n\n# now\n$ pip install titiler.application uvicorn\n$ uvicorn titiler.application.main:app --reload\n
3.0.0rc2
and add backend_options
attribute in MosaicTilerFactory (developmentseed/titiler!247)**render_params.kwargs
to pass custom render params in image.render
method (developmentseed/titiler!259)/ping
to /healthz
in k8s deploymentbreaking change
OptionalHeaders
, MimeTypes
and ImageDrivers
enums to the singular form (developmentseed/titiler!258)ColorMapName
, ResamplingName
and TileMatrixSetName
) to the singular form (developmentseed/titiler!260)MimeType
to MediaType
(developmentseed/titiler!258)ColorMapParams
dependency to ease the creation of custom colormap dependency (developmentseed/titiler!252)PathParams
to DatasetPathParams
and also made it a simple callable (developmentseed/titiler!260)renamed colormap
query-parameter to colormap_name
(developmentseed/titiler!262)
# before\n/cog/preview.png?colormap=viridis\n\n# now\n/cog/preview.png?colormap_name=viridis\n
use colormap
query-parameter to pass custom colormap (developmentseed/titiler!262)
/cog/preview.png?colormap={\"0\": \"#FFFF00FF\", ...}\n
validate
in MosaicTilerFactory
(developmentseed/titiler!206, author @drnextgis)ressources
package to resources
(developmentseed/titiler!210, author @drnextgis)TITILER_STACK
as prefix to CDK and TITILER_API
as prefix to API (developmentseed/titiler!211, author @fredliporace)create
and update
endpoints (developmentseed/titiler!218)titiler.models.mosaics
because the models are not used anymore (developmentseed/titiler!221)rio_tiler.io.MultiBandReader
bands dependencies (developmentseed/titiler!226)MultiBaseTilerFactory
and MultiBandTilerFactory
custom tiler factories (developmentseed/titiler!230)MultiBaseTilerFactory
factorydebug
configuration and make reponse headers metadata optional (developmentseed/titiler!232)breaking change
titiler.dependencies.AssetsBidxParams
to make asset
a required parameter (developmentseed/titiler!230/info
endpoint now expect the assets
parameter to be passed. To ge the list of available assets we added a new /assets
endpoint within the tiler factoryCOGReader
as default reader
in titiler.endpoints.factory.BaseTilerFactory
rio_tiler.errors.MissingBands
in known errors.titiler.endpoints.factory.TMSFactory
to enable custom TMS endpoints.BaseFactory
to BaseTilerFactory
in titiler.endpoints.factory
API_DISABLE_{COG/STAC/MOSAIC}
environment variables to control default endpoints in titiler main app (developmentseed/titiler#156)overwriting=False/True
on MosaicJSON creation (developmentseed/titiler#164)gdal_config
option to Tiler factories to replace custom APIRoute
class (developmentseed/titiler#168)info.geojson
endpoint to return dataset info as a GeoJSON feature (developmentseed/titiler#166)rio-tiler
, cogeo-mosaic
and optional dependenciesrio-tiler>=2.0.0rc2
width/height
in /crop
endpoint path/crop/{minx},{miny},{maxx},{maxy}.{format}\n/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}\n
"},{"location":"release-notes/#010a11post1-2020-11-12","title":"0.1.0a11.post1 (2020-11-12)","text":"\"rio-cogeo~=2.0\"\n\"rio-tiler>=2.0.0rc1,<2.1\"\n\"cogeo-mosaic>=3.0.0a17,<3.1\"\n
"},{"location":"release-notes/#010a11-2020-11-12","title":"0.1.0a11 (2020-11-12)","text":"tile()
for MosaicTilerFactory
method (developmentseed/titiler#147)tile()
factory method (developmentseed/titiler#141, author @fredliporace)breaking changes
Note: We changed the versioning scheme to {major}.{minor}.{path}{pre}{prenum}
pkg_resources
(pypa/setuptools#510)X-Server-Timings
to Server-Timing
.total;dur={}
in response header Server-Timing
, using new titiler.middleware.TotalTimeMiddleware
middleware (113)from titiler.middleware import CacheControlMiddleware, TotalTimeMiddleware\nfrom fastapi import FastAPI\n\napp.add_middleware(CacheControlMiddleware, cachecontrol=\"public, max-age=3600\")\napp.add_middleware(TotalTimeMiddleware)\n
tests/
an stack/
in titiler python package.EPSG6933
in TMSbreaking changes * [FACTORY] the additional_dependency
should be a Callable which return a dict.
```python\n@dataclass # type: ignore\nclass BaseFactory(metaclass=abc.ABCMeta):\n \"\"\"BaseTiler Factory.\"\"\"\n ...\n # provide custom dependency\n additional_dependency: Callable[..., Dict] = field(default=lambda: dict())\n```\n\n```python\ndef AssetsParams(\n assets: Optional[str] = Query(\n None,\n title=\"Asset indexes\",\n description=\"comma (',') delimited asset names (might not be an available options of some readers)\",\n )\n) -> Dict:\n \"\"\"Assets Dependency.\"\"\"\n kwargs = {}\n if assets:\n kwargs[\"assets\"] = assets.split(\",\")\n return kwargs\n```\n
_
prefix in factory methods (e.g _tile
-> tile
)[FACTORY] refactor dependencies to better align with rio_tiler.io.BaseReader method definition.
Example:
In the metadata
, the MetadataParams
will be used to pass pmin
and pmax
because they are the only required parameters for the metadata method. All other params will be passed to a kwargs
dict.
@dataclass\nclass MetadataParams(DefaultDependency):\n \"\"\"Common Metadada parameters.\"\"\"\n # Required params\n pmin: float = Query(2.0, description=\"Minimum percentile\")\n pmax: float = Query(98.0, description=\"Maximum percentile\")\n # Optional parameters\n bidx: Optional[str] = Query(\n None, title=\"Band indexes\", description=\"comma (',') delimited band indexes\",\n )\n ...\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n\n if self.bidx is not None:\n self.kwargs[\"indexes\"] = tuple(\n int(s) for s in re.findall(r\"\\d+\", self.bidx)\n )\n ...\n\n# metadata method in factory\ndef metadata(\n src_path=Depends(self.path_dependency),\n metadata_params=Depends(self.metadata_dependency),\n kwargs: Dict = Depends(self.additional_dependency),\n):\n \"\"\"Return metadata.\"\"\"\n reader = src_path.reader or self.reader\n with reader(src_path.url, **self.reader_options) as src_dst:\n info = src_dst.metadata(\n metadata_params.pmin,\n metadata_params.pmax,\n **metadata_params.kwargs,\n **kwargs,\n )\n return info\n
* [FACTORY] refactor dependencies definition @dataclass # type: ignore\nclass BaseFactory(metaclass=abc.ABCMeta):\n \"\"\"BaseTiler Factory.\"\"\"\n\n reader: default_readers_type = field(default=COGReader)\n reader_options: Dict = field(default_factory=dict)\n\n # FastAPI router\n router: APIRouter = field(default_factory=APIRouter)\n\n # Path Dependency\n path_dependency: Type[PathParams] = field(default=PathParams)\n\n # Rasterio Dataset Options (nodata, unscale, resampling)\n dataset_dependency: default_deps_type = field(default=DatasetParams)\n\n # Indexes/Expression Dependencies\n layer_dependency: default_deps_type = field(default=BidxExprParams)\n\n # Image rendering Dependencies\n render_dependency: default_deps_type = field(default=RenderParams)\n\n # TileMatrixSet dependency\n tms_dependency: Callable[..., TileMatrixSet] = WebMercatorTMSParams\n\n # provide custom dependency\n additional_dependency: Callable[..., Dict] = field(default=lambda: dict())\n
remove PathParams.reader
attribute. This option was not used and would have been technically difficult to use.
@dataclass\nclass PathParams:\n \"\"\"Create dataset path from args\"\"\"\n\n url: str = Query(..., description=\"Dataset URL\")\n
Update .npy
output format to follow the numpyTile format (#103)
import numpy\nimport requests\nfrom io import BytesIO\n\nendpoint = ...\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\n\nr = requests.get(f\"{endpoint}/cog/tiles/14/10818/9146.npy\",\n params = {\n \"url\": url,\n }\n)\ndata = numpy.load(BytesIO(r.content))\nprint(data.shape)\n> (4, 256, 256)\n
Add titiler.custom.routing.apiroute_factory
. This function enable the creation of custom fastapi.routing.APIRoute class with rasterio.Env()
block.
from fastapi import FastAPI, APIRouter\nfrom rasterio._env import get_gdal_config\nfrom titiler.custom.routing import apiroute_factory\n\napp = FastAPI()\nroute_class = apiroute_factory({\"GDAL_DISABLE_READDIR_ON_OPEN\": \"FALSE\"})\nrouter = APIRouter(route_class=route_class)\n\n@router.get(\"/simple\")\ndef simple():\n \"\"\"should return FALSE.\"\"\"\n res = get_gdal_config(\"GDAL_DISABLE_READDIR_ON_OPEN\")\n return {\"env\": res}\n\napp.include_router(router)\n
Note: This has only be tested for python 3.6 and 3.7.
url_for
method in TilerFactory to retrieve prefixed
endpoint URL (#95)titiler.dependencies.PathParams
mosaicid path translation, where a user could pass url=mosaicid://
to the endpoint.switch to pydantic.BaseSettings
for FastAPI application setting management.
List of Settings:
name: str = \"titiler\"\ncors_origins: str = \"*\"\ncachecontrol: str = \"public, max-age=3600\"\n
API Settings can now be set by adding a .env
file in your local project or by setting environment variables (e.g API_CORS_ORIGIN=\"https://mywebsite.com/*\"
)
/part
GeoTIFF output>=3.0b4,<3.1
and cogeo-mosaic to >=3.0a10,<3.1
e.g Create a Landsat 8 Tiler
from titiler.endpoints.factory import TilerFactory, MosaicTilerFactory\nfrom titiler.dependencies import BandsParams\n\nfrom rio_tiler_pds.landsat.aws.landsat8 import L8Reader # Not in TiTiler dependencies\n\nfrom fastapi import FastAPI\n\napp = FastAPI(title=\"Landsat Tiler\", openapi_url=\"/api/v1/openapi.json\")\nscene = TilerFactory(\n reader=L8Reader, additional_dependency=BandsParams, router_prefix=\"scenes\"\n)\nmosaic = MosaicTilerFactory(\n dataset_reader=L8Reader,\n additional_dependency=BandsParams,\n add_update=False,\n add_create=False,\n router_prefix=\"mosaic\",\n)\napp.include_router(scene.router, prefix=\"/scenes\", tags=[\"Scenes\"])\napp.include_router(mosaic.router, prefix=\"/mosaic\", tags=[\"Mosaic\"])\n
"},{"location":"release-notes/#01a0-2020-08-31","title":"0.1a0 (2020-08-31)","text":"First release on pypi
"},{"location":"release-notes/#tiler-factory","title":"Tiler Factory","text":"For this release we created new Tiler Factories class which handle creation of FastAPI routers for a given rio_tiler Readers.
from titiler.endpoints.factory import TilerFactory\nfrom rio_tiler.io import COGReader, STACReader\n\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\ncog = TilerFactory()\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n
"},{"location":"release-notes/#readers-tilematrixsets","title":"Readers / TileMatrixSets","text":"The titiler.endpoints.factory.TilerFactory
class will create a tiler with Web Mercator
as uniq supported Tile Matrix Set.
For other TMS support, tiler needs to be created with titiler.endpoints.factory.TMSTilerFactory
and with a TMS friendly reader (e.g rio_tiler_crs.COGReader
).
Simple tiler with only Web Mercator support
from rio_tiler.io import COGReader\n\nfrom titiler.endpoints import factory\nfrom titiler.dependencies import WebMercatorTMSParams\n\napp = factory.TilerFactory(reader=COGReader)\nassert app.tms_dependency == WebMercatorTMSParams\n
Tiler with more TMS support (from morecantile)
from rio_tiler_crs import COGReader\n\nfrom titiler.endpoints import factory\nfrom titiler.dependencies import TMSParams\n\napp = factory.TMSTilerFactory(reader=COGReader)\nassert app.tms_dependency == TMSParams\n
"},{"location":"release-notes/#other-changes","title":"Other changes","text":"template_dir = pkg_resources.resource_filename(\"titiler\", \"templates\")\ntemplates = Jinja2Templates(directory=template_dir)\n\ncog_template = templates.TemplateResponse(\n name=\"cog_index.html\",\n context={\n \"request\": request,\n \"tilejson\": request.url_for(\"cog_tilejson\"),\n \"metadata\": request.url_for(\"cog_metadata\"),\n },\n media_type=\"text/html\",\n)\n\nstac_template = templates.TemplateResponse(\n name=\"stac_index.html\",\n context={\n \"request\": request,\n \"tilejson\": request.url_for(\"stac_tilejson\"),\n \"metadata\": request.url_for(\"stac_info\"),\n },\n media_type=\"text/html\",\n)\n
"},{"location":"release-notes/#pre-pypi-releases","title":"Pre Pypi releases","text":""},{"location":"release-notes/#212-2020-06-24","title":"2.1.2 (2020-06-24)","text":"width
& height
parameters in API docs to force output size for part/preview endpoints.resampling_method
in API docs.link: developmentseed/titiler@725da5f
"},{"location":"release-notes/#211-2020-06-22","title":"2.1.1 (2020-06-22)","text":"link: developmentseed/titiler@95b98a3
"},{"location":"release-notes/#210-2020-06-11","title":"2.1.0 (2020-06-11)","text":"/preview
, /crop
, /point
endpointslink: developmentseed/titiler@8b63fc6
"},{"location":"release-notes/#200-2020-06-09","title":"2.0.0 (2020-06-09)","text":"/stac/viewer
, /cog/viewer
)link: developmentseed/titiler@fa2cb78
"},{"location":"release-notes/#100-2020-06-04","title":"1.0.0 (2020-06-04)","text":"Initial release
link: developmentseed/titiler@f4fdc02
"},{"location":"tile_matrix_sets/","title":"TileMatrixSets","text":"Slippy map tiles are square or rectangular images that follow a coordinate system defined by a grid called Tile Matrix
: docs.opengeospatial.org/is/17-083r2/17-083r2.html. The Web Mercator grid
is the de facto standard for Web maps. Made popular by google since 2005, it has pros and cons and:
With any such projection, some distortion is unavoidable. In the Mercator projection, geographical features further from the equator are exaggerated in size. For example, Greenland appears to be of a similar size to Africa. However, Africa is actually more than 14 times as large (by area).
ref: developer.tomtom.com/blog/decoded/understanding-map-tile-grids-and-zoom-levels
As one of the first requirements, we built TiTiler
with support for serving tiles in multiple Projections by using rio-tiler
and morecantile
which provide the low level TileMatrixSets support.
$ curl http://127.0.0.1:8000/tileMatrixSets | jq '.tileMatrixSets[] | .id'\n\"LINZAntarticaMapTilegrid\"\n\"EuropeanETRS89_LAEAQuad\"\n\"CanadianNAD83_LCC\"\n\"UPSArcticWGS84Quad\"\n\"NZTM2000\"\n\"NZTM2000Quad\"\n\"UTM31WGS84Quad\"\n\"UPSAntarcticWGS84Quad\"\n\"WorldMercatorWGS84Quad\"\n\"WGS1984Quad\"\n\"WorldCRS84Quad\"\n\"WebMercatorQuad\"\n
You can easily add more TileMatrixSet support, see custom tms.
Notebook: Working_with_nonWebMercatorTMS
"},{"location":"advanced/APIRoute_and_environment_variables/","title":"APIRoute and environment variables","text":"Important
This has been deprecated. You can now pass environment_dependency=lambda: {\"GDAL_DISABLE_READDIR_ON_OPEN\":\"FALSE\"}
to the Tiler Factory. This will be passed to a rasterio.Env()
context manager on top of all gdal related blocks.
from titiler.core.factory import TilerFactory\ncog = TilerFactory(\n reader=COGReader,\n router_prefix=\"cog\",\n environment_dependency=lambda: {\"GDAL_DISABLE_READDIR_ON_OPEN\":\"FALSE\"},\n)\n
Sometimes, specifically when using GDAL, it can be useful to have environment variables set for certain endpoints (e.g. when using Landsat data on AWS you need GDAL_DISABLE_READDIR_ON_OPEN=FALSE
but you don't want this environment variable set for other endpoints). To be able to do this we created a custom APIRoute class which wraps classic fastapi APIRoute with a rasterio.Env()
block: github.com/developmentseed/titiler/blob/8a7127ca56631c2c327713d99e80285048c3aa6c/titiler/custom/routing.py#L13-L41
Example:
from fastapi import FastAPI, APIRouter\nfrom rasterio._env import get_gdal_config\nfrom titiler.core.routing import apiroute_factory\nfrom titiler.core.factory import TilerFactory\n\napp = FastAPI()\nroute_class = apiroute_factory({\"GDAL_DISABLE_READDIR_ON_OPEN\": \"FALSE\"})\nrouter = APIRouter(route_class=route_class)\n\ntiler = TilerFactory(router=router)\n\n@router.get(\"/simple\")\ndef simple():\n \"\"\"should return FALSE.\"\"\"\n res = get_gdal_config(\"GDAL_DISABLE_READDIR_ON_OPEN\")\n return {\"env\": res}\n\napp.include_router(router)\n
Important
This has only be tested for python 3.6 and 3.7.
"},{"location":"advanced/Algorithms/","title":"Custom Algorithm","text":"Starting with titiler>=0.8
, we added the possibility to apply custom algorithms on Image outputs from tile
, crop
or preview
endpoints.
The algorithms are meant to overcome the limitation of expression
(using numexpr) by allowing more complex operations.
We added a set of custom algorithms:
hillshade
: Create hillshade from elevation datasetcontours
: Create contours lines (raster) from elevation datasetterrarium
: Mapzen's format to encode elevation value in RGB values (github.com/tilezen/joerd/blob/master/docs/formats.md#terrarium)terrainrgb
: Mapbox's format to encode elevation value in RGB values (docs.mapbox.com/data/tilesets/guides/access-elevation-data/)normalizedIndex
: Normalized Difference Index (e.g NDVI)# return a\nhttpx.get(\n \"http://127.0.0.1:8081/cog/tiles/16/34059/23335\",\n params={\n \"url\": \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\",\n \"buffer\": 3, # By default hillshade will crop the output with a 3pixel buffer, so we need to apply a buffer on the tile\n \"algorithm\": \"hillshade\",\n },\n)\n
# Pass algorithm parameter as a json string\nhttpx.get(\n \"http://127.0.0.1:8081/cog/preview\",\n params={\n \"url\": \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\",\n \"algorithm\": \"contour\",\n \"algorithm_params\": json.dumps({\"minz\": 1600, \"maxz\": 2100}) # algorithm params HAVE TO be provided as a JSON string\n },\n)\n
"},{"location":"advanced/Algorithms/#create-your-own-algorithm","title":"Create your own Algorithm","text":"A titiler'w Algorithm
must be defined using titiler.core.algorithm.BaseAlgorithm
base class.
class BaseAlgorithm(BaseModel, metaclass=abc.ABCMeta):\n \"\"\"Algorithm baseclass.\n\n Note: attribute starting with `input_` or `output_` are considered as metadata\n\n \"\"\"\n\n # metadata\n input_nbands: int\n output_nbands: int\n output_dtype: str\n output_min: Optional[Sequence]\n output_max: Optional[Sequence]\n\n @abc.abstractmethod\n def __call__(self, img: ImageData) -> ImageData:\n \"\"\"Apply algorithm\"\"\"\n ...\n\n class Config:\n \"\"\"Config for model.\"\"\"\n\n extra = \"allow\"\n
This base class defines that algorithm:
HAVE TO implement an __call__
method which takes an ImageData as input and return an ImageData. Using __call__
let us use the object as a callable (e.g Algorithm(**kwargs)(image)
).
can have input/output metadata (informative)
can haveparameters
(enabled by extra = \"allow\"
pydantic config)
Here is a simple example of a custom Algorithm:
from titiler.core.algorithm import BaseAlgorithm\nfrom rio_tiler.models import ImageData\n\nclass Multiply(BaseAlgorithm):\n\n # Parameters\n factor: int # There is no default, which means calls to this algorithm without any parameter will fail\n\n # We don't set any metadata for this Algorithm\n\n def __call__(self, img: ImageData) -> ImageData:\n # Multiply image data bcy factor\n data = img.data * self.factor\n\n # Create output ImageData\n return ImageData(\n data,\n img.mask,\n assets=img.assets,\n crs=img.crs,\n bounds=img.bounds,\n )\n
"},{"location":"advanced/Algorithms/#class-vs-script","title":"Class Vs script","text":"Using a Pydantic's BaseModel
class to construct the custom algorithm enables two things parametrization and type casting/validation.
If we look at the Multiply
algorithm, we can see it needs a factor
parameter. In Titiler (in the post_process dependency) we will pass this parameter via query string (e.g /preview.png?algo=multiply&algo_parameter={\"factor\":3}
) and pydantic will make sure we use the right types/values.
# Available algorithm\nalgo = {\"multiply\": Multiply}\n\ndef post_process_dependency(\n algorithm: Literal[tuple(algo.keys())] = Query(None, description=\"Algorithm name\"),\n algorithm_params: str = Query(None, description=\"Algorithm parameter\"),\n) -> Optional[BaseAlgorithm]:\n \"\"\"Data Post-Processing dependency.\"\"\"\n # Parse `algorithm_params` JSON parameters\n kwargs = json.loads(algorithm_params) if algorithm_params else {}\n if algorithm:\n # Here we construct the Algorithm Object with the kwargs from the `algo_params` query-parameter\n return algo[algorithm](**kwargs)\n\n return None\n
"},{"location":"advanced/Algorithms/#dependency","title":"Dependency","text":"To be able to use your own algorithm in titiler's endpoint you need to create a Dependency
to tell the application what algorithm are available.
To ease the dependency creation, we added a dependency
property in the titiler.core.algorithm.Algorithms
class, which will return a FastAPI dependency to be added to the endpoints.
Note: The Algorithms
class is a store for the algorithm that can be extented using the .register()
method.
from typing import Callable\nfrom titiler.core.algorithm import algorithms as default_algorithms\nfrom titiler.core.algorithm import Algorithms\nfrom titiler.core.factory import TilerFactory\n\n# Add the `Multiply` algorithm to the default ones\nalgorithms: Algorithms = default_algorithms.register({\"multiply\": Multiply})\n\n# Create a PostProcessParams dependency\nPostProcessParams: Callable = algorithms.dependency\n\nendpoints = TilerFactory(process_dependency=PostProcessParams)\n
"},{"location":"advanced/Algorithms/#order-of-operation","title":"Order of operation","text":"When creating a map tile (or other images), we will fist apply the algorithm
then the rescaling
and finally the color_formula
.
with reader(url as src_dst:\n image = src_dst.tile(\n x,\n y,\n z,\n )\n dst_colormap = getattr(src_dst, \"colormap\", None)\n\n# Apply algorithm\nif post_process:\n image = post_process(image)\n\n# Apply data rescaling\nif rescale:\n image.rescale(rescale)\n\n# Apply color-formula\nif color_formula:\n image.apply_color_formula(color_formula)\n\n# Determine the format\nif not format:\n format = ImageType.jpeg if image.mask.all() else ImageType.png\n\n# Image Rendering\nreturn image.render(\n img_format=format.driver,\n colormap=colormap or dst_colormap,\n **format.profile,\n)\n
"},{"location":"advanced/Extensions/","title":"Extensions","text":"Starting with titiler>=0.11
, we added a new titiler package titiler.extensions
which aim to ease the addition of optional
endpoints to factories.
In titiler.core.factory.BaseTilerFactory
class, we've added a new attribute: extensions: List[FactoryExtension] = field(default_factory=list)
. The list
of extension will then be used in the post-init
step such as:
def __post_init__(self):\n \"\"\"Post Init: register route and configure specific options.\"\"\"\n # Register endpoints\n self.register_routes()\n\n # Register Extensions\n for ext in self.extensions:\n ext.register(self)\n\n # Update endpoints dependencies\n for scopes, dependencies in self.route_dependencies:\n self.add_route_dependencies(scopes=scopes, dependencies=dependencies)\n
We defined extension using an Abstract Base Class to make sure they implement a register
method:
@dataclass\nclass FactoryExtension(metaclass=abc.ABCMeta):\n \"\"\"Factory Extension.\"\"\"\n\n @abc.abstractmethod\n def register(self, factory: \"BaseTilerFactory\"):\n \"\"\"Register extension to the factory.\"\"\"\n ...\n
"},{"location":"advanced/Extensions/#available-extensions","title":"Available extensions","text":""},{"location":"advanced/Extensions/#cogvalidateextension","title":"cogValidateExtension","text":"/validate
endpoint which return the content of rio-cogeo info
methodtitiler.extensions[\"cogeo\"]
(installs rio-cogeo
)/viewer
endpoint which return an HTML viewer for simple COGs/viewer
endpoint which return an HTML viewer for STAC item/stac
endpoint which return an HTML viewer for STAC itemtitiler.extensions[\"stac\"]
(installs rio-stac
)/wms
endpoint to support OGC WMS specification (GetCapabilities
and GetMap
)Extensions must be set at TilerFactory's creation using the extensions=
options.
from fastapi import FastAPI\nfrom titiler.core.factory import TilerFactory\nfrom titiler.extensions import cogValidateExtension\n\n# Create a FastAPI application\napp = FastAPI(description=\"A lightweight Cloud Optimized GeoTIFF tile server\")\n\n# Create a set of endpoints using TiTiler TilerFactory\ntiler = TilerFactory(\n router_prefix=\"/cog\",\n extensions=[\n cogValidateExtension() # the cogeoExtension will add a rio-cogeo /validate endpoint\n ]\n)\n\n# Register endpoints to the application\napp.include_router(tiler.router, prefix=\"/cog\")\n
See titiler.application for a full example.
"},{"location":"advanced/Extensions/#create-your-own","title":"Create your own","text":"from dataclasses import dataclass, field\nfrom typing import Tuple, List, Optional\n\nimport rasterio\nfrom starlette.responses import Response\nfrom fastapi import Depends, FastAPI, Query\nfrom titiler.core.factory import BaseTilerFactory, FactoryExtension, TilerFactory\nfrom titiler.core.dependencies import RescalingParams\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.resources.enums import ImageType\n\n\n@dataclass\nclass thumbnailExtension(FactoryExtension):\n \"\"\"Add endpoint to a TilerFactory.\"\"\"\n\n # Set some options\n max_size: int = field(default=128)\n\n # Register method is mandatory and must take a BaseTilerFactory object as input\n def register(self, factory: BaseTilerFactory):\n \"\"\"Register endpoint to the tiler factory.\"\"\"\n\n # register an endpoint to the factory's router\n @factory.router.get(\n \"/thumbnail\",\n responses={\n 200: {\n \"content\": {\n \"image/png\": {},\n \"image/jpeg\": {},\n },\n \"description\": \"Return an image.\",\n }\n },\n response_class=Response,\n )\n def thumbnail(\n # we can reuse the factory dependency\n src_path: str = Depends(factory.path_dependency),\n layer_params=Depends(factory.layer_dependency),\n dataset_params=Depends(factory.dataset_dependency),\n post_process=Depends(factory.process_dependency),\n rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams),\n color_formula: Optional[str] = Query(\n None,\n title=\"Color Formula\",\n description=\"rio-color formula (info: https://github.com/mapbox/rio-color)\",\n ),\n colormap=Depends(factory.colormap_dependency),\n render_params=Depends(factory.render_dependency),\n reader_params=Depends(factory.reader_dependency),\n env=Depends(factory.environment_dependency),\n ):\n with rasterio.Env(**env):\n with factory.reader(src_path, **reader_params.as_dict()) as src:\n image = src.preview(\n max_size=self.max_size,\n **layer_params.as_dict(),\n **dataset_params.as_dict(),\n )\n\n if post_process:\n image = post_process(image)\n\n if rescale:\n image.rescale(rescale)\n\n if color_formula:\n image.apply_color_formula(color_formula)\n\n format = ImageType.jpeg if image.mask.all() else ImageType.png\n\n content = image.render(\n img_format=format.driver,\n colormap=colormap,\n **format.profile,\n **render_params.as_dict(),\n )\n\n return Response(content, media_type=format.mediatype)\n\n# Use it\napp = FastAPI()\ntiler = TilerFactory(\n extensions=[\n thumbnailExtension(max_size=64)\n ]\n)\napp.include_router(tiler.router)\n
"},{"location":"advanced/customization/","title":"Customization","text":"TiTiler
is designed to help user customize input/output for each endpoint. This section goes over some simple customization examples.
Add user defined colormap to the default colormaps provided by rio-tiler
from fastapi import FastAPI\n\nfrom rio_tiler.colormap import cmap as default_cmap\n\nfrom titiler.core.dependencies import create_colormap_dependency\nfrom titiler.core.factory import TilerFactory\n\n\napp = FastAPI(title=\"My simple app with custom TMS\")\n\ncmap_values = {\n \"cmap1\": {6: (4, 5, 6, 255)},\n}\n# add custom colormap `cmap1` to the default colormaps\ncmap = default_cmap.register(cmap_values)\nColorMapParams = create_colormap_dependency(cmap)\n\n\ncog = TilerFactory(colormap_dependency=ColorMapParams)\napp.include_router(cog.router)\n
"},{"location":"advanced/customization/#custom-datasetpathparams-for-reader_dependency","title":"Custom DatasetPathParams for reader_dependency
","text":"One common customization could be to create your own path_dependency
. This dependency is used on all endpoint and pass inputs to the Readers (MosaicBackend, COGReader, STACReader...).
Here an example which allow a mosaic to be passed by a mosaic name
instead of a full S3 url.
import os\nimport re\n\nfrom fastapi import FastAPI, HTTPException, Query\n\nfrom titiler.mosaic.factory import MosaicTilerFactory\n\n\nMOSAIC_BACKEND = os.getenv(\"TITILER_MOSAIC_BACKEND\")\nMOSAIC_HOST = os.getenv(\"TITILER_MOSAIC_HOST\")\n\n\ndef MosaicPathParams(\n mosaic: str = Query(..., description=\"mosaic name\")\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n # mosaic name should be in form of `{user}.{layername}`\n if not re.match(self.mosaic, r\"^[a-zA-Z0-9-_]{1,32}\\.[a-zA-Z0-9-_]{1,32}$\"):\n raise HTTPException(\n status_code=400,\n detail=f\"Invalid mosaic name {self.input}.\",\n )\n\n return f\"{MOSAIC_BACKEND}{MOSAIC_HOST}/{self.input}.json.gz\"\n\n\napp = FastAPI()\nmosaic = MosaicTilerFactory(path_dependency=MosaicPathParams)\napp.include_router(mosaic.router)\n
The endpoint url will now look like: {endpoint}/mosaic/tilejson.json?mosaic=vincent.mosaic
from morecantile import tms, TileMatrixSet\nfrom pyproj import CRS\n\nfrom titiler.core.factory import TilerFactory\n\n# 1. Create Custom TMS\nEPSG6933 = TileMatrixSet.custom(\n (-17357881.81713629, -7324184.56362408, 17357881.81713629, 7324184.56362408),\n CRS.from_epsg(6933),\n identifier=\"EPSG6933\",\n matrix_scale=[1, 1],\n)\n\n# 2. Register TMS\ntms = tms.register([EPSG6933])\n\n# 3. Create Tiler\nCOGTilerWithCustomTMS = TilerFactory(supported_tms=tms)\n
"},{"location":"advanced/customization/#add-a-mosaicjson-creation-endpoint","title":"Add a MosaicJSON creation endpoint","text":"from dataclasses import dataclass\nfrom typing import List, Optional\n\nfrom titiler.mosaic.factory import MosaicTilerFactory\nfrom titiler.core.errors import BadRequestError\nfrom cogeo_mosaic.mosaic import MosaicJSON\nfrom cogeo_mosaic.utils import get_footprints\nimport rasterio\n\nfrom pydantic import BaseModel\n\n\n# Models from POST/PUT Body\nclass CreateMosaicJSON(BaseModel):\n \"\"\"Request body for MosaicJSON creation\"\"\"\n\n files: List[str] # Files to add to the mosaic\n url: str # path where to save the mosaicJSON\n minzoom: Optional[int] = None\n maxzoom: Optional[int] = None\n max_threads: int = 20\n overwrite: bool = False\n\n\nclass UpdateMosaicJSON(BaseModel):\n \"\"\"Request body for updating an existing MosaicJSON\"\"\"\n\n files: List[str] # Files to add to the mosaic\n url: str # path where to save the mosaicJSON\n max_threads: int = 20\n add_first: bool = True\n\n\n@dataclass\nclass CustomMosaicFactory(MosaicTilerFactory):\n\n def register_routes(self):\n \"\"\"Update the class method to add create/update\"\"\"\n super().register_routes()\n # new methods/endpoint\n self.create()\n self.update()\n\n def create(self):\n \"\"\"Register / (POST) Create endpoint.\"\"\"\n\n @self.router.post(\n \"\", response_model=MosaicJSON, response_model_exclude_none=True\n )\n def create(\n body: CreateMosaicJSON,\n env=Depends(self.environment_dependency),\n ):\n \"\"\"Create a MosaicJSON\"\"\"\n # Write can write to either a local path, a S3 path...\n # See https://developmentseed.org/cogeo-mosaic/advanced/backends/ for the list of supported backends\n\n # Create a MosaicJSON file from a list of URL\n mosaic = MosaicJSON.from_urls(\n body.files,\n minzoom=body.minzoom,\n maxzoom=body.maxzoom,\n max_threads=body.max_threads,\n )\n\n # Write the MosaicJSON using a cogeo-mosaic backend\n with rasterio.Env(**env):\n with self.reader(\n body.url, mosaic_def=mosaic, reader=self.dataset_reader\n ) as mosaic:\n try:\n mosaic.write(overwrite=body.overwrite)\n except NotImplementedError:\n raise BadRequestError(\n f\"{mosaic.__class__.__name__} does not support write operations\"\n )\n return mosaic.mosaic_def\n\n def update(self):\n \"\"\"Register / (PUST) Update endpoint.\"\"\"\n\n @self.router.put(\n \"\", response_model=MosaicJSON, response_model_exclude_none=True\n )\n def update_mosaicjson(\n body: UpdateMosaicJSON,\n env=Depends(self.environment_dependency),\n ):\n \"\"\"Update an existing MosaicJSON\"\"\"\n with rasterio.Env(**env):\n with self.reader(body.url, reader=self.dataset_reader) as mosaic:\n features = get_footprints(body.files, max_threads=body.max_threads)\n try:\n mosaic.update(features, add_first=body.add_first, quiet=True)\n except NotImplementedError:\n raise BadRequestError(\n f\"{mosaic.__class__.__name__} does not support update operations\"\n )\n return mosaic.mosaic_def\n
"},{"location":"advanced/dependencies/","title":"Dependencies","text":"If you are new to the concept of Dependency Injection, please read this awesome tutorial: fastapi.tiangolo.com/tutorial/dependencies/
In titiler Factories
, we use the dependencies to define the inputs for each endpoint (and thus the OpenAPI documentation).
Example:
from dataclasses import dataclass\nfrom fastapi import Depends, FastAPI, Query\nfrom titiler.core.dependencies import DefaultDependency\nfrom typing_extensions import Annotated\nfrom rio_tiler.io import Reader\n\n@dataclass\nclass ImageParams(DefaultDependency):\n max_size: Annotated[\n int, Query(description=\"Maximum image size to read onto.\")\n ] = 1024\n\napp = FastAPI()\n\n# Simple preview endpoint\n@app.get(\"/preview.png\")\ndef preview(\n url: str = Query(..., description=\"data set URL\"),\n params: ImageParams = Depends(),\n):\n with Reader(url) as cog:\n img = cog.preview(**params.as_dict()) # we use `DefaultDependency().as_dict()` to pass only non-None parameters\n # or\n img = cog.preview(max_size=params.max_size)\n ...\n
Important
In the example above, we create a custom ImageParams
dependency which will then be injected to the preview
endpoint to add max_size, height and width query string parameters.
Using titiler.core.dependencies.DefaultDependency
, we can use .as_dict(exclude_none=True/False)
method to unpack
the object parameters. This can be useful if method or reader do not take the same parameters.
Define assets
.
@dataclass\nclass AssetsParams(DefaultDependency):\n \"\"\"Assets parameters.\"\"\"\n\n assets: List[str] = Query(\n None,\n title=\"Asset names\",\n description=\"Asset's names.\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return results for asset `data`.\",\n \"value\": [\"data\"],\n },\n \"multi-assets\": {\n \"description\": \"Return results for assets `data` and `cog`.\",\n \"value\": [\"data\", \"cog\"],\n },\n },\n )\n
"},{"location":"advanced/dependencies/#assetsbidxparams","title":"AssetsBidxParams","text":"Define assets
with option of per-asset
expression with asset_expression
option.
@dataclass\nclass AssetsBidxParams(AssetsParams):\n \"\"\"Assets, Asset's band Indexes and Asset's band Expression parameters.\"\"\"\n\n asset_indexes: Annotated[\n Optional[Sequence[str]],\n Query(\n title=\"Per asset band indexes\",\n description=\"Per asset band indexes\",\n alias=\"asset_bidx\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return indexes 1,2,3 of asset `data`.\",\n \"value\": [\"data|1;2;3\"],\n },\n \"multi-assets\": {\n \"description\": \"Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`\",\n \"value\": [\"data|1;2;3\", \"cog|1\"],\n },\n },\n ),\n ] = None\n\n asset_expression: Annotated[\n Optional[Sequence[str]],\n Query(\n title=\"Per asset band expression\",\n description=\"Per asset band expression\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return results for expression `b1*b2+b3` of asset `data`.\",\n \"value\": [\"data|b1*b2+b3\"],\n },\n \"multi-assets\": {\n \"description\": \"Return results for expressions `b1*b2+b3` for asset `data` and `b1+b3` for asset `cog`.\",\n \"value\": [\"data|b1*b2+b3\", \"cog|b1+b3\"],\n },\n },\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.asset_indexes:\n self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore\n idx.split(\"|\")[0]: list(map(int, idx.split(\"|\")[1].split(\",\")))\n for idx in self.asset_indexes\n }\n\n if self.asset_expression:\n self.asset_expression: Dict[str, str] = { # type: ignore\n idx.split(\"|\")[0]: idx.split(\"|\")[1] for idx in self.asset_expression\n }\n
"},{"location":"advanced/dependencies/#assetsbidxexprparams","title":"AssetsBidxExprParams","text":"Define assets
.
* assets
or expression
is required.
@dataclass\nclass AssetsBidxExprParams(AssetsParams):\n \"\"\"Assets, Expression and Asset's band Indexes parameters.\"\"\"\n\n expression: Annotated[\n Optional[str],\n Query(\n title=\"Band Math expression\",\n description=\"Band math expression between assets\",\n openapi_examples={\n \"simple\": {\n \"description\": \"Return results of expression between assets.\",\n \"value\": \"asset1_b1 + asset2_b1 / asset3_b1\",\n },\n },\n ),\n ] = None\n\n asset_indexes: Annotated[\n Optional[Sequence[str]],\n Query(\n title=\"Per asset band indexes\",\n description=\"Per asset band indexes (coma separated indexes)\",\n alias=\"asset_bidx\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return indexes 1,2,3 of asset `data`.\",\n \"value\": [\"data|1,2,3\"],\n },\n \"multi-assets\": {\n \"description\": \"Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`\",\n \"value\": [\"data|1,2,3\", \"cog|1\"],\n },\n },\n ),\n ] = None\n\n asset_as_band: Annotated[\n Optional[bool],\n Query(\n title=\"Consider asset as a 1 band dataset\",\n description=\"Asset as Band\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if not self.assets and not self.expression:\n raise MissingAssets(\n \"assets must be defined either via expression or assets options.\"\n )\n\n if self.asset_indexes:\n self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore\n idx.split(\"|\")[0]: list(map(int, idx.split(\"|\")[1].split(\",\")))\n for idx in self.asset_indexes\n }\n
"},{"location":"advanced/dependencies/#assetsbidxexprparamsoptional","title":"AssetsBidxExprParamsOptional","text":"Define assets
. Without requirement on assets nor expression.
@dataclass\nclass AssetsBidxExprParamsOptional(AssetsBidxExprParams):\n \"\"\"Assets, Expression and Asset's band Indexes parameters but with no requirement.\"\"\"\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.asset_indexes:\n self.asset_indexes: Dict[str, Sequence[int]] = { # type: ignore\n idx.split(\"|\")[0]: list(map(int, idx.split(\"|\")[1].split(\",\")))\n for idx in self.asset_indexes\n }\n
"},{"location":"advanced/dependencies/#bandsparams","title":"BandsParams","text":"Define bands
.
@dataclass\nclass BandsParams(DefaultDependency):\n \"\"\"Band names parameters.\"\"\"\n\n bands: List[str] = Query(\n None,\n title=\"Band names\",\n description=\"Band's names.\",\n openapi_examples={\n \"one-band\": {\n \"description\": \"Return results for band `B01`.\",\n \"value\": [\"B01\"],\n },\n \"multi-bands\": {\n \"description\": \"Return results for bands `B01` and `B02`.\",\n \"value\": [\"B01\", \"B02\"],\n },\n },\n )\n
"},{"location":"advanced/dependencies/#bandsexprparams","title":"BandsExprParams","text":"Define bands
.
* bands
or expression
is required.
@dataclass\nclass BandsExprParamsOptional(ExpressionParams, BandsParams):\n \"\"\"Optional Band names and Expression parameters.\"\"\"\n\n pass\n
"},{"location":"advanced/dependencies/#bandsexprparamsoptional","title":"BandsExprParamsOptional","text":"Define bands
.
@dataclass\nclass BandsExprParamsOptional(ExpressionParams, BandsParams):\n \"\"\"Optional Band names and Expression parameters.\"\"\"\n\n pass\n
"},{"location":"advanced/dependencies/#bidxparams","title":"BidxParams
","text":"Define band indexes.
Name Type Required Default bidx Query (int) No None@dataclass\nclass BidxParams(DefaultDependency):\n \"\"\"Band Indexes parameters.\"\"\"\n\n indexes: Annotated[\n Optional[List[int]],\n Query(\n title=\"Band indexes\",\n alias=\"bidx\",\n description=\"Dataset band indexes\",\n openapi_examples={\"one-band\": {\"value\": [1]}, \"multi-bands\": {\"value\": [1, 2, 3]}},\n ),\n ] = None\n
"},{"location":"advanced/dependencies/#expressionparams","title":"ExpressionParams
","text":"Define band expression.
Name Type Required Default expression Query (str) No None@dataclass\nclass ExpressionParams(DefaultDependency):\n \"\"\"Expression parameters.\"\"\"\n\n expression: Annotated[\n Optional[str],\n Query(\n title=\"Band Math expression\",\n description=\"rio-tiler's band math expression\",\n openapi_examples={\n \"simple\": {\"description\": \"Simple band math.\", \"value\": \"b1/b2\"},\n \"multi-bands\": {\n \"description\": \"Semicolon (;) delimited expressions (band1: b1/b2, band2: b2+b3).\",\n \"value\": \"b1/b2;b2+b3\",\n },\n },\n ),\n ] = None\n
"},{"location":"advanced/dependencies/#bidxexprparams","title":"BidxExprParams
","text":"Define band indexes or expression.
Name Type Required Default bidx Query (int) No None expression Query (str) No None@dataclass\nclass BidxExprParams(ExpressionParams, BidxParams):\n \"\"\"Band Indexes and Expression parameters.\"\"\"\n\n pass\n
"},{"location":"advanced/dependencies/#colorformulaparams","title":"ColorFormulaParams
","text":"Color Formula option (see vincentsarago/color-operations).
Name Type Required Default color_formula Query (str) No Nonedef ColorFormulaParams(\n color_formula: Annotated[\n Optional[str],\n Query(\n title=\"Color Formula\",\n description=\"rio-color formula (info: https://github.com/mapbox/rio-color)\",\n ),\n ] = None,\n) -> Optional[str]:\n \"\"\"ColorFormula Parameter.\"\"\"\n return color_formula\n
"},{"location":"advanced/dependencies/#colormapparams","title":"ColorMapParams
","text":"Colormap options. See titiler.core.dependencies.
Name Type Required Default colormap_name Query (str) No None colormap Query (encoded json) No Nonecmap = {}\n\ndef ColorMapParams(\n colormap_name: Annotated[ # type: ignore\n Literal[tuple(cmap.list())],\n Query(description=\"Colormap name\"),\n ] = None,\n colormap: Annotated[\n Optional[str], Query(description=\"JSON encoded custom Colormap\")\n ] = None,\n):\n if colormap_name:\n return cmap.get(colormap_name)\n\n if colormap:\n try:\n c = json.loads(\n colormap,\n object_hook=lambda x: {\n int(k): parse_color(v) for k, v in x.items()\n },\n )\n\n # Make sure to match colormap type\n if isinstance(c, Sequence):\n c = [(tuple(inter), parse_color(v)) for (inter, v) in c]\n\n return c\n except json.JSONDecodeError as e:\n raise HTTPException(\n status_code=400, detail=\"Could not parse the colormap value.\"\n ) from e\n\n return None\n
"},{"location":"advanced/dependencies/#coordcrsparams","title":"CoordCRSParams","text":"Define input Coordinate Reference System.
Name Type Required Default crs Query (str) No Nonedef CoordCRSParams(\n crs: Annotated[\n Optional[str],\n Query(\n alias=\"coord_crs\",\n description=\"Coordinate Reference System of the input coords. Default to `epsg:4326`.\",\n ),\n ] = None,\n) -> Optional[CRS]:\n \"\"\"Coordinate Reference System Coordinates Param.\"\"\"\n if crs:\n return CRS.from_user_input(crs)\n\n return None\n
"},{"location":"advanced/dependencies/#datasetparams","title":"DatasetParams
","text":"Overwrite nodata
value, apply rescaling
and change the I/O
or Warp
resamplings.
@dataclass\nclass DatasetParams(DefaultDependency):\n \"\"\"Low level WarpedVRT Optional parameters.\"\"\"\n\n nodata: Annotated[\n Optional[Union[str, int, float]],\n Query(\n title=\"Nodata value\",\n description=\"Overwrite internal Nodata value\",\n ),\n ] = None\n unscale: Annotated[\n bool,\n Query(\n title=\"Apply internal Scale/Offset\",\n description=\"Apply internal Scale/Offset. Defaults to `False` in rio-tiler.\",\n ),\n ] = False\n resampling_method: Annotated[\n Optional[RIOResampling],\n Query(\n alias=\"resampling\",\n description=\"RasterIO resampling algorithm. Defaults to `nearest` in rio-tiler.\",\n ),\n ] = None\n reproject_method: Annotated[\n Optional[WarpResampling],\n Query(\n alias=\"reproject\",\n description=\"WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest` in rio-tiler.\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.nodata is not None:\n self.nodata = numpy.nan if self.nodata == \"nan\" else float(self.nodata)\n\n if self.unscale is not None:\n self.unscale = bool(self.unscale)\n
"},{"location":"advanced/dependencies/#datasetpathparams","title":"DatasetPathParams
","text":"Set dataset path.
Name Type Required Default url Query (str) Yes -def DatasetPathParams(\n url: Annotated[str, Query(description=\"Dataset URL\")]\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n return url\n
"},{"location":"advanced/dependencies/#dstcrsparams","title":"DstCRSParams","text":"Define output Coordinate Reference System.
Name Type Required Default crs Query (str) No Nonedef DstCRSParams(\n crs: Annotated[\n Optional[str],\n Query(\n alias=\"dst_crs\",\n description=\"Output Coordinate Reference System.\",\n ),\n ] = None,\n) -> Optional[CRS]:\n \"\"\"Coordinate Reference System Coordinates Param.\"\"\"\n if crs:\n return CRS.from_user_input(crs)\n\n return None\n
"},{"location":"advanced/dependencies/#histogramparams","title":"HistogramParams","text":"Define numpy's histogram options.
Name Type Required Default histogram_bins Query (encoded list of Number) No 10 histogram_range Query (encoded list of Number) No None@dataclass\nclass HistogramParams(DefaultDependency):\n \"\"\"Numpy Histogram options.\"\"\"\n\n bins: Annotated[\n Optional[str],\n Query(\n alias=\"histogram_bins\",\n title=\"Histogram bins.\",\n description=\"\"\"\nDefines the number of equal-width bins in the given range (10, by default).\n\nIf bins is a sequence (comma `,` delimited values), it defines a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform bin widths.\n\nlink: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html\n \"\"\",\n openapi_examples={\n \"simple\": {\n \"description\": \"Defines the number of equal-width bins\",\n \"value\": 8,\n },\n \"array\": {\n \"description\": \"Defines custom bin edges (comma `,` delimited values)\",\n \"value\": \"0,100,200,300\",\n },\n },\n ),\n ] = None\n\n range: Annotated[\n Optional[str],\n Query(\n alias=\"histogram_range\",\n title=\"Histogram range\",\n description=\"\"\"\nComma `,` delimited range of the bins.\n\nThe lower and upper range of the bins. If not provided, range is simply (a.min(), a.max()).\n\nValues outside the range are ignored. The first element of the range must be less than or equal to the second.\nrange affects the automatic bin computation as well.\n\nlink: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html\n \"\"\",\n examples=\"0,1000\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.bins:\n bins = self.bins.split(\",\")\n if len(bins) == 1:\n self.bins = int(bins[0]) # type: ignore\n else:\n self.bins = list(map(float, bins)) # type: ignore\n else:\n self.bins = 10\n\n if self.range:\n self.range = list(map(float, self.range.split(\",\"))) # type: ignore\n
"},{"location":"advanced/dependencies/#imagerenderingparams","title":"ImageRenderingParams
","text":"Control output image rendering options.
Name Type Required Default return_mask Query (bool) No False@dataclass\nclass ImageRenderingParams(DefaultDependency):\n \"\"\"Image Rendering options.\"\"\"\n\n add_mask: Annotated[\n Optional[bool],\n Query(\n alias=\"return_mask\",\n description=\"Add mask to the output data. Defaults to `True` in rio-tiler\",\n ),\n ] = None\n
"},{"location":"advanced/dependencies/#partfeatureparams","title":"PartFeatureParams","text":"Same as PreviewParams
but without default max_size
.
@dataclass\nclass PartFeatureParams(DefaultDependency):\n \"\"\"Common parameters for bbox and feature.\"\"\"\n\n max_size: Annotated[Optional[int], \"Maximum image size to read onto.\"] = None\n height: Annotated[Optional[int], \"Force output image height.\"] = None\n width: Annotated[Optional[int], \"Force output image width.\"] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width and self.height:\n self.max_size = None\n
"},{"location":"advanced/dependencies/#pixelselectionparams","title":"PixelSelectionParams","text":"In titiler.mosaic
, define pixel-selection method to apply.
def PixelSelectionParams(\n pixel_selection: Annotated[ # type: ignore\n Literal[tuple([e.name for e in PixelSelectionMethod])],\n Query(description=\"Pixel selection method.\"),\n ] = \"first\",\n) -> MosaicMethodBase:\n \"\"\"\n Returns the mosaic method used to combine datasets together.\n \"\"\"\n return PixelSelectionMethod[pixel_selection].value()\n
"},{"location":"advanced/dependencies/#previewparams","title":"PreviewParams","text":"Define image output size.
Name Type Required Default max_size Query (int) No 1024 height Query (int) No None width Query (int) No None@dataclass\nclass PreviewParams(DefaultDependency):\n \"\"\"Common Preview parameters.\"\"\"\n\n max_size: Annotated[int, \"Maximum image size to read onto.\"] = 1024\n height: Annotated[Optional[int], \"Force output image height.\"] = None\n width: Annotated[Optional[int], \"Force output image width.\"] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width and self.height:\n self.max_size = None\n
"},{"location":"advanced/dependencies/#rescalingparams","title":"RescalingParams
","text":"Set Min/Max values to rescale from, to 0 -> 255.
Name Type Required Default rescale Query (str, comma delimited Numer) No Nonedef RescalingParams(\n rescale: Annotated[\n Optional[List[str]],\n Query(\n title=\"Min/Max data Rescaling\",\n description=\"comma (',') delimited Min,Max range. Can set multiple time for multiple bands.\",\n examples=[\"0,2000\", \"0,1000\", \"0,10000\"], # band 1 # band 2 # band 3\n ),\n ] = None,\n) -> Optional[RescaleType]:\n \"\"\"Min/Max data Rescaling\"\"\"\n if rescale:\n return [tuple(map(float, r.replace(\" \", \"\").split(\",\"))) for r in rescale]\n\n return None\n
"},{"location":"advanced/dependencies/#statisticsparams","title":"StatisticsParams","text":"Define options for rio-tiler's statistics method.
Name Type Required Default categorical Query (bool) No False categories Query (list of Number) No None p Query (list of Number) No [2, 98]@dataclass\nclass StatisticsParams(DefaultDependency):\n \"\"\"Statistics options.\"\"\"\n\n categorical: Annotated[\n Optional[bool],\n Query(description=\"Return statistics for categorical dataset. Defaults to `False` in rio-tiler\"),\n ] = None\n categories: Annotated[\n Optional[List[Union[float, int]]],\n Query(\n alias=\"c\",\n title=\"Pixels values for categories.\",\n description=\"List of values for which to report counts.\",\n examples=[1, 2, 3],\n ),\n ] = None\n percentiles: Annotated[\n Optional[List[int]],\n Query(\n alias=\"p\",\n title=\"Percentile values\",\n description=\"List of percentile values (default to [2, 98]).\",\n examples=[2, 5, 95, 98],\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Set percentiles default.\"\"\"\n if not self.percentiles:\n self.percentiles = [2, 98]\n
"},{"location":"advanced/dependencies/#tileparams","title":"TileParams","text":"Defile buffer
and padding
to apply at tile creation.
@dataclass\nclass TileParams(DefaultDependency):\n \"\"\"Tile options.\"\"\"\n\n buffer: Annotated[\n Optional[float],\n Query(\n gt=0,\n title=\"Tile buffer.\",\n description=\"Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).\",\n ),\n ] = None\n\n padding: Annotated[\n Optional[int],\n Query(\n gt=0,\n title=\"Tile padding.\",\n description=\"Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`.\",\n ),\n ] = None\n
"},{"location":"advanced/dependencies/#algorithmdependency","title":"algorithm.dependency
","text":"Control which algorithm
to apply to the data.
See titiler.core.algorithm.
Name Type Required Default algorithm Query (str) No None algorithm_params Query (encoded json) No Nonealgorithms = {}\n\ndef post_process(\n algorithm: Annotated[\n Literal[tuple(algorithms.keys())],\n Query(description=\"Algorithm name\"),\n ] = None,\n algorithm_params: Annotated[\n Optional[str],\n Query(description=\"Algorithm parameter\"),\n ] = None,\n) -> Optional[BaseAlgorithm]:\n \"\"\"Data Post-Processing options.\"\"\"\n kwargs = json.loads(algorithm_params) if algorithm_params else {}\n if algorithm:\n try:\n return algorithms.get(algorithm)(**kwargs)\n\n except ValidationError as e:\n raise HTTPException(status_code=400, detail=str(e)) from e\n\n return None\n
"},{"location":"advanced/endpoints_factories/","title":"Endpoints Factories","text":"TiTiler's endpoints factories are helper functions that let users create a FastAPI router (fastapi.APIRouter
) with a minimal set of endpoints.
Important
Most of tiler
Factories are built around rio_tiler.io.BaseReader
, which defines basic methods to access datasets (e.g COG or STAC). The default reader is Reader
for TilerFactory
and MosaicBackend
for MosaicTilerFactory
.
Factories classes use dependencies injection to define most of the endpoint options.
"},{"location":"advanced/endpoints_factories/#basetilerfactory","title":"BaseTilerFactory","text":"class: titiler.core.factory.BaseTilerFactory
Most Factories are built from this abstract based class which is used to define commons attributes and utility functions shared between all factories.
"},{"location":"advanced/endpoints_factories/#methods","title":"Methods","text":"fastapi.APIRouter
.titiler.core.dependencies.DatasetPathParams
.titiler.core.dependencies.BidxExprParams
.nodata
value, apply rescaling
and change the I/O
or Warp
resamplings. Defaults to titiler.core.dependencies.DatasetParams
.algorithm
to apply to the data. Defaults to titiler.core.algorithm.algorithms.dependency
.titiler.core.dependencies.RescalingParams
.titiler.core.dependencies.ColorFormulaParams
.titiler.core.dependencies.ColorMapParams
titiler.core.dependencies.ImageRenderingParams
titiler.core.dependencies.DefaultDependency
lambda: {}
.morecantile.tms
.TileMatrixSet
identifier to use. Defaults to WebMercatorQuad
.\"\"
.OptionalHeader
which endpoints could add (if implemented). Defaults to []
.[]
.[]
.titiler.core.factory.DEFAULT_TEMPLATES
.class: titiler.core.factory.TilerFactory
Factory meant to create endpoints for single dataset using rio-tiler's Reader
.
Reader
./statistics
endpoints. Defaults to titiler.core.dependencies.StatisticsParams
./statistics
endpoints. Defaults to titiler.core.dependencies.HistogramParams
./preview
and /statistics
endpoints. Defaults to titiler.core.dependencies.PreviewParams
./bbox
and /feature
endpoints. Defaults to titiler.core.dependencies.PartFeatureParams
.buffer
and padding
to apply at tile creation. Defaults to titiler.core.dependencies.TileParams
./preview
endpoint to the router. Defaults to True
./bbox
and /feature
endpoints to the router. Defaults to True
./map
endpoints to the router. Defaults to True
.from fastapi import FastAPI\n\nfrom titiler.core.factory import TilerFactory\n\n# Create FastAPI application\napp = FastAPI()\n\n# Create router and register set of endpoints\ncog = TilerFactory(\n add_preview=True,\n add_part=True,\n add_viewer=True,\n)\n\n# add router endpoint to the main application\napp.include_router(cog.router)\n
Method URL Output Description GET
/bounds
JSON (Bounds) return dataset's bounds GET
/info
JSON (Info) return dataset's basic info GET
/info.geojson
GeoJSON (InfoGeoJSON) return dataset's basic info as a GeoJSON feature GET
/statistics
JSON (Statistics) return dataset's statistics POST
/statistics
GeoJSON (Statistics) return dataset's statistics for a GeoJSON GET
/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
image/bin create a web map tile image from a dataset GET
/{tileMatrixSetId}/tilejson.json
JSON (TileJSON) return a Mapbox TileJSON document GET
/{tileMatrixSetId}/WMTSCapabilities.xml
XML return OGC WMTS Get Capabilities GET
/point/{lon},{lat}
JSON (Point) return pixel values from a dataset GET
/preview[.{format}]
image/bin create a preview image from a dataset Optional 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 GET
/{tileMatrixSetId}/map
HTML return a simple map viewer Optional"},{"location":"advanced/endpoints_factories/#multibasetilerfactory","title":"MultiBaseTilerFactory","text":"class: titiler.core.factory.MultiBaseTilerFactory
Custom TilerFactory
to be used with rio_tiler.io.MultiBaseReader
type readers (e.g rio_tiler.io.STACReader
).
MultiBase
Dataset Reader required.titiler.core.dependencies.AssetsBidxExprParams
.titiler.core.dependencies.AssetsParams
.from fastapi import FastAPI\n\nfrom rio_tiler.io import STACReader # STACReader is a MultiBaseReader\n\nfrom titiler.core.factory import MultiBaseTilerFactory\n\napp = FastAPI()\nstac = MultiBaseTilerFactory(reader=STACReader)\napp.include_router(stac.router)\n
Method URL Output Description GET
/bounds
JSON (Bounds) return dataset's bounds GET
/assets
JSON return the list of available assets GET
/info
JSON (Info) return assets basic info GET
/info.geojson
GeoJSON (InfoGeoJSON) return assets basic info as a GeoJSON feature GET
/asset_statistics
JSON (Statistics) return per asset statistics GET
/statistics
JSON (Statistics) return assets statistics (merged) POST
/statistics
GeoJSON (Statistics) return assets statistics for a GeoJSON (merged) GET
/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
image/bin create a web map tile image from assets GET
/{tileMatrixSetId}/tilejson.json
JSON (TileJSON) return a Mapbox TileJSON document GET
/{tileMatrixSetId}/WMTSCapabilities.xml
XML return OGC WMTS Get Capabilities GET
/point/{lon},{lat}
JSON (Point) return pixel values from assets GET
/preview[.{format}]
image/bin create a preview image from assets Optional 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 GET
/{tileMatrixSetId}/map
HTML return a simple map viewer Optional"},{"location":"advanced/endpoints_factories/#multibandtilerfactory","title":"MultiBandTilerFactory","text":"class: titiler.core.factory.MultiBandTilerFactory
Custom TilerFactory
to be used with rio_tiler.io.MultiBandReader
type readers.
MultiBands
Dataset Reader required.titiler.core.dependencies.BandsExprParams
.titiler.core.dependencies.BandsParams
.from fastapi import FastAPI, Query\n\n\nfrom rio_tiler_pds.landsat.aws import LandsatC2Reader # LandsatC2Reader is a MultiBandReader\nfrom titiler.core.factory import MultiBandTilerFactory\n\n\ndef SceneIDParams(\n sceneid: Annotated[\n str,\n Query(description=\"Landsat Scene ID\")\n ]\n) -> str:\n \"\"\"Use `sceneid` in query instead of url.\"\"\"\n return sceneid\n\n\napp = FastAPI()\nlandsat = MultiBandTilerFactory(reader=LandsatC2Reader, path_dependency=SceneIDParams)\napp.include_router(landsat.router)\n
Method URL Output Description GET
/bounds
JSON (Bounds) return dataset's bounds GET
/bands
JSON return the list of available bands GET
/info
JSON (Info) return basic info for a dataset GET
/info.geojson
GeoJSON (InfoGeoJSON) return basic info for a dataset as a GeoJSON feature GET
/statistics
JSON (Statistics) return info and statistics for a dataset POST
/statistics
GeoJSON (Statistics) return info and statistics for a dataset GET
/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
image/bin create a web map tile image from a dataset GET
/{tileMatrixSetId}/tilejson.json
JSON (TileJSON) return a Mapbox TileJSON document GET
/{tileMatrixSetId}/WMTSCapabilities.xml
XML return OGC WMTS Get Capabilities GET
/point/{lon},{lat}
JSON (Point) return pixel value from a dataset GET
/preview[.{format}]
image/bin create a preview image from a dataset Optional 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 GET
/{tileMatrixSetId}/map
HTML return a simple map viewer Optional"},{"location":"advanced/endpoints_factories/#mosaictilerfactory","title":"MosaicTilerFactory","text":"class: titiler.mosaic.factory.MosaicTilerFactory
Endpoints factory for mosaics, built on top of MosaicJSON.
"},{"location":"advanced/endpoints_factories/#attributes_4","title":"Attributes","text":"BaseBackend
Mosaic Reader required.rio_tiler.io.Reader
titiler.core.dependencies.DefaultDependency
pixel_selection
method. Defaults to titiler.mosaic.factory.PixelSelectionParams
.buffer
and padding
to apply at tile creation. Defaults to titiler.core.dependencies.TileParams
.morecantile.tms
.TileMatrixSet
identifier to use. Defaults to WebMercatorQuad
./map
endpoints to the router. Defaults to True
.GET
/
JSON MosaicJSON return a MosaicJSON document GET
/bounds
JSON (Bounds) return mosaic's bounds GET
/info
JSON (Info) return mosaic's basic info GET
/info.geojson
GeoJSON (InfoGeoJSON) return mosaic's basic info as a GeoJSON feature GET
/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
image/bin create a web map tile image from a MosaicJSON GET
/{tileMatrixSetId}/tilejson.json
JSON (TileJSON) return a Mapbox TileJSON document GET
/{tileMatrixSetId}/WMTSCapabilities.xml
XML return OGC WMTS Get Capabilities GET
/point/{lon},{lat}
JSON (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 GET
/{tileMatrixSetId}/map
HTML return a simple map viewer Optional"},{"location":"advanced/endpoints_factories/#tmsfactory","title":"TMSFactory","text":"class: titiler.core.factory.TMSFactory
Endpoints factory for OGC TileMatrixSets
.
morecantile.tms
.from fastapi import FastAPI\n\nfrom titiler.core.factory import TMSFactory\n\napp = FastAPI()\ntms = TMSFactory()\napp.include_router(tms.router)\n
"},{"location":"advanced/endpoints_factories/#endpoints_4","title":"Endpoints","text":"Method URL Output Description GET
/tileMatrixSets
JSON (TileMatrixSetList) retrieve the list of available tiling schemes (tile matrix sets) GET
/tileMatrixSets/{tileMatrixSetId}
JSON (TileMatrixSet) retrieve the definition of the specified tiling scheme (tile matrix set)"},{"location":"advanced/endpoints_factories/#algorithmfactory","title":"AlgorithmFactory","text":"class: titiler.core.factory.AlgorithmFactory
Endpoints factory for custom algorithms.
"},{"location":"advanced/endpoints_factories/#attributes_6","title":"Attributes","text":"Algorithm
. Defaults to titiler.core.algorithm.algorithms
.from fastapi import FastAPI\n\nfrom titiler.core.factory import AlgorithmFactory\n\napp = FastAPI()\nalgo = AlgorithmFactory()\napp.include_router(algo.router)\n
"},{"location":"advanced/endpoints_factories/#endpoints_5","title":"Endpoints","text":"Method URL Output Description GET
/algorithms
JSON (Dict of Algorithm Metadata) retrieve the list of available Algorithms GET
/algorithms/{algorithmId}
JSON (Algorithm Metadata) retrieve the metadata of the specified algorithm."},{"location":"advanced/endpoints_factories/#colormapfactory","title":"ColorMapFactory","text":"class: titiler.core.factory.ColorMapFactory
Endpoints factory for colorMaps metadata.
"},{"location":"advanced/endpoints_factories/#attributes_7","title":"Attributes","text":"ColorMaps
. Defaults to rio_tiler.colormap.cmap
.from fastapi import FastAPI\n\nfrom titiler.core.factory import ColorMapFactory\n\napp = FastAPI()\ncolormap = ColorMapFactory()\napp.include_router(colormap.router)\n
"},{"location":"advanced/endpoints_factories/#endpoints_6","title":"Endpoints","text":"Method URL Output Description GET
/colorMaps
JSON (colorMapList) retrieve the list of available colorMaps GET
/colorMaps/{colorMapId}
JSON (colorMap) retrieve the metadata or image of the specified colorMap."},{"location":"advanced/performance_tuning/","title":"Performance Tuning","text":""},{"location":"advanced/performance_tuning/#overview","title":"Overview","text":"Titiler makes use of several great underlying libraries, including GDAL and Python bindings to GDAL. An effective deployment of titiler generally requires tweaking GDAL configuration settings. This document provides an overview of relevant settings. Full documentation from GDAL is available here.
"},{"location":"advanced/performance_tuning/#gdal-configuration","title":"GDAL Configuration","text":""},{"location":"advanced/performance_tuning/#setting-a-config-variable","title":"Setting a config variable","text":"GDAL configuration is modified using environment variables. Thus in order to change a setting you'll need to set environment variables through your deployment mechanism. For example, in order to test locally you'd set an environment variable in bash:
export GDAL_HTTP_MULTIPLEX=YES\n
"},{"location":"advanced/performance_tuning/#available-configuration-settings","title":"Available configuration settings","text":""},{"location":"advanced/performance_tuning/#gdal_http_merge_consecutive_ranges","title":"GDAL_HTTP_MERGE_CONSECUTIVE_RANGES
","text":"When set to YES
, this tells GDAL to merge adjacent range requests. Instead of making two requests for byte ranges 1-5
and 6-10
, it would make a single request for 1-10
. This should always be set to YES
.
GDAL_DISABLE_READDIR_ON_OPEN
","text":"This is a very important setting to control the number of requests GDAL makes.
This setting has two options: FALSE
and EMPTY_DIR
. FALSE
(the default) causes GDAL to try to establish a list of all the available files in the directory. EMPTY_DIR
tells GDAL to imagine that the directory is empty except for the requested file.
When reading datasets with necessary external sidecar files, it's imperative to set FALSE
. For example, the landsat-pds
bucket on AWS S3 contains GeoTIFF images where overviews are in external .ovr
files. If set to EMPTY_DIR
, GDAL won't find the .ovr
files.
However, in all other cases, it's much better to set EMPTY_DIR
because this prevents GDAL from making a LIST
request.
This setting also has cost implications for reading data from requester-pays buckets. When set to FALSE
, GDAL makes a LIST
request every time it opens a file. Since LIST
requests are much more expensive than GET
requests, this can bring unexpected costs.
CPL_VSIL_CURL_ALLOWED_EXTENSIONS
","text":"A list of file extensions that GDAL is allowed to open. For example if set to .tif
, then GDAL would only open files with a .tif
extension. For example, it would fail on JPEG2000 files with a .jp2
extension, but also wouldn't open GeoTIFFs exposed through an API endpoint that don't have a .tif
suffix.
Note that you also need to include extensions of external overview files. For example, the landsat-pds
bucket on AWS S3 has external overviews in .ovr
files, so if you wished to read this data, you'd want
GDAL_INGESTED_BYTES_AT_OPEN
","text":"Gives the number of initial bytes GDAL should read when opening a file and inspecting its metadata.
Titiler works best with Cloud-Optimized GeoTIFFs (COGs) because they have a tiled internal structure that supports efficient random reads. These files have an initial metadata section that describes the location (byte range) within the file of each internal tile. The more internal tiles the COG has, the more data the header needs to contain.
GDAL needs to read the entire header before it can read any other portion of the file. By default GDAL reads the first 16KB of the file, then if that doesn't contain the entire metadata, it makes one more request for the rest of the metadata.
In environments where latency is relatively high (at least compared to bandwidth), such as AWS S3, it may be beneficial to increase this value depending on the data you expect to read.
There isn't currently a way to get the number of header bytes using GDAL, but alternative GeoTIFF readers such as aiocogeo
can. Using its cli you can find the image's header size:
export AWS_REQUEST_PAYER=\"requester\"\naiocogeo info s3://usgs-landsat/collection02/level-2/standard/oli-tirs/2020/072/076/LC08_L2SR_072076_20201203_20210313_02_T2/LC08_L2SR_072076_20201203_20210313_02_T2_SR_B1.TIF\n\n PROFILE\n ...\n Header size: 32770\n
It's wise to inspect the header sizes of your data sources, and set GDAL_INGESTED_BYTES_AT_OPEN
appropriately. Beware, however, that the given number of bytes will be read for every image, so you don't want to make the value too large.
GDAL_CACHEMAX
","text":"Default GDAL block cache. The value can be either in Mb, bytes or percent of the physical RAM
Recommended: 200 (200Mb)
"},{"location":"advanced/performance_tuning/#cpl_vsil_curl_cache_size","title":"CPL_VSIL_CURL_CACHE_SIZE
","text":"A global least-recently-used cache shared among all downloaded content and may be reused after a file handle has been closed and reopen
Recommended: 200000000 (200Mb)
"},{"location":"advanced/performance_tuning/#vsi_cache","title":"VSI_CACHE
","text":"Setting this to TRUE
enables GDAL to use an internal caching mechanism. It's
Recommended (Strongly): TRUE.
"},{"location":"advanced/performance_tuning/#vsi_cache_size","title":"VSI_CACHE_SIZE
","text":"The size of the above VSI cache in bytes per-file handle. If you open a VRT with 10 files and your VSI_CACHE_SIZE is 10 bytes, the total cache memory usage would be 100 bytes. The cache is RAM based and the content of the cache is discarded when the file handle is closed.
Recommended: 5000000 (5Mb per file handle)
"},{"location":"advanced/performance_tuning/#gdal_band_block_cache","title":"GDAL_BAND_BLOCK_CACHE
","text":"GDAL Block Cache type: ARRAY
or HASHSET
. See gdal.org/development/rfc/rfc26_blockcache.html
PROJ_NETWORK
","text":"Introduced with GDAL 3 and PROJ>7, the PROJ library can fetch more precise transformation grids hosted on the cloud.
Values: ON/OFF
Ref: proj.org/usage/network.html
"},{"location":"advanced/performance_tuning/#gdal_http_multiplex","title":"GDAL_HTTP_MULTIPLEX
","text":"When set to YES
, this attempts to download multiple range requests in parallel, reusing the same TCP connection. Note this is only possible when the server supports HTTP2, which many servers don't yet support. There's no downside to setting YES
here.
GDAL_DATA
","text":"The GDAL_DATA
variable tells rasterio/GDAL where the GDAL C libraries have been installed. When using rasterio wheels, GDAL_DATA must be unset.
PROJ_LIB
","text":"The PROJ_LIB
variable tells rasterio/GDAL where the PROJ C libraries have been installed. When using rasterio wheels, PROJ_LIB must be unset.
AWS_REQUEST_PAYER
","text":""},{"location":"advanced/performance_tuning/#recommended-configuration-for-dynamic-tiling","title":"Recommended Configuration for dynamic tiling","text":"CPL_VSIL_CURL_ALLOWED_EXTENSIONS=\".tif,.TIF,.tiff\"
In addition to GDAL_DISABLE_READDIR_ON_OPEN
, we set the allowed extensions to .tif
to only enable tif files. (OPTIONAL)
GDAL_CACHEMAX=\"200\"
200 Mb Cache.
CPL_VSIL_CURL_CACHE_SIZE=\"200000000
200 Mb VSI Cache.
GDAL_BAND_BLOCK_CACHE=\"HASHSET\"
GDAL_DISABLE_READDIR_ON_OPEN=\"EMPTY_DIR\"
Maybe the most important variable. Setting it to EMPTY_DIR
reduce the number of GET/LIST requests.
GDAL_HTTP_MERGE_CONSECUTIVE_RANGES=\"YES\"
Tells GDAL to merge consecutive range GET requests.
GDAL_HTTP_MULTIPLEX=\"YES\"
GDAL_HTTP_VERSION=\"2\"
Both Multiplex and HTTP_VERSION will only have impact if the files are stored in an environment which support HTTP 2 (e.g cloudfront).
VSI_CACHE=\"TRUE\"
VSI_CACHE_SIZE=\"5000000\"
5Mb cache per file handle.
"},{"location":"advanced/rendering/","title":"Rendering Options","text":"When using Titiler to visualize imagery, there are some helper options that change how the data appears on the screen. You can:
Color maps are arrays of colors, used to map pixel values to specific colors. For example, it is possible to map a single band DEM, where pixel values denote height, to a color map which shows higher values as white:
Titiler supports both default colormaps (each with a name) and custom color maps.
"},{"location":"advanced/rendering/#default-colormaps","title":"Default Colormaps","text":"Default colormaps pre-made, each with a given name. These maps come from the rio-tiler
library, which has taken colormaps packaged with Matplotlib and has added others that are commonly used with raster data.
A list of available color maps can be found in Titiler's Swagger docs, or in the rio-tiler documentation.
To use a default colormap, simply use the parameter colormap_name
:
import httpx\n\nresp = httpx.get(\n \"https://titiler.xyz/cog/preview\",\n params={\n \"url\": \"<YOUR DATASET URL HERE>\",\n \"colormap_name\": \"<YOUR COLORMAP NAME HERE>\" # e.g. autumn_r\n }\n)\n
You can take any of the colormaps listed on rio-tiler
, and add _r
to reverse it.
If you'd like to specify your own colormap, you can specify your own using an encoded JSON:
import httpx\n\nresponse = httpx.get(\n \"https://titiler.xyz/cog/preview\",\n params={\n \"url\": \"<YOUR DATASET URL HERE>\",\n \"bidx\": \"1\",\n \"colormap\": json.dumps({\n \"0\": \"#e5f5f9\",\n \"10\": \"#99d8c9\",\n \"255\": \"#2ca25f\",\n })\n }\n)\n
Titiler supports colormaps that are both discrete (where pixels will be one of the colors that you specify) and linear (where pixel colors will blend between the given colors).
For more information, please check out rio-tiler's docs.
It is also possible to add a colormap dependency to automatically apply a default colormap.
"},{"location":"advanced/rendering/#color-formula","title":"Color Formula","text":"Color formulae are simple commands that apply color corrections to images. This is useful for reducing artefacts like atmospheric haze, dark shadows, or muted colors.
Titiler supports color formulae as defined in Mapbox's rio-color
plugin. These include the operations (taken from the rio-color
docs):
Gamma adjustment adjusts RGB values according to a power law, effectively brightening or darkening the midtones. It can be very effective in satellite imagery for reducing atmospheric haze in the blue and green bands.
Sigmoidal contrast adjustment can alter the contrast and brightness of an image in a way that matches human's non-linear visual perception. It works well to increase contrast without blowing out the very dark shadows or already-bright parts of the image.
Saturation can be thought of as the \"colorfulness\" of a pixel. Highly saturated colors are intense and almost cartoon-like, low saturation is more muted, closer to black and white. You can adjust saturation independently of brightness and hue but the data must be transformed into a different color space.
In Titiler, color_formulae are applied through the color_formula
parameter as a string. An example of this option in action:
import httpx\n\nresponse = httpx.get(\n \"https://titiler.xyz/cog/preview\",\n params={\n \"url\": \"<YOUR DATASET URL HERE>\",\n \"color_formula\": \"gamma rg 1.3, sigmoidal rgb 22 0.1, saturation 1.5\"\n }\n)\n
"},{"location":"advanced/rendering/#rescaling","title":"Rescaling","text":"Rescaling is the act of adjusting the minimum and maximum values when rendering an image. In an image with a single band, the rescaled minimum value will be set to black, and the rescaled maximum value will be set to white. This is useful if you want to accentuate features that only appear at a certain pixel value (e.g. you have a DEM, but you want to highlight how the terrain changes between sea level and 100m).
All titiler endpoinds returning image support rescale
parameter. The parameter should be in form of \"rescale={min},{max}\"
.
import httpx\n\nresponse = httpx.get(\n \"https;//titiler.xyz/cog/preview\",\n params={\n \"url\": \"<YOUR DATASET URL HERE>\",\n \"rescale\": \"0,100\",\n },\n)\n
Titiler supports rescaling on a per-band basis, using multiple rescale
parameters.
import httpx\n\nresponse = httpx.get(\n \"https;//titiler.xyz/cog/preview\",\n params=(\n (\"url\", \"<YOUR DATASET URL HERE>\"),\n (\"rescale\", \"0,100\"),\n (\"rescale\", \"0,1000\"),\n (\"rescale\", \"0,10000\"),\n ),\n)\n
By default, Titiler will rescale the bands using the min/max values of the input datatype. For example, PNG images 8- or 16-bit unsigned pixels, giving a possible range of 0 to 255 or 0 to 65,536, so Titiler will use these ranges to rescale to the output format.
For certain datasets (e.g. DEMs) this default behaviour can make the image seem washed out (or even entirely one color), so if you see this happen look into rescaling your images to something that makes sense for your data.
It is also possible to add a rescaling dependency to automatically apply a default rescale.
"},{"location":"api/titiler/core/dependencies/","title":"dependencies","text":""},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies","title":"titiler.core.dependencies","text":"Common dependency.
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.AssetsBidxExprParams","title":"AssetsBidxExprParamsdataclass
","text":" Bases: AssetsParams
, BidxParams
Assets, Expression and Asset's band Indexes parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass AssetsBidxExprParams(AssetsParams, BidxParams):\n \"\"\"Assets, Expression and Asset's band Indexes parameters.\"\"\"\n\n expression: Annotated[\n Optional[str],\n Query(\n title=\"Band Math expression\",\n description=\"Band math expression between assets\",\n openapi_examples={\n \"simple\": {\n \"description\": \"Return results of expression between assets.\",\n \"value\": \"asset1_b1 + asset2_b1 / asset3_b1\",\n },\n },\n ),\n ] = None\n\n asset_indexes: Annotated[\n Optional[Sequence[str]],\n Query(\n title=\"Per asset band indexes\",\n description=\"Per asset band indexes (coma separated indexes)\",\n alias=\"asset_bidx\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return indexes 1,2,3 of asset `data`.\",\n \"value\": [\"data|1,2,3\"],\n },\n \"multi-assets\": {\n \"description\": \"Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`\",\n \"value\": [\"data|1,2,3\", \"cog|1\"],\n },\n },\n ),\n ] = None\n\n asset_as_band: Annotated[\n Optional[bool],\n Query(\n title=\"Consider asset as a 1 band dataset\",\n description=\"Asset as Band\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if not self.assets and not self.expression:\n raise MissingAssets(\n \"assets must be defined either via expression or assets options.\"\n )\n\n if self.asset_indexes:\n self.asset_indexes = parse_asset_indexes(self.asset_indexes)\n\n if self.asset_indexes and self.indexes:\n warnings.warn(\n \"Both `asset_bidx` and `bidx` passed; only `asset_bidx` will be considered.\",\n UserWarning,\n )\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.AssetsBidxExprParamsOptional","title":"AssetsBidxExprParamsOptional dataclass
","text":" Bases: AssetsBidxExprParams
Assets, Expression and Asset's band Indexes parameters but with no requirement.
Source code intitiler/core/dependencies.py
@dataclass\nclass AssetsBidxExprParamsOptional(AssetsBidxExprParams):\n \"\"\"Assets, Expression and Asset's band Indexes parameters but with no requirement.\"\"\"\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.asset_indexes:\n self.asset_indexes = parse_asset_indexes(self.asset_indexes)\n\n if self.asset_indexes and self.indexes:\n warnings.warn(\n \"Both `asset_bidx` and `bidx` passed; only `asset_bidx` will be considered.\",\n UserWarning,\n )\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.AssetsBidxParams","title":"AssetsBidxParams dataclass
","text":" Bases: AssetsParams
, BidxParams
Assets, Asset's band Indexes and Asset's band Expression parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass AssetsBidxParams(AssetsParams, BidxParams):\n \"\"\"Assets, Asset's band Indexes and Asset's band Expression parameters.\"\"\"\n\n asset_indexes: Annotated[\n Optional[Sequence[str]],\n Query(\n title=\"Per asset band indexes\",\n description=\"Per asset band indexes\",\n alias=\"asset_bidx\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return indexes 1,2,3 of asset `data`.\",\n \"value\": [\"data|1;2;3\"],\n },\n \"multi-assets\": {\n \"description\": \"Return indexes 1,2,3 of asset `data` and indexes 1 of asset `cog`\",\n \"value\": [\"data|1;2;3\", \"cog|1\"],\n },\n },\n ),\n ] = None\n\n asset_expression: Annotated[\n Optional[Sequence[str]],\n Query(\n title=\"Per asset band expression\",\n description=\"Per asset band expression\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return results for expression `b1*b2+b3` of asset `data`.\",\n \"value\": [\"data|b1*b2+b3\"],\n },\n \"multi-assets\": {\n \"description\": \"Return results for expressions `b1*b2+b3` for asset `data` and `b1+b3` for asset `cog`.\",\n \"value\": [\"data|b1*b2+b3\", \"cog|b1+b3\"],\n },\n },\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.asset_indexes:\n self.asset_indexes = parse_asset_indexes(self.asset_indexes)\n\n if self.asset_expression:\n self.asset_expression = parse_asset_expression(self.asset_expression)\n\n if self.asset_indexes and self.indexes:\n warnings.warn(\n \"Both `asset_bidx` and `bidx` passed; only `asset_bidx` will be considered.\",\n UserWarning,\n )\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.AssetsParams","title":"AssetsParams dataclass
","text":" Bases: DefaultDependency
Assets parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass AssetsParams(DefaultDependency):\n \"\"\"Assets parameters.\"\"\"\n\n assets: Annotated[\n Optional[List[str]],\n Query(\n title=\"Asset names\",\n description=\"Asset's names.\",\n openapi_examples={\n \"one-asset\": {\n \"description\": \"Return results for asset `data`.\",\n \"value\": [\"data\"],\n },\n \"multi-assets\": {\n \"description\": \"Return results for assets `data` and `cog`.\",\n \"value\": [\"data\", \"cog\"],\n },\n },\n ),\n ] = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.BandsExprParams","title":"BandsExprParams dataclass
","text":" Bases: ExpressionParams
, BandsParams
Band names and Expression parameters (Band or Expression required).
Source code intitiler/core/dependencies.py
@dataclass\nclass BandsExprParams(ExpressionParams, BandsParams):\n \"\"\"Band names and Expression parameters (Band or Expression required).\"\"\"\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if not self.bands and not self.expression:\n raise MissingBands(\n \"bands must be defined either via expression or bands options.\"\n )\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.BandsExprParamsOptional","title":"BandsExprParamsOptional dataclass
","text":" Bases: ExpressionParams
, BandsParams
Optional Band names and Expression parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass BandsExprParamsOptional(ExpressionParams, BandsParams):\n \"\"\"Optional Band names and Expression parameters.\"\"\"\n\n pass\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.BandsParams","title":"BandsParams dataclass
","text":" Bases: DefaultDependency
Band names parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass BandsParams(DefaultDependency):\n \"\"\"Band names parameters.\"\"\"\n\n bands: Annotated[\n Optional[List[str]],\n Query(\n title=\"Band names\",\n description=\"Band's names.\",\n openapi_examples={\n \"one-band\": {\n \"description\": \"Return results for band `B01`.\",\n \"value\": [\"B01\"],\n },\n \"multi-bands\": {\n \"description\": \"Return results for bands `B01` and `B02`.\",\n \"value\": [\"B01\", \"B02\"],\n },\n },\n ),\n ] = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.BidxExprParams","title":"BidxExprParams dataclass
","text":" Bases: ExpressionParams
, BidxParams
Band Indexes and Expression parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass BidxExprParams(ExpressionParams, BidxParams):\n \"\"\"Band Indexes and Expression parameters.\"\"\"\n\n pass\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.BidxParams","title":"BidxParams dataclass
","text":" Bases: DefaultDependency
Band Indexes parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass BidxParams(DefaultDependency):\n \"\"\"Band Indexes parameters.\"\"\"\n\n indexes: Annotated[\n Optional[List[int]],\n Query(\n title=\"Band indexes\",\n alias=\"bidx\",\n description=\"Dataset band indexes\",\n openapi_examples={\n \"one-band\": {\"value\": [1]},\n \"multi-bands\": {\"value\": [1, 2, 3]},\n },\n ),\n ] = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.DatasetParams","title":"DatasetParams dataclass
","text":" Bases: DefaultDependency
Low level WarpedVRT Optional parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass DatasetParams(DefaultDependency):\n \"\"\"Low level WarpedVRT Optional parameters.\"\"\"\n\n nodata: Annotated[\n Optional[Union[str, int, float]],\n Query(\n title=\"Nodata value\",\n description=\"Overwrite internal Nodata value\",\n ),\n ] = None\n unscale: Annotated[\n Optional[bool],\n Query(\n title=\"Apply internal Scale/Offset\",\n description=\"Apply internal Scale/Offset. Defaults to `False`.\",\n ),\n ] = None\n resampling_method: Annotated[\n Optional[RIOResampling],\n Query(\n alias=\"resampling\",\n description=\"RasterIO resampling algorithm. Defaults to `nearest`.\",\n ),\n ] = None\n reproject_method: Annotated[\n Optional[WarpResampling],\n Query(\n alias=\"reproject\",\n description=\"WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest`.\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.nodata is not None:\n self.nodata = numpy.nan if self.nodata == \"nan\" else float(self.nodata)\n\n if self.unscale is not None:\n self.unscale = bool(self.unscale)\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.DefaultDependency","title":"DefaultDependency dataclass
","text":"Dataclass with dict unpacking
Source code intitiler/core/dependencies.py
@dataclass\nclass DefaultDependency:\n \"\"\"Dataclass with dict unpacking\"\"\"\n\n def keys(self):\n \"\"\"Return Keys.\"\"\"\n warnings.warn(\n \"Dict unpacking will be removed for `DefaultDependency` in titiler 0.19.0\",\n DeprecationWarning,\n )\n return self.__dict__.keys()\n\n def __getitem__(self, key):\n \"\"\"Return value.\"\"\"\n return self.__dict__[key]\n\n def as_dict(self, exclude_none: bool = True) -> Dict:\n \"\"\"Transform dataclass to dict.\"\"\"\n if exclude_none:\n return {k: v for k, v in self.__dict__.items() if v is not None}\n\n return dict(self.__dict__.items())\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.DefaultDependency.__getitem__","title":"__getitem__","text":"__getitem__(key)\n
Return value.
Source code intitiler/core/dependencies.py
def __getitem__(self, key):\n \"\"\"Return value.\"\"\"\n return self.__dict__[key]\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.DefaultDependency.as_dict","title":"as_dict","text":"as_dict(exclude_none: bool = True) -> Dict\n
Transform dataclass to dict.
Source code intitiler/core/dependencies.py
def as_dict(self, exclude_none: bool = True) -> Dict:\n \"\"\"Transform dataclass to dict.\"\"\"\n if exclude_none:\n return {k: v for k, v in self.__dict__.items() if v is not None}\n\n return dict(self.__dict__.items())\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.DefaultDependency.keys","title":"keys","text":"keys()\n
Return Keys.
Source code intitiler/core/dependencies.py
def keys(self):\n \"\"\"Return Keys.\"\"\"\n warnings.warn(\n \"Dict unpacking will be removed for `DefaultDependency` in titiler 0.19.0\",\n DeprecationWarning,\n )\n return self.__dict__.keys()\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.ExpressionParams","title":"ExpressionParams dataclass
","text":" Bases: DefaultDependency
Expression parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass ExpressionParams(DefaultDependency):\n \"\"\"Expression parameters.\"\"\"\n\n expression: Annotated[\n Optional[str],\n Query(\n title=\"Band Math expression\",\n description=\"rio-tiler's band math expression\",\n openapi_examples={\n \"simple\": {\"description\": \"Simple band math.\", \"value\": \"b1/b2\"},\n \"multi-bands\": {\n \"description\": \"Semicolon (;) delimited expressions (band1: b1/b2, band2: b2+b3).\",\n \"value\": \"b1/b2;b2+b3\",\n },\n },\n ),\n ] = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.HistogramParams","title":"HistogramParams dataclass
","text":" Bases: DefaultDependency
Numpy Histogram options.
Source code intitiler/core/dependencies.py
@dataclass\nclass HistogramParams(DefaultDependency):\n \"\"\"Numpy Histogram options.\"\"\"\n\n bins: Annotated[\n Optional[str],\n Query(\n alias=\"histogram_bins\",\n title=\"Histogram bins.\",\n description=\"\"\"\nDefines the number of equal-width bins in the given range (10, by default).\n\nIf bins is a sequence (comma `,` delimited values), it defines a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform bin widths.\n\nlink: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html\n \"\"\",\n openapi_examples={\n \"simple\": {\n \"description\": \"Defines the number of equal-width bins\",\n \"value\": 8,\n },\n \"array\": {\n \"description\": \"Defines custom bin edges (comma `,` delimited values)\",\n \"value\": \"0,100,200,300\",\n },\n },\n ),\n ] = None\n\n range: Annotated[\n Optional[str],\n Query(\n alias=\"histogram_range\",\n title=\"Histogram range\",\n description=\"\"\"\nComma `,` delimited range of the bins.\n\nThe lower and upper range of the bins. If not provided, range is simply (a.min(), a.max()).\n\nValues outside the range are ignored. The first element of the range must be less than or equal to the second.\nrange affects the automatic bin computation as well.\n\nlink: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html\n \"\"\",\n examples=\"0,1000\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.bins:\n bins = self.bins.split(\",\")\n if len(bins) == 1:\n self.bins = int(bins[0]) # type: ignore\n else:\n self.bins = list(map(float, bins)) # type: ignore\n else:\n self.bins = 10\n\n if self.range:\n parsed = list(map(float, self.range.split(\",\")))\n assert (\n len(parsed) == 2\n ), f\"Invalid histogram_range values: {self.range}, should be of form 'min,max'\"\n\n self.range = parsed # type: ignore\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.ImageRenderingParams","title":"ImageRenderingParams dataclass
","text":" Bases: DefaultDependency
Image Rendering options.
Source code intitiler/core/dependencies.py
@dataclass\nclass ImageRenderingParams(DefaultDependency):\n \"\"\"Image Rendering options.\"\"\"\n\n add_mask: Annotated[\n Optional[bool],\n Query(\n alias=\"return_mask\",\n description=\"Add mask to the output data. Defaults to `True`\",\n ),\n ] = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.PartFeatureParams","title":"PartFeatureParams dataclass
","text":" Bases: DefaultDependency
Common parameters for bbox and feature.
Source code intitiler/core/dependencies.py
@dataclass\nclass PartFeatureParams(DefaultDependency):\n \"\"\"Common parameters for bbox and feature.\"\"\"\n\n max_size: Annotated[Optional[int], \"Maximum image size to read onto.\"] = None\n height: Annotated[Optional[int], \"Force output image height.\"] = None\n width: Annotated[Optional[int], \"Force output image width.\"] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width and self.height:\n self.max_size = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.PreviewParams","title":"PreviewParams dataclass
","text":" Bases: DefaultDependency
Common Preview parameters.
Source code intitiler/core/dependencies.py
@dataclass\nclass PreviewParams(DefaultDependency):\n \"\"\"Common Preview parameters.\"\"\"\n\n max_size: Annotated[int, \"Maximum image size to read onto.\"] = 1024\n height: Annotated[Optional[int], \"Force output image height.\"] = None\n width: Annotated[Optional[int], \"Force output image width.\"] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width and self.height:\n self.max_size = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.StatisticsParams","title":"StatisticsParams dataclass
","text":" Bases: DefaultDependency
Statistics options.
Source code intitiler/core/dependencies.py
@dataclass\nclass StatisticsParams(DefaultDependency):\n \"\"\"Statistics options.\"\"\"\n\n categorical: Annotated[\n Optional[bool],\n Query(\n description=\"Return statistics for categorical dataset. Defaults to `False`\"\n ),\n ] = None\n categories: Annotated[\n Optional[List[Union[float, int]]],\n Query(\n alias=\"c\",\n title=\"Pixels values for categories.\",\n description=\"List of values for which to report counts.\",\n examples=[1, 2, 3],\n ),\n ] = None\n percentiles: Annotated[\n Optional[List[int]],\n Query(\n alias=\"p\",\n title=\"Percentile values\",\n description=\"List of percentile values (default to [2, 98]).\",\n examples=[2, 5, 95, 98],\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Set percentiles default.\"\"\"\n if not self.percentiles:\n self.percentiles = [2, 98]\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.TileParams","title":"TileParams dataclass
","text":" Bases: DefaultDependency
Tile options.
Source code intitiler/core/dependencies.py
@dataclass\nclass TileParams(DefaultDependency):\n \"\"\"Tile options.\"\"\"\n\n buffer: Annotated[\n Optional[float],\n Query(\n gt=0,\n title=\"Tile buffer.\",\n description=\"Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).\",\n ),\n ] = None\n\n padding: Annotated[\n Optional[int],\n Query(\n gt=0,\n title=\"Tile padding.\",\n description=\"Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`.\",\n ),\n ] = None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.BufferParams","title":"BufferParams","text":"BufferParams(\n buffer: Annotated[\n Optional[float],\n Query(\n gt=0,\n title=\"Tile buffer.\",\n description=\"Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).\",\n ),\n ] = None\n) -> Optional[float]\n
Tile buffer Parameter.
Source code intitiler/core/dependencies.py
def BufferParams(\n buffer: Annotated[\n Optional[float],\n Query(\n gt=0,\n title=\"Tile buffer.\",\n description=\"Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).\",\n ),\n ] = None,\n) -> Optional[float]:\n \"\"\"Tile buffer Parameter.\"\"\"\n return buffer\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.ColorFormulaParams","title":"ColorFormulaParams","text":"ColorFormulaParams(\n color_formula: Annotated[\n Optional[str],\n Query(\n title=\"Color Formula\",\n description=\"rio-color formula (info: https://github.com/mapbox/rio-color)\",\n ),\n ] = None\n) -> Optional[str]\n
ColorFormula Parameter.
Source code intitiler/core/dependencies.py
def ColorFormulaParams(\n color_formula: Annotated[\n Optional[str],\n Query(\n title=\"Color Formula\",\n description=\"rio-color formula (info: https://github.com/mapbox/rio-color)\",\n ),\n ] = None,\n) -> Optional[str]:\n \"\"\"ColorFormula Parameter.\"\"\"\n return color_formula\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.CoordCRSParams","title":"CoordCRSParams","text":"CoordCRSParams(\n crs: Annotated[\n Optional[str],\n Query(\n alias=coord_crs,\n description=\"Coordinate Reference System of the input coords. Default to `epsg:4326`.\",\n ),\n ] = None\n) -> Optional[CRS]\n
Coordinate Reference System Coordinates Param.
Source code intitiler/core/dependencies.py
def CoordCRSParams(\n crs: Annotated[\n Optional[str],\n Query(\n alias=\"coord_crs\",\n description=\"Coordinate Reference System of the input coords. Default to `epsg:4326`.\",\n ),\n ] = None,\n) -> Optional[CRS]:\n \"\"\"Coordinate Reference System Coordinates Param.\"\"\"\n if crs:\n return CRS.from_user_input(crs)\n\n return None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.DatasetPathParams","title":"DatasetPathParams","text":"DatasetPathParams(url: Annotated[str, Query(description='Dataset URL')]) -> str\n
Create dataset path from args
Source code intitiler/core/dependencies.py
def DatasetPathParams(url: Annotated[str, Query(description=\"Dataset URL\")]) -> str:\n \"\"\"Create dataset path from args\"\"\"\n return url\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.DstCRSParams","title":"DstCRSParams","text":"DstCRSParams(\n crs: Annotated[\n Optional[str], Query(alias=dst_crs, description=\"Output Coordinate Reference System.\")\n ] = None\n) -> Optional[CRS]\n
Coordinate Reference System Coordinates Param.
Source code intitiler/core/dependencies.py
def DstCRSParams(\n crs: Annotated[\n Optional[str],\n Query(\n alias=\"dst_crs\",\n description=\"Output Coordinate Reference System.\",\n ),\n ] = None,\n) -> Optional[CRS]:\n \"\"\"Coordinate Reference System Coordinates Param.\"\"\"\n if crs:\n return CRS.from_user_input(crs)\n\n return None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.RescalingParams","title":"RescalingParams","text":"RescalingParams(\n rescale: Annotated[\n Optional[List[str]],\n Query(\n title=\"Min/Max data Rescaling\",\n description=\"comma (',') delimited Min,Max range. Can set multiple time for multiple bands.\",\n examples=[(0, 2000), (0, 1000), (0, 10000)],\n ),\n ] = None\n) -> Optional[RescaleType]\n
Min/Max data Rescaling
Source code intitiler/core/dependencies.py
def RescalingParams(\n rescale: Annotated[\n Optional[List[str]],\n Query(\n title=\"Min/Max data Rescaling\",\n description=\"comma (',') delimited Min,Max range. Can set multiple time for multiple bands.\",\n examples=[\"0,2000\", \"0,1000\", \"0,10000\"], # band 1 # band 2 # band 3\n ),\n ] = None,\n) -> Optional[RescaleType]:\n \"\"\"Min/Max data Rescaling\"\"\"\n if rescale:\n rescale_array = []\n for r in rescale:\n parsed = tuple(\n map(\n float,\n r.replace(\" \", \"\").replace(\"[\", \"\").replace(\"]\", \"\").split(\",\"),\n )\n )\n assert (\n len(parsed) == 2\n ), f\"Invalid rescale values: {rescale}, should be of form ['min,max', 'min,max'] or [[min,max], [min, max]]\"\n rescale_array.append(parsed)\n\n return rescale_array\n\n return None\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.create_colormap_dependency","title":"create_colormap_dependency","text":"create_colormap_dependency(cmap: ColorMaps) -> Callable\n
Create Colormap Dependency.
Source code intitiler/core/dependencies.py
def create_colormap_dependency(cmap: ColorMaps) -> Callable:\n \"\"\"Create Colormap Dependency.\"\"\"\n\n def deps(\n colormap_name: Annotated[ # type: ignore\n Literal[tuple(cmap.list())],\n Query(description=\"Colormap name\"),\n ] = None,\n colormap: Annotated[\n Optional[str], Query(description=\"JSON encoded custom Colormap\")\n ] = None,\n ):\n if colormap_name:\n return cmap.get(colormap_name)\n\n if colormap:\n try:\n c = json.loads(\n colormap,\n object_hook=lambda x: {\n int(k): parse_color(v) for k, v in x.items()\n },\n )\n\n # Make sure to match colormap type\n if isinstance(c, Sequence):\n c = [(tuple(inter), parse_color(v)) for (inter, v) in c]\n\n return c\n except json.JSONDecodeError as e:\n raise HTTPException(\n status_code=400, detail=\"Could not parse the colormap value.\"\n ) from e\n\n return None\n\n return deps\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.parse_asset_expression","title":"parse_asset_expression","text":"parse_asset_expression(asset_expression: Union[Sequence[str], Dict[str, str]]) -> Dict[str, str]\n
parse asset expression parameters.
Source code intitiler/core/dependencies.py
def parse_asset_expression(\n asset_expression: Union[Sequence[str], Dict[str, str]],\n) -> Dict[str, str]:\n \"\"\"parse asset expression parameters.\"\"\"\n return {idx.split(\"|\")[0]: idx.split(\"|\")[1] for idx in asset_expression}\n
"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.parse_asset_indexes","title":"parse_asset_indexes","text":"parse_asset_indexes(\n asset_indexes: Union[Sequence[str], Dict[str, Sequence[int]]]\n) -> Dict[str, Sequence[int]]\n
parse asset indexes parameters.
Source code intitiler/core/dependencies.py
def parse_asset_indexes(\n asset_indexes: Union[Sequence[str], Dict[str, Sequence[int]]],\n) -> Dict[str, Sequence[int]]:\n \"\"\"parse asset indexes parameters.\"\"\"\n return {\n idx.split(\"|\")[0]: list(map(int, idx.split(\"|\")[1].split(\",\")))\n for idx in asset_indexes\n }\n
"},{"location":"api/titiler/core/errors/","title":"errors","text":""},{"location":"api/titiler/core/errors/#titiler.core.errors","title":"titiler.core.errors","text":"Titiler error classes.
"},{"location":"api/titiler/core/errors/#titiler.core.errors.BadRequestError","title":"BadRequestError","text":" Bases: TilerError
Bad request error.
"},{"location":"api/titiler/core/errors/#titiler.core.errors.TileNotFoundError","title":"TileNotFoundError","text":" Bases: TilerError
Tile not found error.
"},{"location":"api/titiler/core/errors/#titiler.core.errors.TilerError","title":"TilerError","text":" Bases: Exception
Base exception class.
"},{"location":"api/titiler/core/errors/#titiler.core.errors.add_exception_handlers","title":"add_exception_handlers","text":"add_exception_handlers(app: FastAPI, status_codes: Dict[Type[Exception], int]) -> None\n
Add exception handlers to the FastAPI app.
"},{"location":"api/titiler/core/errors/#titiler.core.errors.exception_handler_factory","title":"exception_handler_factory","text":"exception_handler_factory(status_code: int) -> Callable\n
Create a FastAPI exception handler from a status code.
"},{"location":"api/titiler/core/factory/","title":"factory","text":""},{"location":"api/titiler/core/factory/#titiler.core.factory","title":"titiler.core.factory","text":"TiTiler Router factories.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.AlgorithmFactory","title":"AlgorithmFactorydataclass
","text":"Algorithm endpoints Factory.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseTilerFactory","title":"BaseTilerFactorydataclass
","text":"BaseTiler Factory.
Abstract Base Class which defines most inputs used by dynamic tiler.
Attributes:
reader
(BaseReader
) \u2013 A rio-tiler reader (e.g Reader).
router
(APIRouter
) \u2013 Application router to register endpoints to.
path_dependency
(Callable
) \u2013 Endpoint dependency defining path
to pass to the reader init.
dataset_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining dataset overwriting options (e.g nodata).
layer_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining dataset indexes/bands/assets options.
render_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining image rendering options (e.g add_mask).
colormap_dependency
(Callable
) \u2013 Endpoint dependency defining ColorMap options (e.g colormap_name).
process_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining image post-processing options (e.g rescaling, color-formula).
tms_dependency
(Callable
) \u2013 Endpoint dependency defining TileMatrixSet to use.
reader_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining BaseReader options.
environment_dependency
(Callable
) \u2013 Endpoint dependency to define GDAL environment at runtime.
router_prefix
(str
) \u2013 prefix where the router will be mounted in the application.
optional_headers(sequence
(of titiler.core.resources.enums.OptionalHeader
) \u2013 additional headers to return with the response.
add_route_dependencies(*, scopes: List[EndpointScope], dependencies=List[DependsFunc])\n
Add dependencies to routes.
Allows a developer to add dependencies to a route after the route has been defined.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseTilerFactory.register_routes","title":"register_routesabstractmethod
","text":"register_routes()\n
Register Tiler Routes.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseTilerFactory.url_for","title":"url_for","text":"url_for(request: Request, name: str, **path_params: Any) -> str\n
Return full url (with prefix) for a specific endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.ColorMapFactory","title":"ColorMapFactorydataclass
","text":"Colormap endpoints Factory.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.ColorMapFactory.url_for","title":"url_for","text":"url_for(request: Request, name: str, **path_params: Any) -> str\n
Return full url (with prefix) for a specific endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.FactoryExtension","title":"FactoryExtensiondataclass
","text":"Factory Extension.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.FactoryExtension.register","title":"registerabstractmethod
","text":"register(factory: BaseTilerFactory)\n
Register extension to the factory.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBandTilerFactory","title":"MultiBandTilerFactorydataclass
","text":" Bases: TilerFactory
Custom Tiler Factory for MultiBandReader classes.
NoteTo be able to use the rio_tiler.io.MultiBandReader we need to be able to pass a bands
argument to most of its methods. By using the BandsExprParams
for the layer_dependency
, the .tile(), .point(), .preview() and the .part() methods will receive bands or expression arguments.
The rio_tiler.io.MultiBandReader .info()
and .metadata()
have bands
as a requirement arguments (github.com/cogeotiff/rio-tiler/blob/main/rio_tiler/io/base.py#L775). This means we have to update the /info and /metadata endpoints in order to add the bands
dependency.
For implementation example see developmentseed/titiler-pds
"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBandTilerFactory.info","title":"info","text":"info()\n
Register /info endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBandTilerFactory.statistics","title":"statistics","text":"statistics()\n
add statistics endpoints.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBaseTilerFactory","title":"MultiBaseTilerFactorydataclass
","text":" Bases: TilerFactory
Custom Tiler Factory for MultiBaseReader classes.
NoteTo be able to use the rio_tiler.io.MultiBaseReader we need to be able to pass a assets
argument to most of its methods. By using the AssetsBidxExprParams
for the layer_dependency
, the .tile(), .point(), .preview() and the .part() methods will receive assets, expression or indexes arguments.
The rio_tiler.io.MultiBaseReader .info()
and .metadata()
have assets
as a requirement arguments (github.com/cogeotiff/rio-tiler/blob/main/rio_tiler/io/base.py#L365). This means we have to update the /info and /metadata endpoints in order to add the assets
dependency.
info()\n
Register /info endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBaseTilerFactory.statistics","title":"statistics","text":"statistics()\n
Register /statistics endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TMSFactory","title":"TMSFactorydataclass
","text":"TileMatrixSet endpoints Factory.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TMSFactory.register_routes","title":"register_routes","text":"register_routes()\n
Register TMS endpoint routes.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TMSFactory.url_for","title":"url_for","text":"url_for(request: Request, name: str, **path_params: Any) -> str\n
Return full url (with prefix) for a specific endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory","title":"TilerFactorydataclass
","text":" Bases: BaseTilerFactory
Tiler Factory.
Attributes:
reader
(BaseReader
) \u2013 A rio-tiler reader. Defaults to rio_tiler.io.Reader
.
stats_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining options for rio-tiler's statistics method.
histogram_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining options for numpy's histogram method.
img_preview_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining options for rio-tiler's preview method.
img_part_dependency
(DefaultDependency
) \u2013 Endpoint dependency defining options for rio-tiler's part/feature methods.
add_preview
(bool
) \u2013 add /preview
endpoints. Defaults to True.
add_part
(bool
) \u2013 add /bbox
and /feature
endpoints. Defaults to True.
add_viewer
(bool
) \u2013 add /map
endpoints. Defaults to True.
bounds()\n
Register /bounds endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.info","title":"info","text":"info()\n
Register /info endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.map_viewer","title":"map_viewer","text":"map_viewer()\n
Register /map endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.part","title":"part","text":"part()\n
Register /bbox and /feature
endpoints.
point()\n
Register /point endpoints.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.preview","title":"preview","text":"preview()\n
Register /preview endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.register_routes","title":"register_routes","text":"register_routes()\n
This Method register routes to the router.
Because we wrap the endpoints in a class we cannot define the routes as methods (because of the self argument). The HACK is to define routes inside the class method and register them after the class initialization.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.statistics","title":"statistics","text":"statistics()\n
add statistics endpoints.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.tile","title":"tile","text":"tile()\n
Register /tiles endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.tilejson","title":"tilejson","text":"tilejson()\n
Register /tilejson.json endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.wmts","title":"wmts","text":"wmts()\n
Register /wmts endpoint.
"},{"location":"api/titiler/core/middleware/","title":"middleware","text":""},{"location":"api/titiler/core/middleware/#titiler.core.middleware","title":"titiler.core.middleware","text":"Titiler middlewares.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.CacheControlMiddleware","title":"CacheControlMiddleware","text":"MiddleWare to add CacheControl in response headers.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.CacheControlMiddleware.__call__","title":"__call__async
","text":"__call__(scope: Scope, receive: Receive, send: Send)\n
Handle call.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.CacheControlMiddleware.__init__","title":"__init__","text":"__init__(\n app: ASGIApp,\n cachecontrol: Optional[str] = None,\n cachecontrol_max_http_code: Optional[int] = 500,\n exclude_path: Optional[Set[str]] = None,\n) -> None\n
Init Middleware.
Parameters:
app
(ASGIApp
) \u2013 starlette/FastAPI application.
cachecontrol
(str
, default: None
) \u2013 Cache-Control string to add to the response.
exclude_path
(set
, default: None
) \u2013 Set of regex expression to use to filter the path.
MiddleWare to add logging.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.LoggerMiddleware.__call__","title":"__call__async
","text":"__call__(scope: Scope, receive: Receive, send: Send)\n
Handle call.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.LoggerMiddleware.__init__","title":"__init__","text":"__init__(app: ASGIApp, querystrings: bool = False, headers: bool = False) -> None\n
Init Middleware.
Parameters:
app
(ASGIApp
) \u2013 starlette/FastAPI application.
Middleware to make URL parameters case-insensitive. taken from: tiangolo/fastapi#826
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.LowerCaseQueryStringMiddleware.__call__","title":"__call__async
","text":"__call__(scope: Scope, receive: Receive, send: Send)\n
Handle call.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.LowerCaseQueryStringMiddleware.__init__","title":"__init__","text":"__init__(app: ASGIApp) -> None\n
Init Middleware.
Parameters:
app
(ASGIApp
) \u2013 starlette/FastAPI application.
MiddleWare to add Total process time in response headers.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.TotalTimeMiddleware.__call__","title":"__call__async
","text":"__call__(scope: Scope, receive: Receive, send: Send)\n
Handle call.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.TotalTimeMiddleware.__init__","title":"__init__","text":"__init__(app: ASGIApp) -> None\n
Init Middleware.
Parameters:
app
(ASGIApp
) \u2013 starlette/FastAPI application.
Custom routing classes.
"},{"location":"api/titiler/core/routing/#titiler.core.routing.EndpointScope","title":"EndpointScope","text":" Bases: TypedDict
Define endpoint.
"},{"location":"api/titiler/core/routing/#titiler.core.routing.add_route_dependencies","title":"add_route_dependencies","text":"add_route_dependencies(\n routes: List[BaseRoute], *, scopes: List[EndpointScope], dependencies=List[params.Depends]\n)\n
Add dependencies to routes.
Allows a developer to add dependencies to a route after the route has been defined.
"},{"location":"api/titiler/core/routing/#titiler.core.routing.apiroute_factory","title":"apiroute_factory","text":"apiroute_factory(env: Optional[Dict] = None) -> Type[APIRoute]\n
Create Custom API Route class with custom Env.
Because we cannot create middleware for specific router we need to create a custom APIRoute which add the rasterio.Env(
block before the endpoint is actually called. This way we set the env outside the threads and we make sure that event multithreaded Reader will get the environment set.
Note: This has been tested in python 3.6 and 3.7 only.
"},{"location":"api/titiler/core/models/OGC/","title":"OGC","text":""},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC","title":"titiler.core.models.OGC","text":"OGC models.
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.Link","title":"Link","text":" Bases: BaseModel
Link model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/common-core/link.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.TileMatrixSetLink","title":"TileMatrixSetLink","text":" Bases: BaseModel
TileMatrixSetLink model.
Based on docs.opengeospatial.org/per/19-069.html#_tilematrixsets
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.TileMatrixSetList","title":"TileMatrixSetList","text":" Bases: BaseModel
TileMatrixSetList model.
Based on docs.opengeospatial.org/per/19-069.html#_tilematrixsets
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.TileMatrixSetRef","title":"TileMatrixSetRef","text":" Bases: BaseModel
TileMatrixSetRef model.
Based on docs.opengeospatial.org/per/19-069.html#_tilematrixsets
"},{"location":"api/titiler/core/models/mapbox/","title":"Mapbox/MapLibre","text":""},{"location":"api/titiler/core/models/mapbox/#titiler.core.models.mapbox","title":"titiler.core.models.mapbox","text":"Common response models.
"},{"location":"api/titiler/core/models/mapbox/#titiler.core.models.mapbox.TileJSON","title":"TileJSON","text":" Bases: BaseModel
TileJSON model.
Based on github.com/mapbox/tilejson-spec/tree/master/2.2.0
"},{"location":"api/titiler/core/models/mapbox/#titiler.core.models.mapbox.TileJSON.compute_center","title":"compute_center","text":"compute_center()\n
Compute center if it does not exist.
"},{"location":"api/titiler/core/models/responses/","title":"responses","text":""},{"location":"api/titiler/core/models/responses/#titiler.core.models.responses","title":"titiler.core.models.responses","text":"TiTiler response models.
"},{"location":"api/titiler/core/models/responses/#titiler.core.models.responses.ColorMapsList","title":"ColorMapsList","text":" Bases: BaseModel
Model for colormap list.
"},{"location":"api/titiler/core/models/responses/#titiler.core.models.responses.Point","title":"Point","text":" Bases: BaseModel
Point model.
response model for /point
endpoints
Bases: BaseModel
Statistics model in geojson response.
"},{"location":"api/titiler/core/resources/enums/","title":"enums","text":""},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums","title":"titiler.core.resources.enums","text":"Titiler.core Enums.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageDriver","title":"ImageDriver","text":" Bases: str
, Enum
Supported output GDAL drivers.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType","title":"ImageType","text":" Bases: str
, Enum
Available Output image type.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType.driver","title":"driver","text":"driver()\n
Return rio-tiler image default profile.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType.mediatype","title":"mediatype","text":"mediatype()\n
Return image media type.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType.profile","title":"profile","text":"profile()\n
Return rio-tiler image default profile.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.MediaType","title":"MediaType","text":" Bases: str
, Enum
Responses Media types formerly known as MIME types.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.OptionalHeader","title":"OptionalHeader","text":" Bases: str
, Enum
Optional Header to add in responses.
"},{"location":"api/titiler/core/resources/responses/","title":"responses","text":""},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses","title":"titiler.core.resources.responses","text":"Common response models.
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.GeoJSONResponse","title":"GeoJSONResponse","text":" Bases: JSONResponse
GeoJSON Response
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.JSONResponse","title":"JSONResponse","text":" Bases: JSONResponse
Custom JSON Response.
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.JSONResponse.render","title":"render","text":"render(content: Any) -> bytes\n
Render JSON.
Same defaults as starlette.responses.JSONResponse.render but allow NaN to be replaced by null using simplejson
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.XMLResponse","title":"XMLResponse","text":" Bases: Response
XML Response
"},{"location":"api/titiler/extensions/cogeo/","title":"cogeo","text":""},{"location":"api/titiler/extensions/cogeo/#titiler.extensions.cogeo","title":"titiler.extensions.cogeo","text":"rio-cogeo Extension.
"},{"location":"api/titiler/extensions/cogeo/#titiler.extensions.cogeo.cogValidateExtension","title":"cogValidateExtensiondataclass
","text":" Bases: FactoryExtension
Add /validate endpoint to a COG TilerFactory.
"},{"location":"api/titiler/extensions/cogeo/#titiler.extensions.cogeo.cogValidateExtension.register","title":"register","text":"register(factory: BaseTilerFactory)\n
Register endpoint to the tiler factory.
"},{"location":"api/titiler/extensions/stac/","title":"stac","text":""},{"location":"api/titiler/extensions/stac/#titiler.extensions.stac","title":"titiler.extensions.stac","text":"rio-stac Extension.
"},{"location":"api/titiler/extensions/stac/#titiler.extensions.stac.Item","title":"Item","text":" Bases: TypedDict
STAC Item.
"},{"location":"api/titiler/extensions/stac/#titiler.extensions.stac.stacExtension","title":"stacExtensiondataclass
","text":" Bases: FactoryExtension
Add /stac endpoint to a COG TilerFactory.
"},{"location":"api/titiler/extensions/stac/#titiler.extensions.stac.stacExtension.register","title":"register","text":"register(factory: BaseTilerFactory)\n
Register endpoint to the tiler factory.
"},{"location":"api/titiler/extensions/viewer/","title":"viewer","text":""},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer","title":"titiler.extensions.viewer","text":"titiler Viewer Extensions.
"},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer.cogViewerExtension","title":"cogViewerExtensiondataclass
","text":" Bases: FactoryExtension
Add /viewer endpoint to the TilerFactory.
"},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer.cogViewerExtension.register","title":"register","text":"register(factory: BaseTilerFactory)\n
Register endpoint to the tiler factory.
"},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer.stacViewerExtension","title":"stacViewerExtensiondataclass
","text":" Bases: FactoryExtension
Add /viewer endpoint to the TilerFactory.
"},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer.stacViewerExtension.register","title":"register","text":"register(factory: BaseTilerFactory)\n
Register endpoint to the tiler factory.
"},{"location":"api/titiler/mosaic/errors/","title":"errors","text":""},{"location":"api/titiler/mosaic/errors/#titiler.mosaic.errors","title":"titiler.mosaic.errors","text":"Titiler mosaic errors.
"},{"location":"api/titiler/mosaic/factory/","title":"factory","text":""},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory","title":"titiler.mosaic.factory","text":"TiTiler.mosaic Router factories.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory","title":"MosaicTilerFactorydataclass
","text":" Bases: BaseTilerFactory
MosaicTiler Factory.
The main difference with titiler.endpoint.factory.TilerFactory is that this factory needs the reader
to be of cogeo_mosaic.backends.BaseBackend
type (e.g MosaicBackend) and a dataset_reader
(BaseReader).
assets()\n
Register /assets endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.bounds","title":"bounds","text":"bounds()\n
Register /bounds endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.info","title":"info","text":"info()\n
Register /info endpoint
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.map_viewer","title":"map_viewer","text":"map_viewer()\n
Register /map endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.point","title":"point","text":"point()\n
Register /point endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.read","title":"read","text":"read()\n
Register / (Get) Read endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.register_routes","title":"register_routes","text":"register_routes()\n
This Method register routes to the router.
Because we wrap the endpoints in a class we cannot define the routes as methods (because of the self argument). The HACK is to define routes inside the class method and register them after the class initialization.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.tile","title":"tile","text":"tile()\n
Register /tiles endpoints.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.tilejson","title":"tilejson","text":"tilejson()\n
Add tilejson endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.validate","title":"validate","text":"validate()\n
Register /validate endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.wmts","title":"wmts","text":"wmts()\n
Add wmts endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.PixelSelectionParams","title":"PixelSelectionParams","text":"PixelSelectionParams(\n pixel_selection: Annotated[\n Literal[tuple([name for e in PixelSelectionMethod])],\n Query(description=\"Pixel selection method.\"),\n ] = \"first\"\n) -> MosaicMethodBase\n
Returns the mosaic method used to combine datasets together.
"},{"location":"api/titiler/mosaic/models/responses/","title":"responses","text":""},{"location":"api/titiler/mosaic/models/responses/#titiler.mosaic.models.responses","title":"titiler.mosaic.models.responses","text":"TiTiler.mosaic response models.
"},{"location":"api/titiler/mosaic/models/responses/#titiler.mosaic.models.responses.Point","title":"Point","text":" Bases: BaseModel
Point model.
response model for /point
endpoints
TiTiler is built on top of FastAPI, a modern, fast, Python web framework for building APIs. We can make our FastAPI application work as an Azure Function by wrapping it within the Azure Function Python worker.
If you are not familiar with Azure functions we recommend checking docs.microsoft.com/en-us/azure/azure-functions/ first.
Minimal TiTiler Azure function code:
import azure.functions as func\nfrom titiler.application.main import cog, mosaic, stac, tms\nfrom fastapi import FastAPI\n\n\napp = FastAPI()\napp.include_router(cog.router, prefix=\"/cog\", tags=[\"Cloud Optimized GeoTIFF\"])\napp.include_router(\n stac.router, prefix=\"/stac\", tags=[\"SpatioTemporal Asset Catalog\"]\n)\napp.include_router(mosaic.router, prefix=\"/mosaicjson\", tags=[\"MosaicJSON\"])\napp.include_router(tms.router, tags=[\"TileMatrixSets\"])\n\n\nasync def main(\n req: func.HttpRequest, context: func.Context,\n) -> func.HttpResponse:\n return await func.AsgiMiddleware(app).handle_async(req, context)\n
"},{"location":"deployment/azure/#requirements","title":"Requirements","text":"See: docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-python?tabs=azure-cli%2Cbash%2Cbrowser#create-supporting-azure-resources-for-your-function
$ git clone https://github.com/developmentseed/titiler.git\n$ cd titiler/deployment/azure\n\n$ az login\n$ az group create --name AzureFunctionsTiTiler-rg --location eastus\n$ az storage account create --name titilerstorage --sku Standard_LRS -g AzureFunctionsTiTiler-rg\n$ az functionapp create --consumption-plan-location eastus --runtime python --runtime-version 3.8 --functions-version 3 --name titiler --os-type linux -g AzureFunctionsTiTiler-rg -s titilerstorage\n$ func azure functionapp publish titiler\n
or
use VScode: docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-python#publish-the-project-to-azure
"},{"location":"deployment/azure/#docs","title":"Docs","text":"Try locally
minikube start\nkubectl config use-context minikube\nhelm init --wait\n\n# in the k8s directory\nhelm install -f titiler/Chart.yaml titiler\n
For more info about K8S cluster and node configuration please see: developmentseed/titiler#212
"},{"location":"deployment/aws/ecs/","title":"AWS ECS (Fargate) + ALB (Application Load Balancer)","text":"Warning
When using Fargate or vanilla ECS, you should set the number of worker carefully. Setting too high a number of workers could lead to extra charges due to a bug in fastapi (https://github.com/developmentseed/titiler/issues/119, https://github.com/tiangolo/fastapi/issues/253).\n
"},{"location":"deployment/aws/ecs/#deploy","title":"Deploy","text":"The example handles tasks such as generating a docker image and setting up an application load balancer (ALB) and ECS services.
Install CDK and connect to your AWS account. This step is only necessary once per AWS account.
# Download titiler repo\n$ git clone https://github.com/developmentseed/titiler.git\n\n# Create a virtual environment\npython -m pip install --upgrade virtualenv\nvirtualenv .venv\nsource .venv/bin/activate\n\n# Install CDK dependencies\npython -m pip install -r requirements-cdk.txt\n\n# Install NodeJS dependencies\nnpm install\n\n$ npm run cdk -- bootstrap # Deploys the CDK toolkit stack into an AWS environment\n\n# or in specific region\n$ npm run cdk -- bootstrap aws://${AWS_ACCOUNT_ID}/eu-central-1\n
Generate CloudFormation template
$ npm run cdk -- synth # Synthesizes and prints the CloudFormation template for this stack\n
Update settings (see intro.md)
export TITILER_STACK_NAME=\"mytiler\"\nexport TITILER_STACK_STAGE=\"dev\"\nexport TITILER_STACK_MIN_ECS_INSTANCES=10\n
Available settings for ECS:
min_ecs_instances: int = 5\nmax_ecs_instances: int = 50\n\n# CPU value | Memory value\n# 256 (.25 vCPU) | 0.5 GB, 1 GB, 2 GB\n# 512 (.5 vCPU) | 1 GB, 2 GB, 3 GB, 4 GB\n# 1024 (1 vCPU) | 2 GB, 3 GB, 4 GB, 5 GB, 6 GB, 7 GB, 8 GB\n# 2048 (2 vCPU) | Between 4 GB and 16 GB in 1-GB increments\n# 4096 (4 vCPU) | Between 8 GB and 30 GB in 1-GB increments\ntask_cpu: int = 256\ntask_memory: int = 512\n\n# GUNICORN configuration\n# Ref: https://github.com/developmentseed/titiler/issues/119\n\n# WORKERS_PER_CORE\n# This image will check how many CPU cores are available in the current server running your container.\n# It will set the number of workers to the number of CPU cores multiplied by this value.\nworkers_per_core: int = 1\n\n# MAX_WORKERS\n# You can use it to let the image compute the number of workers automatically but making sure it's limited to a maximum.\n# should depends on `task_cpu`\nmax_workers: int = 1\n\n# WEB_CONCURRENCY\n# Override the automatic definition of number of workers.\n# Set to the number of CPU cores in the current server multiplied by the environment variable WORKERS_PER_CORE.\n# So, in a server with 2 cores, by default it will be set to 2.\nweb_concurrency: Optional[int]\n
Deploy
# Deploys the stack(s) mytiler-ecs-dev in cdk/app.py\n$ npm run cdk -- deploy mytiler-ecs-dev\n
Examples of AWS deployments can be found in github.com/developmentseed/titiler/tree/main/deployment/aws. Those examples use AWS Cloud Development Kit to define stacks using python code.
"},{"location":"deployment/aws/intro/#configurationsettings","title":"Configuration/Settings","text":"Deployment settings are managed via pydantic.BaseSettings and stored in config.py. Pydantic BaseSettings can receive input to overwrite the default value from a .env
file or from environment variables.
Variables in .env
or in environment variable need to be prefixed with TITILER_STACK_
:
TITILER_STACK_NAME=\"my-tiler\"\nTITILER_STACK_STAGE=\"dev\"\n\nTITILER_STACK_BUCKETS='[\"my-bucket*\", \"*\"]'\n\nTITILER_STACK_MEMORY=3008\n\n# Uncomment to allow lambda to access content on requester-payer buckets\n# TITILER_STACK_ENV='{\"AWS_REQUEST_PAYER\":\"requester\"}'\n\n# Uncomment if you only on the /cog endpoint\n# TITILER_STACK_ENV='{\"TITILER_API_DISABLE_STAC\": \"TRUE\", \"TITILER_API_DISABLE_MOSAIC\": \"TRUE\"}'\n
Default values from config.py:
name: str = \"titiler\"\nstage: str = \"production\"\n\nowner: Optional[str]\nclient: Optional[str]\n\n# Default options are optimized for CloudOptimized GeoTIFF\n# For more information on GDAL env see: https://gdal.org/user/configoptions.html\nenv: Dict = {\n \"CPL_VSIL_CURL_ALLOWED_EXTENSIONS\": \".tif,.TIF,.tiff\",\n \"GDAL_CACHEMAX\": \"200\" # 200 mb\n \"GDAL_DISABLE_READDIR_ON_OPEN\": \"EMPTY_DIR\",\n \"GDAL_HTTP_MERGE_CONSECUTIVE_RANGES\": \"YES\",\n \"GDAL_HTTP_MULTIPLEX\": \"YES\",\n \"GDAL_HTTP_VERSION\": \"2\",\n \"PYTHONWARNINGS\": \"ignore\",\n \"VSI_CACHE\": \"TRUE\",\n \"VSI_CACHE_SIZE\": \"5000000\" # 5 MB (per file-handle)\n}\n\n# add S3 bucket where TiTiler could do HEAD and GET Requests\nbuckets: List = []\n\n###########################################################################\n# AWS ECS\n# The following settings only apply to AWS ECS deployment\nmin_ecs_instances: int = 5\nmax_ecs_instances: int = 50\n\n# CPU value | Memory value\n# 256 (.25 vCPU) | 0.5 GB, 1 GB, 2 GB\n# 512 (.5 vCPU) | 1 GB, 2 GB, 3 GB, 4 GB\n# 1024 (1 vCPU) | 2 GB, 3 GB, 4 GB, 5 GB, 6 GB, 7 GB, 8 GB\n# 2048 (2 vCPU) | Between 4 GB and 16 GB in 1-GB increments\n# 4096 (4 vCPU) | Between 8 GB and 30 GB in 1-GB increments\ntask_cpu: int = 256\ntask_memory: int = 512\n\n# GUNICORN configuration\n# Ref: https://github.com/developmentseed/titiler/issues/119\n\n# WORKERS_PER_CORE\n# This image will check how many CPU cores are available in the current server running your container.\n# It will set the number of workers to the number of CPU cores multiplied by this value.\nworkers_per_core: int = 1\n\n# MAX_WORKERS\n# You can use it to let the image compute the number of workers automatically but making sure it's limited to a maximum.\n# should depends on `task_cpu`\nmax_workers: int = 1\n\n# WEB_CONCURRENCY\n# Override the automatic definition of number of workers.\n# Set to the number of CPU cores in the current server multiplied by the environment variable WORKERS_PER_CORE.\n# So, in a server with 2 cores, by default it will be set to 2.\nweb_concurrency: Optional[int]\n\nimage_version: str = \"latest\"\n\n###########################################################################\n# AWS LAMBDA\n# The following settings only apply to AWS Lambda deployment\ntimeout: int = 10\nmemory: int = 1536\n# more about lambda config: https://www.sentiatechblog.com/aws-re-invent-2020-day-3-optimizing-lambda-cost-with-multi-threading\n\n# The maximum of concurrent executions you want to reserve for the function.\n# Default: - No specific limit - account limit.\nmax_concurrent: Optional[int]\n
"},{"location":"deployment/aws/lambda/","title":"AWS Lambda","text":"TiTiler is built on top of FastAPI, a modern, fast, Python web framework for building APIs. It doesn't work natively with AWS Lambda and API Gateway because FastAPI understands HTTP requests, not API Gateway's event
and context
JSON objects. However, we can make our FastAPI application work on Lambda by wrapping it with the awesome mangum
module, which translates API Gateway events into HTTP requests.
from mangum import Mangum\nfrom titiler.main import app\n\nhandler = Mangum(app, enable_lifespan=False)\n
"},{"location":"deployment/aws/lambda/#deploy","title":"Deploy","text":"The Lambda stack is also deployed by the AWS CDK utility. Under the hood, CDK will create the deployment package required for AWS Lambda, upload it to AWS, and handle the creation of the Lambda and API Gateway resources.
Install CDK and connect to your AWS account. This step is only necessary once per AWS account.
# Download titiler repo\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler/deployment/aws\n\n# Create a virtual environment\npython -m pip install --upgrade virtualenv\nvirtualenv .venv\nsource .venv/bin/activate\n\n# Install CDK dependencies\npython -m pip install -r requirements-cdk.txt\n\n# Install NodeJS dependencies\nnpm install\n\n$ npm run cdk -- bootstrap # Deploys the CDK toolkit stack into an AWS environment\n\n# or in specific region\n$ npm run cdk -- bootstrap aws://${AWS_ACCOUNT_ID}/eu-central-1\n
Pre-Generate CFN template
$ npm run cdk -- synth # Synthesizes and prints the CloudFormation template for this stack\n
Update settings (see intro.md)
export TITILER_STACK_NAME=\"mytiler\"\nexport TITILER_STACK_STAGE=\"dev\"\nexport TITILER_STACK_MEMORY=512\n
Available settings for AWS Lambda:
timeout: int = 10\nmemory: int = 1536\n\n# The maximum of concurrent executions you want to reserve for the function.\n# Default: - No specific limit - account limit.\nmax_concurrent: Optional[int]\n
Deploy
$ npm run cdk -- deploy mytiler-lambda-dev # Deploys the stack(s) titiler-lambda-dev in cdk/app.py\n\n# Deploy in specific region\n$ AWS_DEFAULT_REGION=eu-central-1 AWS_REGION=eu-central-1 npm run cdk -- deploy mytiler-lambda-dev\n
An AWS SAM (Serverless Application Model) application is publicly available over serverlessrepo.aws.amazon.com/applications/us-east-1/552819999234/TiTiler
This enable almost a one click
deployment solution
The SAM template is built on top of developmentseed/titiler-lambda-layer.
"},{"location":"endpoints/algorithms/","title":"/algorithms","text":"In addition to the /cog
, /stac
and /mosaicjson
endpoints, the titiler.application
package FastAPI application commes with additional metadata endpoints.
GET
/algorithms
JSON retrieve the list of available Algorithms GET
/algorithms/{algorithmId}
JSON retrieve the metadata of the specified algorithm."},{"location":"endpoints/algorithms/#description","title":"Description","text":""},{"location":"endpoints/algorithms/#list-algorithm","title":"List Algorithm","text":":endpoint:/algorithm
- Get the list of supported TileMatrixSet
$ curl https://myendpoint/algorithms | jq\n\n{\n \"hillshade\": {\n \"title\": \"Hillshade\",\n \"description\": \"Create hillshade from DEM dataset.\",\n \"inputs\": {\n \"nbands\": 1\n },\n \"outputs\": {\n \"nbands\": 1,\n \"dtype\": \"uint8\",\n \"min\": null,\n \"max\": null\n },\n \"parameters\": {\n \"azimuth\": {\n \"default\": 90,\n \"maximum\": 360,\n \"minimum\": 0,\n \"title\": \"Azimuth\",\n \"type\": \"integer\"\n },\n \"angle_altitude\": {\n \"default\": 90.0,\n \"maximum\": 90.0,\n \"minimum\": -90.0,\n \"title\": \"Angle Altitude\",\n \"type\": \"number\"\n },\n \"buffer\": {\n \"default\": 3,\n \"maximum\": 99,\n \"minimum\": 0,\n \"title\": \"Buffer\",\n \"type\": \"integer\"\n }\n }\n },\n ...\n}\n
"},{"location":"endpoints/algorithms/#get-algorithm-info","title":"Get Algorithm info","text":":endpoint:/algorithms/{algorithmId}
- Get the algorithm metadata
$ curl http://127.0.0.1:8000/algorithms/contours | jq\n\n{\n \"title\": \"Contours\",\n \"description\": \"Create contours from DEM dataset.\",\n \"inputs\": {\n \"nbands\": 1\n },\n \"outputs\": {\n \"nbands\": 3,\n \"dtype\": \"uint8\",\n \"min\": null,\n \"max\": null\n },\n \"parameters\": {\n \"increment\": {\n \"default\": 35,\n \"maximum\": 999,\n \"minimum\": 0,\n \"title\": \"Increment\",\n \"type\": \"integer\"\n },\n \"thickness\": {\n \"default\": 1,\n \"maximum\": 10,\n \"minimum\": 0,\n \"title\": \"Thickness\",\n \"type\": \"integer\"\n },\n \"minz\": {\n \"default\": -12000,\n \"maximum\": 99999,\n \"minimum\": -99999,\n \"title\": \"Minz\",\n \"type\": \"integer\"\n },\n \"maxz\": {\n \"default\": 8000,\n \"maximum\": 99999,\n \"minimum\": -99999,\n \"title\": \"Maxz\",\n \"type\": \"integer\"\n }\n }\n}\n
"},{"location":"endpoints/cog/","title":"/cog","text":"The titiler.application
package comes with a full FastAPI application with COG, STAC and MosaicJSON supports.
The /cog
routes are based on titiler.core.factory.TilerFactory
but with cogValidateExtension
and cogViewerExtension
extensions.
GET
/cog/bounds
JSON return dataset's bounds GET
/cog/info
JSON return dataset's basic info GET
/cog/info.geojson
GeoJSON return dataset's basic info as a GeoJSON feature GET
/cog/statistics
JSON return dataset's statistics POST
/cog/statistics
GeoJSON return dataset's statistics for a GeoJSON GET
/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
image/bin create a web map tile image from a dataset GET
/cog/{tileMatrixSetId}/tilejson.json
JSON return a Mapbox TileJSON document GET
/cog/{tileMatrixSetId}/WMTSCapabilities.xml
XML return OGC WMTS Get Capabilities GET
/cog/point/{lon},{lat}
JSON return pixel values from a dataset GET
/cog/preview[.{format}]
image/bin create a preview image from a dataset GET
/cog/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}
image/bin create an image from part of a dataset POST
/cog/feature[/{width}x{height}][].{format}]
image/bin create an image from a GeoJSON feature GET
/cog/{tileMatrixSetId}/map
HTML simple map viewer GET
/cog/validate
JSON validate a COG and return dataset info (from titiler.extensions.cogValidateExtension
) GET
/cog/viewer
HTML demo webpage (from titiler.extensions.cogViewerExtension
) GET
/cog/stac
GeoJSON create STAC Items from a dataset (from titiler.extensions.stacExtension
)"},{"location":"endpoints/cog/#description","title":"Description","text":""},{"location":"endpoints/cog/#tiles","title":"Tiles","text":":endpoint:/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
PathParams:
WebMercatorQuad
)QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).0.5
. Output tilesize will be expanded to tilesize + 2 * buffer
(e.g 0.5 = 257x257, 1.0 = 258x258).0
.hillshade
).Example:
https://myendpoint/cog/tiles/WebMercatorQuad/1/2/3?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/tiles/WebMercatorQuad/1/2/3.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2
https://myendpoint/cog/tiles/WorldCRS84Quad/1/2/3@2x.png?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/tiles/WorldCRS84Quad/1/2/3?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie
:endpoint:/cog/preview[.{format}]
PathParams:
QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).hillshade
).Important
if height and width are provided max_size will be ignored.
Example:
https://myendpoint/cog/preview?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/preview.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2
https://myendpoint/cog/preview?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie
:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}.{format}
:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}
PathParams:
QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).epsg:4326
.coord_crs
.nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).hillshade
).Important
if height and width are provided max_size will be ignored.
Example:
https://myendpoint/cog/bbox/0,0,10,10.png?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/bbox/0,0,10,10.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie
:endpoint:/cog/feature[/{width}x{height}][].{format}] - [POST]
Body:
PathParams:
QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).epsg:4326
.coord_crs
.nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).hillshade
).Important
if height and width are provided max_size will be ignored.
Example:
https://myendpoint/cog/feature?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/feature.png?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/feature/100x100.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie
Note: if height
and width
are provided max_size
will be ignored.
:endpoint:/cog/point/{lon},{lat}
PathParams:
QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).epsg:4326
.nearest
.nearest
.Example:
https://myendpoint/cog/point/0,0?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/point/0,0?url=https://somewhere.com/mycog.tif&bidx=1
:endpoint:/cog/{tileMatrixSetId}/tilejson.json
tileJSON document
PathParams:
WebMercatorQuad
)QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).0.5
. Output tilesize will be expanded to tilesize + 2 * buffer
(e.g 0.5 = 257x257, 1.0 = 258x258).0
.hillshade
).Example:
https://myendpoint/cog/WebMercatorQuad/tilejson.json?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/WebMercatorQuad/tilejson.json?url=https://somewhere.com/mycog.tif&tile_format=png
https://myendpoint/cog/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/mycog.tif&tile_scale=2&bidx=1,2,3
:endpoint:/cog/{tileMatrixSetId}/map
Simple viewer
PathParams:
WebMercatorQuad
)QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).0.5
. Output tilesize will be expanded to tilesize + 2 * buffer
(e.g 0.5 = 257x257, 1.0 = 258x258).0
.hillshade
).Example:
https://myendpoint/cog/WebMercatorQuad/map?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/WebMercatorQuad/map?url=https://somewhere.com/mycog.tif&tile_format=png
https://myendpoint/cog/WorldCRS84Quad/map?url=https://somewhere.com/mycog.tif&tile_scale=2&bidx=1,2,3
:endpoint:/cog/bounds
general image bounds
Example:
https://myendpoint/cog/bounds?url=https://somewhere.com/mycog.tif
:endpoint:/cog/info
general raster info
:endpoint:/cog/info.geojson
general raster info as a GeoJSON feature
Example:
https://myendpoint/cog/info?url=https://somewhere.com/mycog.tif
https://myendpoint/cog/info.geojson?url=https://somewhere.com/mycog.tif
Advanced raster statistics
:endpoint:/cog/statistics - [GET]
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).nearest
.hillshade
).Example:
https://myendpoint/cog/statistics?url=https://somewhere.com/mycog.tif&bidx=1,2,3&categorical=true&c=1&c=2&c=3&p=2&p98
:endpoint:/cog/statistics - [POST]
Body:
QueryParams:
bidx=1
, bidx=1&bidx=2&bidx=3
).expression=b1/b2
).epsg:4326
.coord_crs
.nearest
.nearest
.hillshade
).Example:
https://myendpoint/cog/statistics?url=https://somewhere.com/mycog.tif&bidx=1,2,3&categorical=true&c=1&c=2&c=3&p=2&p98
:endpoint:/cog/viewer
- COG Viewer
Example:
https://myendpoint/cog/viewer?url=https://somewhere.com/mycog.tif
:endpoint:/cog/validate
- COG Viewer
Example:
https://myendpoint/cog/validate?url=https://somewhere.com/mycog.tif
:endpoint:/cog/stac
- Create STAC Item
projection
extension and properties.raster
extension and properties.eo
extension and properties.Example:
https://myendpoint/cog/stac?url=https://somewhere.com/mycog.tif
In addition to the /cog
, /stac
and /mosaicjson
endpoints, the titiler.application
package FastAPI application commes with additional metadata endpoints.
GET
/colorMaps
JSON retrieve the list of available colorMaps GET
/colorMaps/{colorMapId}
JSON retrieve the metadata or image of the specified colorMap."},{"location":"endpoints/colormaps/#description","title":"Description","text":""},{"location":"endpoints/colormaps/#list-colormaps","title":"List colormaps","text":":endpoint:/colorMaps
- Get the list of supported ColorMaps
$ curl https://myendpoint/colorMaps | jq\n\n{\n \"colorMaps\": [\n \"dense_r\",\n \"delta\",\n ...\n ],\n \"links\": [\n {\n \"href\": \"http://myendpoint/colorMaps\",\n \"rel\": \"self\",\n \"type\": \"application/json\",\n \"title\": \"List of available colormaps\"\n },\n {\n \"href\": \"http://myendpoint/colorMaps/{colorMapId}\",\n \"rel\": \"data\",\n \"type\": \"application/json\",\n \"templated\": true,\n \"title\": \"Retrieve colormap metadata\"\n },\n {\n \"href\": \"http://myendpoint/colorMaps/{colorMapId}?format=png\",\n \"rel\": \"data\",\n \"type\": \"image/png\",\n \"templated\": true,\n \"title\": \"Retrieve colormap as image\"\n }\n ]\n}\n
"},{"location":"endpoints/colormaps/#get-colormap-metadata-or-as-image","title":"Get ColorMap metadata or as image","text":":endpoint:/colorMaps/{colorMapId}
- Get the ColorMap metadata or image
PathParams:
QueryParams:
horizontal
.$ curl http://myendpoint/colorMaps/viridis | jq\n\n{\n \"0\": [\n 68,\n 1,\n 84,\n 255\n ],\n ...\n \"255\": [\n 253,\n 231,\n 36,\n 255\n ]\n}\n
curl http://myendpoint/colorMaps/viridis?format=png\n
curl http://myendpoint/colorMaps/viridis?format=png&orientation=vertical\n
curl http://myendpoint/colorMaps/viridis?format=png&orientation=vertical&width=100&height=1000\n
"},{"location":"endpoints/mosaic/","title":"/mosaicjson","text":"The titiler.application
package comes with a full FastAPI application with COG, STAC and MosaicJSON supports.
Read Mosaic Info/Metadata and create Web map Tiles from a multiple COG. The mosaic
router is built on top of titiler.mosaic.factor.MosaicTilerFactory
.
GET
/mosaicjson/
JSON return a MosaicJSON document GET
/mosaicjson/bounds
JSON return mosaic's bounds GET
/mosaicjson/info
JSON return mosaic's basic info GET
/mosaicjson/info.geojson
GeoJSON return mosaic's basic info as a GeoJSON feature GET
/mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
image/bin create a web map tile image from mosaic assets GET
/mosaicjson/{tileMatrixSetId}/tilejson.json
JSON return a Mapbox TileJSON document GET
/mosaicjson/{tileMatrixSetId}/WMTSCapabilities.xml
XML return OGC WMTS Get Capabilities GET
/mosaicjson/point/{lon},{lat}
JSON return pixel value from a mosaic assets GET
/mosaicjson/{z}/{x}/{y}/assets
JSON return list of assets intersecting a XYZ tile GET
/mosaicjson/{lon},{lat}/assets
JSON return list of assets intersecting a point GET
/mosaicjson/{minx},{miny},{maxx},{maxy}/assets
JSON return list of assets intersecting a bounding box GET
/mosaicjson/{tileMatrixSetId}/map
HTML simple map viewer"},{"location":"endpoints/mosaic/#description","title":"Description","text":"[TODO]
"},{"location":"endpoints/stac/","title":"/stac","text":"The titiler.application
package comes with a full FastAPI application with COG, STAC and MosaicJSON supports.
The /stac
routes are based on titiler.core.factory.MultiBaseTilerFactory
but with stacViewerExtension
extension.
GET
/stac/assets
JSON return available assets within the STAC item GET
/stac/bounds
JSON return STAC item bounds GET
/stac/info
JSON return asset's basic info GET
/stac/info.geojson
GeoJSON return asset's basic info as a GeoJSON feature GET
/stac/asset_statistics
JSON return per asset statistics GET
/stac/statistics
JSON return asset's statistics POST
/stac/statistics
GeoJSON return asset's statistics for a GeoJSON GET
/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
image/bin create a web map tile image from assets GET
/stac/{tileMatrixSetId}/tilejson.json
JSON return a Mapbox TileJSON document GET
/stac/{tileMatrixSetId}/WMTSCapabilities.xml
XML return OGC WMTS Get Capabilities GET
/stac/point/{lon},{lat}
JSON return pixel value from assets GET
/stac/preview[.{format}]
image/bin create a preview image from assets GET
/stac/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}
image/bin create an image from part of assets POST
/stac/feature[/{width}x{height}][].{format}]
image/bin create an image from a geojson covering the assets GET
/stac/{tileMatrixSetId}/map
HTML simple map viewer GET
/stac/viewer
HTML demo webpage (from titiler.extensions.stacViewerExtension
)"},{"location":"endpoints/stac/#description","title":"Description","text":""},{"location":"endpoints/stac/#tiles","title":"Tiles","text":":endpoint:/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]
PathParams:
WebMercatorQuad
)QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).0.5
. Output tilesize will be expanded to tilesize + 2 * buffer
(e.g 0.5 = 257x257, 1.0 = 258x258).0
.hillshade
).Important
assets OR expression is required
Example:
https://myendpoint/stac/tiles/WebMercatorQuad/1/2/3?url=https://somewhere.com/item.json&assets=B01&assets=B00
https://myendpoint/stac/tiles/WebMercatorQuad/1/2/3.jpg?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/tiles/WorldCRS84Quad/1/2/3@2x.png?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/tiles/WorldCRS84Quad/1/2/3?url=https://somewhere.com/item.json&expression=B01/B02&rescale=0,1000&colormap_name=cfastie
:endpoint:/stac/preview[.{format}]
PathParams:
QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).hillshade
).Important
assets OR expression is required
if height and width are provided max_size will be ignored.
Example:
https://myendpoint/stac/preview?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/preview.jpg?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/preview?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie
:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}.{format}
:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}
PathParams:
QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).epsg:4326
.coord_crs
.nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).hillshade
).Important
assets OR expression is required
if height and width are provided max_size will be ignored.
Example:
https://myendpoint/stac/bbox/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/bbox/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie
:endpoint:/stac/feature[/{width}x{height}][].{format}] - [POST]
Body:
PathParams:
QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).epsg:4326
.coord_crs
.nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).hillshade
).Important
assets OR expression is required
if height and width are provided max_size will be ignored.
Example:
https://myendpoint/stac/feature?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/feature.png?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/feature/100x100.png?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie
:endpoint:/cog/point/{lon},{lat}
PathParams:
QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).epsg:4326
.nearest
.Important
assets OR expression is required
Example:
https://myendpoint/stac/point/0,0?url=https://somewhere.com/item.json&assets=B01
:endpoint:/stac/{tileMatrixSetId}/tilejson.json
tileJSON document
PathParams:
WebMercatorQuad
)QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).0.5
. Output tilesize will be expanded to tilesize + 2 * buffer
(e.g 0.5 = 257x257, 1.0 = 258x258).0
.hillshade
).Important
assets OR expression is required
Example:
https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=png
https://myendpoint/stac/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/item.json&tile_scale=2&expression=B01/B02
:endpoint:/stac/{tileMatrixSetId}/map
Simple viewer
PathParams:
WebMercatorQuad
)QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).nearest
.nearest
.rescale=0,1000
, rescale=0,1000&rescale=0,3000&rescale=0,2000
).0.5
. Output tilesize will be expanded to tilesize + 2 * buffer
(e.g 0.5 = 257x257, 1.0 = 258x258).0
.hillshade
).Important
assets OR expression is required
Example:
https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01
https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=png
https://myendpoint/stac/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/item.json&tile_scale=2&expression=B01/B02
:endpoint:/stac/bounds
- Return the bounds of the STAC item.
Example:
https://myendpoint/stac/bounds?url=https://somewhere.com/item.json
:endpoint:/stac/info
- Return basic info on STAC item's COG.
Example:
https://myendpoint/stac/info?url=https://somewhere.com/item.json&assets=B01
:endpoint:/stac/info.geojson
- Return basic info on STAC item's COG as a GeoJSON feature
Example:
https://myendpoint/stac/info.geojson?url=https://somewhere.com/item.json&assets=B01
:endpoint:/stac/assets
- Return the list of available assets
Example:
https://myendpoint/stac/assets?url=https://somewhere.com/item.json
:endpoint:/stac/asset_statistics - [GET]
Asset1|1,2,3
).Asset1|b1\\*b2
).nearest
.Example:
https://myendpoint/stac/statistics?url=https://somewhere.com/item.json&assets=B01&categorical=true&c=1&c=2&c=3&p=2&p98
:endpoint:/stac/statistics - [GET]
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).nearest
.hillshade
).Example:
https://myendpoint/stac/statistics?url=https://somewhere.com/item.json&assets=B01&categorical=true&c=1&c=2&c=3&p=2&p98
:endpoint:/stac/statistics - [POST]
Body:
QueryParams:
Asset1_b1/Asset2_b1
).Asset1/Asset2
can be passed.Asset1|1,2,3
).epsg:4326
.coord_crs
.nearest
.nearest
.hillshade
).Example:
https://myendpoint/stac/statistics?url=https://somewhere.com/item.json&assets=B01&categorical=true&c=1&c=2&c=3&p=2&p98
:endpoint:/stac/viewer
- STAC viewer
Example:
https://myendpoint/stac/viewer?url=https://somewhere.com/item.json
In addition to the /cog
, /stac
and /mosaicjson
endpoints, the titiler.application
package FastAPI application comes with additional metadata endpoints.
GET
/tileMatrixSets
JSON return the list of supported TileMatrixSet GET
/tileMatrixSets/{tileMatrixSetId}
JSON return the TileMatrixSet JSON document"},{"location":"endpoints/tms/#description","title":"Description","text":""},{"location":"endpoints/tms/#list-tms","title":"List TMS","text":":endpoint:/tileMatrixSets
- Get the list of supported TileMatrixSet
$ curl https://myendpoint/tileMatrixSets | jq\n\n{\n \"tileMatrixSets\": [\n {\n \"id\": \"LINZAntarticaMapTilegrid\",\n \"title\": \"LINZ Antarctic Map Tile Grid (Ross Sea Region)\",\n \"links\": [\n {\n \"href\": \"https://myendpoint/tileMatrixSets/LINZAntarticaMapTilegrid\",\n \"rel\": \"item\",\n \"type\": \"application/json\"\n }\n ]\n },\n ...\n ]\n}\n
"},{"location":"endpoints/tms/#get-tms-info","title":"Get TMS info","text":":endpoint:/tileMatrixSets/{tileMatrixSetId}
- Get the TileMatrixSet JSON document
$ curl http://127.0.0.1:8000/tileMatrixSets/WebMercatorQuad | jq\n\n{\n \"type\": \"TileMatrixSetType\",\n \"title\": \"Google Maps Compatible for the World\",\n \"identifier\": \"WebMercatorQuad\",\n \"supportedCRS\": \"http://www.opengis.net/def/crs/EPSG/0/3857\",\n \"wellKnownScaleSet\": \"http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible\",\n \"boundingBox\": {\n \"type\": \"BoundingBoxType\",\n \"crs\": \"http://www.opengis.net/def/crs/EPSG/0/3857\",\n \"lowerCorner\": [\n -20037508.3427892,\n -20037508.3427892\n ],\n \"upperCorner\": [\n 20037508.3427892,\n 20037508.3427892\n ]\n },\n \"tileMatrix\": [\n {\n \"type\": \"TileMatrixType\",\n \"identifier\": \"0\",\n \"scaleDenominator\": 559082264.028717,\n \"topLeftCorner\": [\n -20037508.3427892,\n 20037508.3427892\n ],\n \"tileWidth\": 256,\n \"tileHeight\": 256,\n \"matrixWidth\": 1,\n \"matrixHeight\": 1\n },\n ...\n
"},{"location":"examples/code/create_gdal_wmts_extension/","title":"GDAL WMTS Extension","text":"Goal: add a /wmts.xml
endpoint to return a GDAL WMTS service description XML file
requirements: titiler.extension >=0.11
1 - Create an extension
# wmts.py\n\"\"\"gdal WMTS service Extension.\"\"\"\n\nimport xml.etree.ElementTree as ET\nfrom dataclasses import dataclass\nfrom typing import Literal\nfrom urllib.parse import urlencode\n\nfrom fastapi import Depends, Query\nfrom starlette.requests import Request\n\nfrom titiler.core.factory import BaseTilerFactory, FactoryExtension\nfrom titiler.core.resources.responses import XMLResponse\n\n\n@dataclass\nclass gdalwmtsExtension(FactoryExtension):\n \"\"\"Add /wmts.xml endpoint to a TilerFactory.\"\"\"\n\n def register(self, factory: BaseTilerFactory): # noqa: C901\n \"\"\"Register endpoint to the tiler factory.\"\"\"\n\n @factory.router.get(\n \"/{tileMatrixSetId}/wmts.xml\",\n response_class=XMLResponse,\n responses={\n 200: {\n \"description\": \"GDAL WMTS service description XML file\",\n \"content\": {\n \"application/xml\": {},\n },\n },\n },\n )\n def gdal_wmts(\n request: Request,\n tileMatrixSetId: Literal[tuple(factory.supported_tms.list())] = Path( # type: ignore\n description=\"TileMatrixSet Name\",\n ),\n url: str = Depends(factory.path_dependency), # noqa\n bandscount: int = Query(\n ..., description=\"Number of band returned by the tiler\"\n ),\n datatype: str = Query(..., description=\"Datatype returned by the tiler\"),\n maxconnections: int = Query(\n None,\n description=\"Maximum number of simultaneous connections (defaults to 2).\",\n ),\n timeout: int = Query(\n None, description=\"Connection timeout in seconds (defaults to 30).\"\n ),\n cache: bool = Query(None, description=\"Allow local cache.\"),\n ):\n \"\"\"Return a GDAL WMTS Service description.\"\"\"\n route_params = {\n \"tileMatrixSetId\": tileMatrixSetId,\n }\n wmts_url = factory.url_for(request, \"wmts\", **route_params)\n\n qs_key_to_remove = [\n \"tilematrixsetid\",\n \"bandscount\",\n \"datatype\",\n \"maxconnections\",\n \"timeout\",\n ]\n qs = [\n (key, value)\n for (key, value) in request.query_params._list\n if key.lower() not in qs_key_to_remove\n ]\n if qs:\n wmts_url += f\"?{urlencode(qs)}\"\n\n maxconnections = maxconnections or 2\n timeout = timeout or 30\n\n xml = ET.Element(\"GDAL_WMTS\")\n cap = ET.SubElement(xml, \"GetCapabilitiesUrl\")\n cap.text = wmts_url\n\n bandel = ET.SubElement(xml, \"BandsCount\")\n bandel.text = str(bandscount)\n datael = ET.SubElement(xml, \"DataType\")\n datael.text = datatype\n\n if cache:\n cacheel = ET.SubElement(xml, \"Cache\")\n\n connel = ET.SubElement(xml, \"MaxConnections\")\n connel.text = str(maxconnections)\n timeel = ET.SubElement(xml, \"Timeout\")\n timeel.text = str(timeout)\n codeel = ET.SubElement(xml, \"ZeroBlockHttpCodes\")\n codeel.text = \"404\"\n excepel = ET.SubElement(xml, \"ZeroBlockOnServerException\")\n excepel.text = \"true\"\n\n return XMLResponse(ET.tostring(xml))\n
2 - Create app and register our extension
\"\"\"app.\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\nfrom titiler.core.factory import TilerFactory\n\nfrom fastapi import FastAPI\n\nfrom .wmts import gdalwmtsExtension\n\napp = FastAPI(title=\"My simple app with custom TMS\")\n\n# Create a set of endpoints using TilerFactory and add our extension\ntiler = TilerFactory(extensions=[gdalwmtsExtension()])\n\napp.include_router(tiler.router)\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n
3 - Use it
from rio_tiler.io import Reader\n\nwith Reader(\"http://0.0.0.0/WebMercatorQuad/wmts.xml?url=file.tif&bidx=1&bandscount=1&datatype=float32&tile_format=tif\") as src:\n im = src.preview()\n
Notes
The /wmts.xml
endpoint has no idea about the data itself and do not care about the bidx
or expression
which is why we need to set bandscount
and datatype
parameters.
In the example above we use tile_format=tif
so GDAL will fetch tif
tiles and keep the datatype from the data (which we assume to be float32)
Goal: Create a simple Raster tiler
requirements: titiler.core
\"\"\"Minimal COG tiler.\"\"\"\n\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory()\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n
"},{"location":"examples/code/mosaic_from_urls/","title":"Mosaic from COG urls","text":"Goal: Create a custom mosaic tiler which takes multiple URL as input
requirements: titiler.core | titiler.mosaic
1 - Create a custom Mosaic Backends
\"\"\"mosaic backends.\n\nThe goal is to build a minimalist Mosaic Backend which takes COG paths as input.\n\n>>> with MultiFilesBackend([\"cog1.tif\", \"cog2.tif\"]) as mosaic:\n img = mosaic.tile(1, 1, 1)\n\napp/backends.py\n\n\"\"\"\nfrom typing import Type, List, Tuple, Dict, Union\n\nimport attr\nfrom rio_tiler.io import BaseReader, COGReader, MultiBandReader, MultiBaseReader\nfrom rio_tiler.constants import WEB_MERCATOR_TMS, WGS84_CRS\nfrom rasterio.crs import CRS\nfrom morecantile import TileMatrixSet\n\nfrom cogeo_mosaic.backends.base import BaseBackend\nfrom cogeo_mosaic.mosaic import MosaicJSON\n\n\n@attr.s\nclass MultiFilesBackend(BaseBackend):\n\n input: List[str] = attr.ib()\n\n reader: Union[\n Type[BaseReader],\n Type[MultiBaseReader],\n Type[MultiBandReader],\n ] = attr.ib(default=COGReader)\n reader_options: Dict = attr.ib(factory=dict)\n\n geographic_crs: CRS = attr.ib(default=WGS84_CRS)\n\n tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)\n minzoom: int = attr.ib(default=0)\n maxzoom: int = attr.ib(default=30)\n\n # default values for bounds\n bounds: Tuple[float, float, float, float] = attr.ib(\n default=(-180, -90, 180, 90)\n )\n crs: CRS = attr.ib(init=False, default=WGS84_CRS)\n\n # mosaic_def is outside the __init__ method\n mosaic_def: MosaicJSON = attr.ib(init=False)\n\n _backend_name = \"MultiFiles\"\n\n def __attrs_post_init__(self):\n \"\"\"Post Init.\"\"\"\n # Construct a FAKE/Empty mosaicJSON\n # mosaic_def has to be defined.\n self.mosaic_def = MosaicJSON(\n mosaicjson=\"0.0.2\",\n name=\"it's fake but it's ok\",\n minzoom=self.minzoom,\n maxzoom=self.maxzoom,\n tiles=[] # we set `tiles` to an empty list.\n )\n\n def write(self, overwrite: bool = True):\n \"\"\"This method is not used but is required by the abstract class.\"\"\"\n pass\n\n def update(self):\n \"\"\"We overwrite the default method.\"\"\"\n pass\n\n def _read(self) -> MosaicJSON:\n \"\"\"This method is not used but is required by the abstract class.\"\"\"\n pass\n\n def assets_for_tile(self, x: int, y: int, z: int) -> List[str]:\n \"\"\"Retrieve assets for tile.\"\"\"\n return self.get_assets()\n\n def assets_for_point(self, lng: float, lat: float) -> List[str]:\n \"\"\"Retrieve assets for point.\"\"\"\n return self.get_assets()\n\n def get_assets(self) -> List[str]:\n \"\"\"assets are just files we give in path\"\"\"\n return self.input\n\n @property\n def _quadkeys(self) -> List[str]:\n return []\n
2 - Create endpoints
\"\"\"routes.\n\napp/routers.py\n\n\"\"\"\n\nfrom dataclasses import dataclass\nfrom typing import List\n\nfrom titiler.mosaic.factory import MosaicTilerFactory\nfrom fastapi import Query\n\nfrom .backends import MultiFilesBackend\n\n@dataclass\nclass MosaicTiler(MosaicTilerFactory):\n \"\"\"Custom MosaicTilerFactory.\n\n Note this is a really simple MosaicTiler Factory with only few endpoints.\n \"\"\"\n\n def register_routes(self):\n \"\"\"This Method register routes to the router. \"\"\"\n\n self.tile()\n self.tilejson()\n\n\ndef DatasetPathParams(url: str = Query(..., description=\"Dataset URL\")) -> List[str]:\n \"\"\"Create dataset path from args\"\"\"\n return url.split(\",\")\n\n\nmosaic = MosaicTiler(reader=MultiFilesBackend, path_dependency=DatasetPathParams)\n
3 - Create app and register our custom endpoints
\"\"\"app.\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\nfrom titiler.mosaic.errors import MOSAIC_STATUS_CODES\n\nfrom fastapi import FastAPI\n\nfrom .routers import mosaic\n\napp = FastAPI()\napp.include_router(mosaic.router)\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\nadd_exception_handlers(app, MOSAIC_STATUS_CODES)\n
$ uvicorn app:app --reload\n\n$ curl http://127.0.0.1:8000/tilejson.json?url=cog1.tif,cog2.tif\n
Gotcha
[-180, -90, 180, 90]
Goal: Create a dynamic tiler for Sentinel-2 (using AWS Public Dataset)
requirements: titiler.core, titiler.mosaic, rio-tiler-pds
Note: See developmentseed/titiler-pds for a end-to-end implementation
"},{"location":"examples/code/tiler_for_sentinel2/#sentinel-2","title":"Sentinel 2","text":"Thanks to Digital Earth Africa and in collaboration with Sinergise, Element 84, Amazon Web Services (AWS) and the Committee on Earth Observation Satellites (CEOS), Sentinel 2 (Level 2) data over Africa, usually stored as JPEG2000, has been translated to COG. More importantly, a STAC database and API has been set up.
www.digitalearthafrica.org/news/operational-and-ready-use-satellite-data-now-available-across-africa
The API is provided by @element84 and follows the latest specification: earth-search.aws.element84.com/v0
\"\"\"Sentinel 2 (COG) Tiler.\"\"\"\n\nfrom titiler.core.factory import MultiBandTilerFactory\nfrom titiler.core.dependencies import BandsExprParams\nfrom titiler.mosaic.factory import MosaicTilerFactory\n\nfrom rio_tiler_pds.sentinel.aws import S2COGReader\nfrom rio_tiler_pds.sentinel.utils import s2_sceneid_parser\n\nfrom fastapi import FastAPI, Query\n\n\ndef CustomPathParams(\n sceneid: str = Query(..., description=\"Sentinel 2 Sceneid.\")\n):\n \"\"\"Create dataset path from args\"\"\"\n assert s2_sceneid_parser(sceneid) # Makes sure the sceneid is valid\n return sceneid\n\n\napp = FastAPI()\n\nscene_tiler = MultiBandTilerFactory(reader=S2COGReader, path_dependency=CustomPathParams, router_prefix=\"scenes\")\napp.include_router(scene_tiler.router, prefix=\"/scenes\", tags=[\"scenes\"])\n\nmosaic_tiler = MosaicTilerFactory(\n router_prefix=\"mosaic\",\n dataset_reader=S2COGReader,\n layer_dependency=BandsExprParams,\n)\napp.include_router(mosaic_tiler.router, prefix=\"/mosaic\", tags=[\"mosaic\"])\n
"},{"location":"examples/code/tiler_for_sentinel2/#how-to","title":"How to","text":"Search for Data
import os\nimport json\nimport base64\nimport httpx\nimport datetime\nimport itertools\nimport urllib.parse\nimport pathlib\n\nfrom io import BytesIO\nfrom functools import partial\nfrom concurrent import futures\n\nfrom rasterio.plot import reshape_as_image\nfrom rasterio.features import bounds as featureBounds\n\n# Endpoint variables\ntitiler_endpoint = \"http://127.0.0.1:8000\"\nstac_endpoint = \"https://earth-search.aws.element84.com/v0/search\"\n\n# Make sure both are up\nassert httpx.get(f\"{titiler_endpoint}/docs\").status_code == 200\nassert httpx.get(stac_endpoint).status_code == 200\n\ngeojson = {\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [\n [\n [\n -2.83447265625,\n 4.12728532324537\n ],\n [\n 2.120361328125,\n 4.12728532324537\n ],\n [\n 2.120361328125,\n 8.254982704877875\n ],\n [\n -2.83447265625,\n 8.254982704877875\n ],\n [\n -2.83447265625,\n 4.12728532324537\n ]\n ]\n ]\n }\n }\n ]\n}\n\nbounds = featureBounds(geojson)\n\nstart = datetime.datetime.strptime(\"2019-01-01\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT00:00:00Z\")\nend = datetime.datetime.strptime(\"2019-12-11\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT23:59:59Z\")\n\n# POST body\nquery = {\n \"collections\": [\"sentinel-s2-l2a-cogs\"],\n \"datetime\": f\"{start}/{end}\",\n \"query\": {\n \"eo:cloud_cover\": {\n \"lt\": 3\n },\n \"sentinel:data_coverage\": {\n \"gt\": 10\n }\n },\n \"intersects\": geojson[\"features\"][0][\"geometry\"],\n \"limit\": 1000,\n \"fields\": {\n 'include': ['id', 'properties.datetime', 'properties.eo:cloud_cover'], # This will limit the size of returned body\n 'exclude': ['assets', 'links'] # This will limit the size of returned body\n },\n \"sortby\": [\n {\n \"field\": \"properties.eo:cloud_cover\",\n \"direction\": \"desc\"\n },\n ]\n}\n\n# POST Headers\nheaders = {\n \"Content-Type\": \"application/json\",\n \"Accept-Encoding\": \"gzip\",\n \"Accept\": \"application/geo+json\",\n}\n\ndata = httpx.post(stac_endpoint, headers=headers, json=query).json()\nprint(\"Results context:\")\nprint(data[\"context\"])\n\nsceneid = [f[\"id\"] for f in data[\"features\"]]\ncloudcover = [f[\"properties\"][\"eo:cloud_cover\"] for f in data[\"features\"]]\ndates = [f[\"properties\"][\"datetime\"][0:10] for f in data[\"features\"]]\n
Get TileJSON
# Fetch TileJSON\n# For this example we use the first `sceneid` returned from the STAC API\n# and we sent the Bands to B04,B03,B02 which are red,green,blue\ndata = httpx.get(f\"{titiler_endpoint}/scenes/WebMercatorQuad/tilejson.json?sceneid={sceneid[4]}&bands=B04&bands=B03&bands=B02&rescale=0,2000\").json()\nprint(data)\n
Mosaic
from cogeo_mosaic.backends import MosaicBackend\nfrom typing import Dict, List, Sequence, Optional\nfrom pygeos import polygons\nimport mercantile\n\n# Simple Mosaic\ndef custom_accessor(feature):\n \"\"\"Return feature identifier.\"\"\"\n return feature[\"id\"]\n\nwith MosaicBackend(\n \"stac+https://earth-search.aws.element84.com/v0/search\",\n query,\n minzoom=8,\n maxzoom=15,\n mosaic_options={\"accessor\": custom_accessor},\n) as mosaic:\n print(mosaic.metadata)\n mosaic_doc = mosaic.mosaic_def.dict(exclude_none=True)\n\n# Optimized Mosaic\ndef optimized_filter(\n tile: mercantile.Tile, # noqa\n dataset: Sequence[Dict],\n geoms: Sequence[polygons],\n minimum_tile_cover=None, # noqa\n tile_cover_sort=False, # noqa\n maximum_items_per_tile: Optional[int] = None,\n) -> List:\n \"\"\"Optimized filter that keeps only one item per grid ID.\"\"\"\n gridid: List[str] = []\n selected_dataset: List[Dict] = []\n\n for item in dataset:\n grid = item[\"id\"].split(\"_\")[1]\n if grid not in gridid:\n gridid.append(grid)\n selected_dataset.append(item)\n\n dataset = selected_dataset\n\n indices = list(range(len(dataset)))\n if maximum_items_per_tile:\n indices = indices[:maximum_items_per_tile]\n\n return [dataset[ind] for ind in indices]\n\n\nwith MosaicBackend(\n \"stac+https://earth-search.aws.element84.com/v0/search\",\n query,\n minzoom=8,\n maxzoom=14,\n mosaic_options={\"accessor\": custom_accessor, \"asset_filter\": optimized_filter},\n) as mosaic:\n print(mosaic.metadata)\n mosaic_doc = mosa\n\n# Write the mosaic\nmosaic_file = \"mymosaic.json.gz\"\nwith MosaicBackend(mosaic_file, mosaic_def=mosaic_doc) as mosaic:\n mosaic.write(overwrite=True)\n
Use the mosaic in titiler
mosaic = str(pathlib.Path(mosaic_file).absolute())\ndata = httpx.get(f\"{titiler_endpoint}/mosaic/WebMercatorQuad/tilejson.json?url=file:///{mosaic}&bands=B01&rescale=0,1000\").json()\nprint(data)\n
"},{"location":"examples/code/tiler_with_auth/","title":"Tiler with Auth","text":"Goal: Add simple token auth
requirements: titiler.core, python-jose[cryptography]
Learn more about security over FastAPI documentation
1 - Security settings (secret key)
\"\"\"Security Settings.\n\napp/settings.py\n\n\"\"\"\n\nfrom pydantic import BaseSettings\n\n\nclass AuthSettings(BaseSettings):\n \"\"\"Application settings\"\"\"\n\n # Create secret key using `openssl rand -hex 32`\n # example: \"09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7\"\n secret: str\n expires: int = 3600\n algorithm: str = \"HS256\"\n\n class Config:\n \"\"\"model config\"\"\"\n\n env_prefix = \"SECURITY_\"\n\n\nauth_config = AuthSettings()\n
2 - Create a Token Model
\"\"\"Models.\n\napp/models.py\n\n\"\"\"\n\nfrom datetime import datetime, timedelta\nfrom typing import List, Optional\n\nfrom jose import jwt\nfrom pydantic import BaseModel, Field, validator\n\nfrom .settings import auth_config\n\n# We add scopes - because we are fancy\navailables_scopes = [\"tiles:read\"]\n\n\nclass AccessToken(BaseModel):\n \"\"\"API Token info.\"\"\"\n\n sub: str = Field(..., alias=\"username\", regex=\"^[a-zA-Z0-9-_]{1,32}$\")\n scope: List = [\"tiles:read\"]\n iat: Optional[datetime] = None\n exp: Optional[datetime] = None\n groups: Optional[List[str]]\n\n @validator(\"iat\", pre=True, always=True)\n def set_creation_time(cls, v) -> datetime:\n \"\"\"Set token creation time (iat).\"\"\"\n return datetime.utcnow()\n\n @validator(\"exp\", always=True)\n def set_expiration_time(cls, v, values) -> datetime:\n \"\"\"Set token expiration time (iat).\"\"\"\n return values[\"iat\"] + timedelta(seconds=auth_config.expires)\n\n @validator(\"scope\", each_item=True)\n def valid_scopes(cls, v, values):\n \"\"\"Validate Scopes.\"\"\"\n v = v.lower()\n if v not in availables_scopes:\n raise ValueError(f\"Invalid scope: {v}\")\n return v.lower()\n\n class Config:\n \"\"\"Access Token Model config.\"\"\"\n\n extra = \"forbid\"\n\n @property\n def username(self) -> str:\n \"\"\"Return Username.\"\"\"\n return self.sub\n\n def __str__(self):\n \"\"\"Create jwt token string.\"\"\"\n return jwt.encode(\n self.dict(exclude_none=True),\n auth_config.secret,\n algorithm=auth_config.algorithm,\n )\n\n @classmethod\n def from_string(cls, token: str):\n \"\"\"Parse jwt token string.\"\"\"\n res = jwt.decode(token, auth_config.secret, algorithms=[auth_config.algorithm])\n user = res.pop(\"sub\")\n res[\"username\"] = user\n return cls(**res)\n
3 - Create a custom path dependency
The DatasetPathParams
will add 2 querystring parameter to our application: - url
: the dataset url (like in the regular titiler app) - access_token
: our token
parameter
\"\"\"Dependencies.\n\napp/dependencies.py\n\n\"\"\"\n\nfrom jose import JWTError\n\nfrom fastapi import HTTPException, Query, Security\nfrom fastapi.security.api_key import APIKeyQuery\n\nfrom .models import AccessToken\n\napi_key_query = APIKeyQuery(name=\"access_token\", auto_error=False)\n\n\n# Custom Dataset Path dependency\ndef DatasetPathParams(\n url: str = Query(..., description=\"Dataset URL\"),\n api_key_query: str = Security(api_key_query)\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n\n if not api_key_query:\n raise HTTPException(status_code=401, detail=\"Missing `access_token`\")\n\n try:\n AccessToken.from_string(api_key_query)\n except JWTError:\n raise HTTPException(status_code=401, detail=\"Invalid `access_token`\")\n\n return url\n
3b - Create a Token creation/read endpoint (Optional)
\"\"\"Tokens App.\n\napp/tokens.py\n\n\"\"\"\n\nfrom typing import Any, Dict\n\nfrom .models import AccessToken\n\nfrom fastapi import APIRouter, Query\n\nrouter = APIRouter()\n\n\n@router.post(r\"/create\", responses={200: {\"description\": \"Create a token\"}})\ndef create_token(body: AccessToken):\n \"\"\"create token.\"\"\"\n return {\"token\": str(body)}\n\n\n@router.get(r\"/create\", responses={200: {\"description\": \"Create a token\"}})\ndef get_token(\n username: str = Query(..., description=\"Username\"),\n scope: str = Query(None, description=\"Coma (,) delimited token scopes\"),\n):\n \"\"\"create token.\"\"\"\n params: Dict[str, Any] = {\"username\": username}\n if scope:\n params[\"scope\"] = scope.split(\",\")\n token = AccessToken(**params)\n return {\"token\": str(token)}\n
4 - Create the Tiler app with our custom DatasetPathParams
\"\"\"app\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\nfrom .dependencies import DatasetPathParams\n\napp = FastAPI(title=\"My simple app with auth\")\n\n# here we create a custom Tiler with out custom DatasetPathParams function\ncog = TilerFactory(path_dependency=DatasetPathParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\n# optional\nfrom . import tokens\napp.include_router(tokens.router)\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n
"},{"location":"examples/code/tiler_with_cache/","title":"Tiler with Cache","text":"Goal: Add a cache layer on top of the tiler
requirements: titiler.core, aiocache[redis]
Note: Use aioredis 1.3 because aiocache doesnt work with aioredis version 2.0
1 - Cache settings
\"\"\"settings.\n\napp/settings.py\n\n\"\"\"\n\nfrom pydantic import BaseSettings\nfrom typing import Optional\n\n\nclass CacheSettings(BaseSettings):\n \"\"\"Cache settings\"\"\"\n\n endpoint: Optional[str] = None\n ttl: int = 3600\n namespace: str = \"\"\n\n class Config:\n \"\"\"model config\"\"\"\n\n env_file = \".env\"\n env_prefix = \"CACHE_\"\n\n\ncache_setting = CacheSettings()\n
Env file example with redis URI
CACHE_ENDPOINT=redis://127.0.0.1:6379/0\n
2 - Cache plugin
Because aiocache.cached
doesn't support non-async method we have to create a custom cached
class
\"\"\"Cache Plugin.\n\napp/cache.py\n\n\"\"\"\n\nimport asyncio\nimport urllib\nfrom typing import Any, Dict\n\nimport aiocache\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.responses import Response\n\nfrom fastapi.dependencies.utils import is_coroutine_callable\n\nfrom .settings import cache_setting\n\n\nclass cached(aiocache.cached):\n \"\"\"Custom Cached Decorator.\"\"\"\n\n async def get_from_cache(self, key):\n try:\n value = await self.cache.get(key)\n if isinstance(value, Response):\n value.headers[\"X-Cache\"] = \"HIT\"\n return value\n except Exception:\n aiocache.logger.exception(\n \"Couldn't retrieve %s, unexpected error\", key\n )\n\n async def decorator(\n self,\n f,\n *args,\n cache_read=True,\n cache_write=True,\n aiocache_wait_for_write=True,\n **kwargs,\n ):\n key = self.get_cache_key(f, args, kwargs)\n\n if cache_read:\n value = await self.get_from_cache(key)\n if value is not None:\n return value\n\n # CUSTOM, we add support for non-async method\n if is_coroutine_callable(f):\n result = await f(*args, **kwargs)\n else:\n result = await run_in_threadpool(f, *args, **kwargs)\n\n if cache_write:\n if aiocache_wait_for_write:\n await self.set_in_cache(key, result)\n else:\n asyncio.ensure_future(self.set_in_cache(key, result))\n\n return result\n\n\ndef setup_cache():\n \"\"\"Setup aiocache.\"\"\"\n config: Dict[str, Any] = {\n 'cache': \"aiocache.SimpleMemoryCache\",\n 'serializer': {\n 'class': \"aiocache.serializers.PickleSerializer\"\n }\n }\n if cache_setting.ttl is not None:\n config[\"ttl\"] = cache_setting.ttl\n\n if cache_setting.endpoint:\n url = urllib.parse.urlparse(cache_setting.endpoint)\n ulr_config = dict(urllib.parse.parse_qsl(url.query))\n config.update(ulr_config)\n\n cache_class = aiocache.Cache.get_scheme_class(url.scheme)\n config.update(cache_class.parse_uri_path(url.path))\n config[\"endpoint\"] = url.hostname\n config[\"port\"] = str(url.port)\n\n # Add other configuration into config here, Example for namespace:\n \"\"\"\n if cache_setting.namespace != \"\":\n config[\"namespace\"] = cache_setting.namespace\n \"\"\"\n\n if url.password:\n config[\"password\"] = url.password\n\n if cache_class == aiocache.Cache.REDIS:\n config[\"cache\"] = \"aiocache.RedisCache\"\n elif cache_class == aiocache.Cache.MEMCACHED:\n config[\"cache\"] = \"aiocache.MemcachedCache\"\n\n aiocache.caches.set_config({\"default\": config})\n
3 - Write a custom minimal Tiler with Cache
\"\"\"routes.\n\napp/routes.py\n\"\"\"\nfrom dataclasses import dataclass\nfrom typing import Callable, Dict, Type, Literal, List, Tuple, Optional\nfrom urllib.parse import urlencode\n\nfrom fastapi import Depends, Path, Query\nfrom starlette.requests import Request\nfrom starlette.responses import Response\n\nfrom morecantile import TileMatrixSet\nfrom rio_tiler.io import BaseReader, Reader\n\nfrom titiler.core.factory import img_endpoint_params\nfrom titiler.core.factory import TilerFactory as TiTilerFactory\nfrom titiler.core.dependencies import RescalingParams\nfrom titiler.core.models.mapbox import TileJSON\nfrom titiler.core.resources.enums import ImageType\n\nfrom .cache import cached\n\n\n@dataclass\nclass TilerFactory(TiTilerFactory):\n\n reader: Type[BaseReader] = Reader\n\n def register_routes(self):\n \"\"\"This Method register routes to the router.\"\"\"\n\n @self.router.get(r\"/tiles/{z}/{x}/{y}\", **img_endpoint_params)\n @self.router.get(r\"/tiles/{z}/{x}/{y}.{format}\", **img_endpoint_params)\n @self.router.get(r\"/tiles/{z}/{x}/{y}@{scale}x\", **img_endpoint_params)\n @self.router.get(r\"/tiles/{z}/{x}/{y}@{scale}x.{format}\", **img_endpoint_params)\n @self.router.get(r\"/tiles/{tileMatrixSetId}/{z}/{x}/{y}\", **img_endpoint_params)\n @self.router.get(\n r\"/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}\", **img_endpoint_params\n )\n @self.router.get(\n r\"/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x\", **img_endpoint_params\n )\n @self.router.get(\n r\"/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}\",\n **img_endpoint_params,\n )\n # Add default cache config dictionary into cached alias.\n # Note: if alias is used, other arguments in cached will be ignored. Add other arguments into default dicttionary in setup_cache function.\n @cached(alias=\"default\")\n def tile(\n z: int = Path(..., ge=0, le=30, description=\"TMS tiles's zoom level\"),\n x: int = Path(..., description=\"TMS tiles's column\"),\n y: int = Path(..., description=\"TMS tiles's row\"),\n tileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query(\n self.default_tms,\n description=f\"TileMatrixSet Name (default: '{self.default_tms}')\",\n ),\n scale: int = Query(\n 1, gt=0, lt=4, description=\"Tile size scale. 1=256x256, 2=512x512...\"\n ),\n format: ImageType = Query(\n None, description=\"Output image type. Default is auto.\"\n ),\n src_path=Depends(self.path_dependency),\n layer_params=Depends(self.layer_dependency),\n dataset_params=Depends(self.dataset_dependency),\n buffer: Optional[float] = Query(\n None,\n gt=0,\n title=\"Tile buffer.\",\n description=\"Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).\",\n ),\n post_process=Depends(self.process_dependency),\n rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams),\n color_formula: Optional[str] = Query(\n None,\n title=\"Color Formula\",\n description=\"rio-color formula (info: https://github.com/mapbox/rio-color)\",\n ),\n colormap=Depends(self.colormap_dependency),\n render_params=Depends(self.render_dependency),\n reader_params=Depends(self.reader_dependency),\n ):\n \"\"\"Create map tile from a dataset.\"\"\"\n tms = self.supported_tms.get(tileMatrixSetId)\n\n with self.reader(src_path, tms=tms, **reader_params) as src_dst:\n image = src_dst.tile(\n x,\n y,\n z,\n tilesize=scale * 256,\n buffer=buffer,\n **layer_params,\n **dataset_params,\n )\n dst_colormap = getattr(src_dst, \"colormap\", None)\n\n\n if post_process:\n image = post_process(image)\n\n if rescale:\n image.rescale(rescale)\n\n if color_formula:\n image.apply_color_formula(color_formula)\n\n if cmap := colormap or dst_colormap:\n image = image.apply_colormap(cmap)\n\n if not format:\n format = ImageType.jpeg if image.mask.all() else ImageType.png\n\n content = image.render(\n img_format=format.driver,\n **format.profile,\n **render_params,\n )\n\n return Response(content, media_type=format.mediatype)\n\n @self.router.get(\n \"/tilejson.json\",\n response_model=TileJSON,\n responses={200: {\"description\": \"Return a tilejson\"}},\n response_model_exclude_none=True,\n )\n @self.router.get(\n \"/{tileMatrixSetId}/tilejson.json\",\n response_model=TileJSON,\n responses={200: {\"description\": \"Return a tilejson\"}},\n response_model_exclude_none=True,\n )\n @cached(alias=\"default\")\n def tilejson(\n request: Request,\n tileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query(\n self.default_tms,\n description=f\"TileMatrixSet Name (default: '{self.default_tms}')\",\n ),\n src_path=Depends(self.path_dependency),\n tile_format: Optional[ImageType] = Query(\n None, description=\"Output image type. Default is auto.\"\n ),\n tile_scale: int = Query(\n 1, gt=0, lt=4, description=\"Tile size scale. 1=256x256, 2=512x512...\"\n ),\n minzoom: Optional[int] = Query(\n None, description=\"Overwrite default minzoom.\"\n ),\n maxzoom: Optional[int] = Query(\n None, description=\"Overwrite default maxzoom.\"\n ),\n layer_params=Depends(self.layer_dependency), # noqa\n dataset_params=Depends(self.dataset_dependency), # noqa\n buffer: Optional[float] = Query( # noqa\n None,\n gt=0,\n title=\"Tile buffer.\",\n description=\"Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).\",\n ),\n post_process=Depends(self.process_dependency), # noqa\n rescale: Optional[List[Tuple[float, ...]]] = Depends(\n RescalingParams\n ), # noqa\n color_formula: Optional[str] = Query( # noqa\n None,\n title=\"Color Formula\",\n description=\"rio-color formula (info: https://github.com/mapbox/rio-color)\",\n ),\n colormap=Depends(self.colormap_dependency), # noqa\n render_params=Depends(self.render_dependency), # noqa\n reader_params=Depends(self.reader_dependency),\n ):\n \"\"\"Return TileJSON document for a dataset.\"\"\"\n route_params = {\n \"z\": \"{z}\",\n \"x\": \"{x}\",\n \"y\": \"{y}\",\n \"scale\": tile_scale,\n \"tileMatrixSetId\": tileMatrixSetId,\n }\n if tile_format:\n route_params[\"format\"] = tile_format.value\n\n tiles_url = self.url_for(request, \"tile\", **route_params)\n\n qs_key_to_remove = [\n \"tilematrixsetid\",\n \"tile_format\",\n \"tile_scale\",\n \"minzoom\",\n \"maxzoom\",\n ]\n qs = [\n (key, value)\n for (key, value) in request.query_params._list\n if key.lower() not in qs_key_to_remove\n ]\n if qs:\n tiles_url += f\"?{urlencode(qs)}\"\n\n tms = self.supported_tms.get(tileMatrixSetId)\n with self.reader(src_path, tms=tms, **reader_params) as src_dst:\n return {\n \"bounds\": src_dst.geographic_bounds,\n \"minzoom\": minzoom if minzoom is not None else src_dst.minzoom,\n \"maxzoom\": maxzoom if maxzoom is not None else src_dst.maxzoom,\n \"tiles\": [tiles_url],\n }\n\n # Register Map viewer\n self.map_viewer()\n\ncog = TilerFactory()\n
4 - Create the Tiler app with our custom DatasetPathParams
\"\"\"app\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\nfrom .cache import setup_cache\nfrom .routes import cog\n\napp = FastAPI(title=\"My simple app with cache\")\n\n# Setup Cache on Startup\napp.add_event_handler(\"startup\", setup_cache)\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n
"},{"location":"examples/code/tiler_with_custom_algorithm/","title":"Add custom algorithms","text":"Goal: add custom Algorithm to a tiler
requirements: titiler.core
1 - Create a custom algorithm and register it to the list of available algorithms
\"\"\"algos.\n\napp/algorithms.py\n\n\"\"\"\nfrom titiler.core.algorithm import BaseAlgorithm\nfrom titiler.core.algorithm import algorithms as default_algorithms\n\nfrom rio_tiler.models import ImageData\n\n\nclass Multiply(BaseAlgorithm):\n\n # Parameters\n factor: int # There is no default, which means calls to this algorithm without any parameter will fail\n\n # We don't set any metadata for this Algorithm\n\n def __call__(self, img: ImageData) -> ImageData:\n # Multiply image data bcy factor\n data = img.data * self.factor\n\n # Create output ImageData\n return ImageData(\n data,\n assets=img.assets,\n crs=img.crs,\n bounds=img.bounds,\n )\n\n# default_algorithms is a `titiler.core.algorithm.Algorithms` Object\nalgorithms = default_algorithms.register(\n {\n \"multiply\": Multiply,\n }\n)\n
2 - Create application and register endpoints
\"\"\"application.\n\napp/app.py\n\n\"\"\"\nfrom fastapi import FastAPI\nfrom titiler.core.factory import TilerFactory\n\nfrom .algorithms import algorithms\n\n\napp = FastAPI(title=\"My simple app with custom Algorithm\")\n\n# The Algorithms class (titiler.core.algorithm.algorithms) as a `dependency` property which return a process_dependency.\ntiler = TilerFactory(process_dependency=algorithms.dependency)\napp.include_router(tiler.router)\n
"},{"location":"examples/code/tiler_with_custom_colormap/","title":"Tiler with custom Colormap dependency","text":"Goal: Add a custom colormap dependency to allow user pass linear colormap
definition.
# https://colorbrewer2.org/#type=sequential&scheme=BuGn&n=3\ncmap = urlencode(\n {\n \"colormap\": json.dumps(\n {\n \"0\": \"#e5f5f9\",\n \"10\": \"#99d8c9\",\n \"255\": \"#2ca25f\",\n }\n )\n }\n)\nresponse = requests.get(\n f\"http://127.0.0.1:8000/cog/tiles/WebMercatorQuad/8/53/50.png?url=https://myurl.com/cog.tif&bidx=1&rescale=0,10000&{cmap}\"\n)\n
requirements: titiler.core matplotlib
1 - Create a custom ColorMapParams
dependency
\"\"\"dependencies.\n\napp/dependencies.py\n\n\"\"\"\n\nimport json\n\nfrom typing import Dict, Optional, Literal\nfrom typing_extensions import Annotated\n\nimport numpy\nimport matplotlib\nfrom rio_tiler.colormap import parse_color\nfrom rio_tiler.colormap import cmap as default_cmap\nfrom fastapi import HTTPException, Query\n\n\ndef ColorMapParams(\n colormap_name: Annotated[ # type: ignore\n Literal[tuple(default_cmap.list())],\n Query(description=\"Colormap name\"),\n ] = None,\n colormap: Annotated[\n str,\n Query(description=\"JSON encoded custom Colormap\"),\n ] = None,\n colormap_type: Annotated[\n Literal[\"explicit\", \"linear\"],\n Query(description=\"User input colormap type.\"),\n ] = \"explicit\",\n) -> Optional[Dict]:\n \"\"\"Colormap Dependency.\"\"\"\n if colormap_name:\n return default_cmap.get(colormap_name)\n\n if colormap:\n try:\n cm = json.loads(\n colormap,\n object_hook=lambda x: {int(k): parse_color(v) for k, v in x.items()},\n )\n except json.JSONDecodeError:\n raise HTTPException(\n status_code=400, detail=\"Could not parse the colormap value.\"\n )\n\n if colormap_type == \"linear\":\n # input colormap has to start from 0 to 255 ?\n cm = matplotlib.colors.LinearSegmentedColormap.from_list(\n 'custom',\n [\n (k / 255, matplotlib.colors.to_hex([v / 255 for v in rgba]))\n for (k, rgba) in cm.items()\n ],\n 256,\n )\n x = numpy.linspace(0, 1, 256)\n cmap_vals = cm(x)[:, :]\n cmap_uint8 = (cmap_vals * 255).astype('uint8')\n cm = {idx: value.tolist() for idx, value in enumerate(cmap_uint8)}\n\n return cm\n\n return None\n
2 - Create app and register our custom endpoints
\"\"\"app.\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\nfrom titiler.core.factory import TilerFactory\n\nfrom fastapi import FastAPI\n\nfrom .dependencies import ColorMapParams\n\napp = FastAPI(title=\"My simple app with custom TMS\")\n\ncog = TilerFactory(colormap_dependency=ColorMapParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n
"},{"location":"examples/code/tiler_with_custom_stac_validation/","title":"STAC endpoints with custom `/validate`","text":"Goal: Create a custom STAC endpoints with validation
requirements: titiler.core
&& jsonschema
\"\"\"FastAPI application.\"\"\"\n\nfrom fastapi import FastAPI\n\nfrom rio_tiler.io import STACReader\n\nfrom titiler.core.dependencies import DatasetPathParams\nfrom titiler.core.factory import MultiBaseTilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\n\n# STAC uses MultiBaseReader so we use MultiBaseTilerFactory to built the default endpoints\nstac = MultiBaseTilerFactory(reader=STACReader, router_prefix=\"stac\")\n\n\n# We add `/validate` to the router\n@stac.router.get(\"/validate\")\ndef stac_validate_get(src_path=Depends(DatasetPathParams)):\n \"\"\"STAC validation.\"\"\"\n with STACReader(src_path) as stac_src:\n return stac_src.item.validate()\n\n\n# Create FastAPI application\napp = FastAPI(title=\"My simple app with custom STAC endpoint\")\napp.include_router(stac.router, tags=[\"STAC\"])\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n
"},{"location":"examples/code/tiler_with_custom_tms/","title":"Tiler with custom TMS","text":"Goal: add custom TMS to a tiler
requirements: titiler.core
1 - Create custom TMS and custom endpoints
\"\"\"routes.\n\napp/routes.py\n\n\"\"\"\n\nfrom titiler.core.factory import TilerFactory, TMSFactory\nfrom morecantile import tms, TileMatrixSet\nfrom pyproj import CRS\n\n# 1. Create Custom TMS\nEPSG6933 = TileMatrixSet.custom(\n (-17357881.81713629, -7324184.56362408, 17357881.81713629, 7324184.56362408),\n CRS.from_epsg(6933),\n id=\"EPSG6933\",\n matrix_scale=[1, 1],\n)\n# 2. Register TMS\ntms = tms.register({EPSG6933.id:EPSG6933})\n\ntms_factory = TMSFactory(supported_tms=tms)\ncog_factory = TilerFactory(supported_tms=tms)\n
2 - Create app and register our custom endpoints
\"\"\"app.\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\nfrom .routes import cog_factory, tms_factory\n\napp = FastAPI(title=\"My simple app with custom TMS\")\n\napp.include_router(cog_factory.router, tags=[\"Cloud Optimized GeoTIFF\"])\napp.include_router(tms_factory.router, tags=[\"Tiling Schemes\"])\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n
"},{"location":"examples/code/working_with_signed_urls/","title":"Loading data with signed URLs","text":"ref: developmentseed/titiler?331
Goal: allow users to pass signed url or url containing query parameters (delimited with &
)
requirements: titiler.core
"},{"location":"examples/code/working_with_signed_urls/#what-why-how","title":"What / Why / How","text":"Passing a signed URL or a complex URL for a dataset is not supported by default in TiTiler because the parameters (delimited with &
) from the signed url conflict with the query parameters from the application itself. In order to allow signed url in the application there are two solutions:
The easiest way (from the application's point of view) to allow complex URLs is to allow an encoded url as an input parameter.
import base64\nurl = \"http://my.dataset.com/cog.tif?p=aaa&c&1234&t=4321\"\n\n# base64.b64encode(url.encode())\n>>> \"aHR0cDovL215LmRhdGFzZXQuY29tL2NvZy50aWY/cD1hYWEmYyYxMjM0JnQ9NDMyMQ==\"\n
When base64 encoded, the url is just a regular string and thus will be valid in the application.
\"\"\"Minimal COG tiler with Signed URL support.\"\"\"\n\nimport base64\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\n# Custom Path dependency which can `decode` a base64 url\ndef DatasetPathParams(\n url: str = Query(..., description=\"Dataset URL\"),\n base64_encoded: bool = Query(None)\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n if base64_encoded:\n url = base64.b64decode(url).decode()\n return url\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory(path_dependency=DatasetPathParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n
import base64\nfrom my_provider import signed_url\n\nmy_url = \"https://dataset.com/....\"\n\n# Get signed URL\nmy_signed_url = signed_url(my_url)\n\n# Encode the signed url using base64\nurl = base64.b64encode(my_signed_url.encode())\n\ninfo = request.get(f\"{titiler_endpoint}/info\", params={\"url\": url, signed_url: True})\n
"},{"location":"examples/code/working_with_signed_urls/#12-encode-only-the-url-params","title":"1.2 Encode only the url params","text":"\"\"\"Minimal COG tiler with Signed URL support.\"\"\"\n\nimport base64\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\n# Another solution is to pass only the query parameters encoded in base64\ndef DatasetPathParams(\n url: str = Query(..., description=\"Dataset URL\"),\n url_params: str = Query(\n None, description=\"Base64 encoded Query parameters to add to the dataset URL.\"\n ),\n) -> str:\n \"\"\"DatasetPath Params.\"\"\"\n if url_params:\n url += f\"?{b64decode(url_params).decode()}\"\n return url\n\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory(path_dependency=DatasetPathParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n
import base64\nfrom urllib.parse import urlparse\nfrom my_provider import signed_url\n\nmy_url = \"https://dataset.com/....\"\n\n# Get signed URL\nmy_signed_url = signed_url(my_url)\n\n# Extract the url parameters\nsigned_params = urlparse(my_signed_url).query\n\n# Encode the parameters using base64\nencoded_params = base64.b64encode(signed_params.encode())\n\ninfo = request.get(f\"{titiler_endpoint}/info\", params={\"url\": url, url_params: encoded_params})\n
"},{"location":"examples/code/working_with_signed_urls/#2-signing-url-in-the-application","title":"2. Signing URL in the application","text":"Another solution is to sign the URL directly in TiTiler.
\"\"\"Minimal COG tiler with Signed URL support.\"\"\"\n\nimport my_provider # e.g AWS, Google, ...\n\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\n# Custom Path dependency which will sign url\n# !!! You may want to add caching here to avoid to many call to the signing provider !!!\ndef DatasetPathParams(\n url: str = Query(..., description=\"Dataset URL\"),\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n # Use your provider library to sign the URL\n return my_provider.sign(url)\n\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory(path_dependency=DatasetPathParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n
import base64\nfrom my_provider import signed_url\n\nmy_url = \"https://dataset.com/....\"\n\ninfo = request.get(f\"{titiler_endpoint}/info\", params={\"url\": my_url})\n
"},{"location":"examples/notebooks/Working_with_Algorithm/","title":"Working With Algorithms","text":"In\u00a0[\u00a0]: Copied! import json\nimport httpx\n\nfrom folium import Map, TileLayer\nimport json import httpx from folium import Map, TileLayer In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. In\u00a0[\u00a0]: Copied!
url = \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\"\nurl = \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\" In\u00a0[\u00a0]: Copied!
# Fetch dataset Metadata\nr = httpx.get(\n f\"{titiler_endpoint}/cog/info\",\n params = {\n \"url\": url,\n }\n).json()\n\nprint(r)\n# Fetch dataset Metadata r = httpx.get( f\"{titiler_endpoint}/cog/info\", params = { \"url\": url, } ).json() print(r) In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"]\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Office f\u00e9d\u00e9ral de topographie swisstopo\"\n).add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] ) TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Office f\u00e9d\u00e9ral de topographie swisstopo\" ).add_to(m) m In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n # rio-tiler cannot rescale automatically the data when using a colormap\n \"rescale\": \"1615.812,2015.09448\",\n \"colormap_name\": \"terrain\",\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"]\n)\n\naod_layer = TileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Office f\u00e9d\u00e9ral de topographie swisstopo\"\n)\naod_layer.add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, # rio-tiler cannot rescale automatically the data when using a colormap \"rescale\": \"1615.812,2015.09448\", \"colormap_name\": \"terrain\", } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] ) aod_layer = TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Office f\u00e9d\u00e9ral de topographie swisstopo\" ) aod_layer.add_to(m) m In\u00a0[\u00a0]: Copied!
# Fetch algorithms\nprint(\"Available algorithm\")\nprint(list(httpx.get(f\"{titiler_endpoint}/algorithms\").json()))\nprint()\nprint(\"Metadata from `Hillshade` algorithm\")\nmeta = httpx.get(f\"{titiler_endpoint}/algorithms/hillshade\").json()\nprint(\"Inputs\")\nprint(meta[\"inputs\"])\nprint(\"Outputs\")\nprint(meta[\"outputs\"])\nprint(\"Parameters\")\nprint(meta[\"parameters\"])\n# Fetch algorithms print(\"Available algorithm\") print(list(httpx.get(f\"{titiler_endpoint}/algorithms\").json())) print() print(\"Metadata from `Hillshade` algorithm\") meta = httpx.get(f\"{titiler_endpoint}/algorithms/hillshade\").json() print(\"Inputs\") print(meta[\"inputs\"]) print(\"Outputs\") print(meta[\"outputs\"]) print(\"Parameters\") print(meta[\"parameters\"]) In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n \"algorithm\": \"hillshade\",\n # Hillshade algorithm use a 3pixel buffer so we need\n # to tell the tiler to apply a 3 pixel buffer around each tile\n \"buffer\": 3,\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"]\n)\n\naod_layer = TileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Yo!!\"\n)\naod_layer.add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, \"algorithm\": \"hillshade\", # Hillshade algorithm use a 3pixel buffer so we need # to tell the tiler to apply a 3 pixel buffer around each tile \"buffer\": 3, } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] ) aod_layer = TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Yo!!\" ) aod_layer.add_to(m) m In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n \"algorithm\": \"contours\",\n \"algorithm_params\": json.dumps(\n {\n \"increment\": 20, # contour line every 20 meters\n \"thickness\": 2, # 2m thickness\n \"minz\": 1600,\n \"maxz\": 2000\n }\n ),\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"]\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Yo!!\"\n).add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, \"algorithm\": \"contours\", \"algorithm_params\": json.dumps( { \"increment\": 20, # contour line every 20 meters \"thickness\": 2, # 2m thickness \"minz\": 1600, \"maxz\": 2000 } ), } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] ) TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Yo!!\" ).add_to(m) m In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n \"algorithm\": \"contours\",\n \"algorithm_params\": json.dumps(\n {\n \"increment\": 5, # contour line every 5 meters\n \"thickness\": 1, # 1m thickness\n \"minz\": 1600,\n \"maxz\": 2000\n }\n ),\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"]\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Yo!!\"\n).add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, \"algorithm\": \"contours\", \"algorithm_params\": json.dumps( { \"increment\": 5, # contour line every 5 meters \"thickness\": 1, # 1m thickness \"minz\": 1600, \"maxz\": 2000 } ), } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] ) TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Yo!!\" ).add_to(m) m In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_Algorithm/#working-with-algorithms","title":"Working With Algorithms\u00b6","text":"
For this demo we will use some elevation data from https://www.swisstopo.admin.ch/fr/geodata/height/alti3d.html dataset
"},{"location":"examples/notebooks/Working_with_Algorithm/#requirements","title":"Requirements\u00b6","text":"!pip install folium httpx
By default, the tiles will be rescaled from min/max from dataset statistics (1615.812 / 2015.09448)
"},{"location":"examples/notebooks/Working_with_Algorithm/#show-available-algorithms","title":"Show Available Algorithms\u00b6","text":""},{"location":"examples/notebooks/Working_with_Algorithm/#display-hillshade-tiles","title":"Display Hillshade Tiles\u00b6","text":""},{"location":"examples/notebooks/Working_with_Algorithm/#pass-parameters-to-the-algorithm","title":"Pass parameters to the algorithm\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/","title":"Working With COG - At Scale","text":"In\u00a0[\u00a0]: Copied!# Uncomment this line if you need to install the dependencies\n# !pip install rasterio boto3 folium requests tqdm\n# Uncomment this line if you need to install the dependencies # !pip install rasterio boto3 folium requests tqdm In\u00a0[\u00a0]: Copied!
import os\nimport datetime\nimport json\nimport urllib.parse\nfrom io import BytesIO\nfrom functools import partial\nfrom concurrent import futures\n\nimport httpx\nimport numpy\nfrom boto3.session import Session as boto3_session\n\nfrom rasterio.plot import reshape_as_image\nfrom rasterio.features import bounds as featureBounds\n\nfrom tqdm.notebook import tqdm\n\nfrom folium import Map, TileLayer, GeoJson\n\nimport matplotlib.pyplot as plt\nimport matplotlib.dates as mdates\nimport os import datetime import json import urllib.parse from io import BytesIO from functools import partial from concurrent import futures import httpx import numpy from boto3.session import Session as boto3_session from rasterio.plot import reshape_as_image from rasterio.features import bounds as featureBounds from tqdm.notebook import tqdm from folium import Map, TileLayer, GeoJson import matplotlib.pyplot as plt import matplotlib.dates as mdates In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. In\u00a0[\u00a0]: Copied!
# use geojson.io\ngeojson = {\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [\n [\n [\n -74.1796875,\n 45.18978009667531\n ],\n [\n -73.092041015625,\n 45.18978009667531\n ],\n [\n -73.092041015625,\n 46.00459325574482\n ],\n [\n -74.1796875,\n 46.00459325574482\n ],\n [\n -74.1796875,\n 45.18978009667531\n ]\n ]\n ]\n }\n }\n ]\n}\n\nbounds = featureBounds(geojson)\n# use geojson.io geojson = { \"type\": \"FeatureCollection\", \"features\": [ { \"type\": \"Feature\", \"properties\": {}, \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ -74.1796875, 45.18978009667531 ], [ -73.092041015625, 45.18978009667531 ], [ -73.092041015625, 46.00459325574482 ], [ -74.1796875, 46.00459325574482 ], [ -74.1796875, 45.18978009667531 ] ] ] } } ] } bounds = featureBounds(geojson) In\u00a0[\u00a0]: Copied!
m = Map(\n tiles=\"OpenStreetMap\",\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=6\n)\n\nGeoJson(geojson).add_to(m)\nm\nm = Map( tiles=\"OpenStreetMap\", location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=6 ) GeoJson(geojson).add_to(m) m In\u00a0[\u00a0]: Copied!
# To Be able to run this notebook you'll need to have AWS credential available in the environment\n\n# import os\n# os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\"\n# os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\"\n# To Be able to run this notebook you'll need to have AWS credential available in the environment # import os # os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\" # os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\" In\u00a0[\u00a0]: Copied!
session = boto3_session(region_name=\"us-west-2\")\nclient = session.client(\"s3\")\n\nbucket = \"omi-no2-nasa\" #https://registry.opendata.aws/omi-no2-nasa/\n\n\ndef list_objects(bucket, prefix):\n \"\"\"AWS s3 list objects.\"\"\"\n\n paginator = client.get_paginator('list_objects_v2')\n\n files = []\n for subset in paginator.paginate(Bucket=bucket, Prefix=prefix):\n files.extend(subset.get(\"Contents\", []))\n\n return files\n\nlist_files = list_objects(bucket, \"OMI-Aura_L3\")\n\nprint(\"Archive Size\")\nfiles = [r[\"Key\"] for r in list_files]\nprint(f\"Found {len(files)} OMI-NO2 files\")\n\nsize = sum([r[\"Size\"]/1000000. for r in list_files])\nprint(f\"Size of the archive: {size} Mo ({size / 1000} Go)\")\nsession = boto3_session(region_name=\"us-west-2\") client = session.client(\"s3\") bucket = \"omi-no2-nasa\" #https://registry.opendata.aws/omi-no2-nasa/ def list_objects(bucket, prefix): \"\"\"AWS s3 list objects.\"\"\" paginator = client.get_paginator('list_objects_v2') files = [] for subset in paginator.paginate(Bucket=bucket, Prefix=prefix): files.extend(subset.get(\"Contents\", [])) return files list_files = list_objects(bucket, \"OMI-Aura_L3\") print(\"Archive Size\") files = [r[\"Key\"] for r in list_files] print(f\"Found {len(files)} OMI-NO2 files\") size = sum([r[\"Size\"]/1000000. for r in list_files]) print(f\"Size of the archive: {size} Mo ({size / 1000} Go)\") In\u00a0[\u00a0]: Copied!
print(files[0:10])\nprint(files[0:10])
file name structure is \"OMI-Aura_L3-OMNO2d_{YEAR}m{MONTH:02}{DAY:02}...\"
We can then easily filter e.g
In\u00a0[\u00a0]: Copied!files_2019 = list(filter(lambda x: x.split(\"_\")[2][0:4] == \"2019\", files))\nprint(len(files_2019))\nfiles_2019 = list(filter(lambda x: x.split(\"_\")[2][0:4] == \"2019\", files)) print(len(files_2019)) In\u00a0[\u00a0]: Copied!
files_Oct5 = list(filter(lambda x: (x.split(\"_\")[2][5:7] == \"10\") & (x.split(\"_\")[2][7:9] == \"05\"), files))\nprint(len(files_Oct5))\nprint(files_Oct5)\nfiles_Oct5 = list(filter(lambda x: (x.split(\"_\")[2][5:7] == \"10\") & (x.split(\"_\")[2][7:9] == \"05\"), files)) print(len(files_Oct5)) print(files_Oct5) In\u00a0[\u00a0]: Copied!
def _url(src_path):\n return f\"s3://omi-no2-nasa/{src_path}\"\ndef _url(src_path): return f\"s3://omi-no2-nasa/{src_path}\" In\u00a0[\u00a0]: Copied!
# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32)\n\nr = httpx.get(\n f\"{titiler_endpoint}/cog/statistics\",\n params = {\n \"url\": _url(files[0])\n }\n).json()\n\nprint(json.dumps(r, indent=4))\n# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32) r = httpx.get( f\"{titiler_endpoint}/cog/statistics\", params = { \"url\": _url(files[0]) } ).json() print(json.dumps(r, indent=4)) In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": _url(files[2]),\n \"rescale\": \"0,3000000000000000\",\n \"colormap_name\": \"viridis\",\n }\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=6\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"NASA\"\n).add_to(m)\n\nGeoJson(geojson, style_function=lambda feature: {\"fill\": False, \"color\": \"red\"}).add_to(m)\n\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": _url(files[2]), \"rescale\": \"0,3000000000000000\", \"colormap_name\": \"viridis\", } ).json() m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=6 ) TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"NASA\" ).add_to(m) GeoJson(geojson, style_function=lambda feature: {\"fill\": False, \"color\": \"red\"}).add_to(m) m In\u00a0[\u00a0]: Copied!
def _stats(data, mask):\n arr = numpy.ma.array(data)\n arr.mask = mask == 0\n return arr.min().item(), arr.max().item(), arr.mean().item(), arr.std().item()\n\n\nxmin, ymin, xmax, ymax = bounds\n\ndef fetch_bbox(file):\n url = f\"{titiler_endpoint}/cog/bbox/{xmin},{ymin},{xmax},{ymax}.npy\"\n params = {\n \"url\": _url(file),\n \"bidx\": \"1\",\n \"max_size\": 128,\n }\n r = httpx.get(url, params=params)\n data = numpy.load(BytesIO(r.content))\n s = _stats(data[0:-1], data[-1])\n return (\n _stats(data[0:-1], data[-1]),\n datetime.datetime.strptime(file.split(\"_\")[2].replace(\"m\", \"\"), \"%Y%m%d\"),\n )\n\n# small tool to filter invalid response from the API\ndef _filter_futures(tasks):\n for future in tasks:\n try:\n yield future.result()\n except Exception:\n pass\ndef _stats(data, mask): arr = numpy.ma.array(data) arr.mask = mask == 0 return arr.min().item(), arr.max().item(), arr.mean().item(), arr.std().item() xmin, ymin, xmax, ymax = bounds def fetch_bbox(file): url = f\"{titiler_endpoint}/cog/bbox/{xmin},{ymin},{xmax},{ymax}.npy\" params = { \"url\": _url(file), \"bidx\": \"1\", \"max_size\": 128, } r = httpx.get(url, params=params) data = numpy.load(BytesIO(r.content)) s = _stats(data[0:-1], data[-1]) return ( _stats(data[0:-1], data[-1]), datetime.datetime.strptime(file.split(\"_\")[2].replace(\"m\", \"\"), \"%Y%m%d\"), ) # small tool to filter invalid response from the API def _filter_futures(tasks): for future in tasks: try: yield future.result() except Exception: pass In\u00a0[\u00a0]: Copied!
# Every 15 of each month for all the years\nfiles_15 = list(filter(lambda x: (x.split(\"_\")[2][7:9] == \"15\"), files))\n# Every 15 of each month for all the years files_15 = list(filter(lambda x: (x.split(\"_\")[2][7:9] == \"15\"), files)) In\u00a0[\u00a0]: Copied!
with futures.ThreadPoolExecutor(max_workers=10) as executor:\n future_work = [\n executor.submit(fetch_bbox, file) for file in files_15\n ]\n\n for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n pass\n\nvalues, dates = zip(*list(_filter_futures(future_work)))\n\nmax_values = [\n v[1]\n for v in values\n]\n\nfig, ax1 = plt.subplots(dpi=300)\nfig.autofmt_xdate()\n\nax1.plot(dates, max_values, label=\"No2\")\nax1.xaxis.set_major_locator(mdates.YearLocator(1,7))\n\nax1.set_xlabel(\"Dates\")\nax1.set_ylabel(\"No2\")\n\nax1.legend()\nwith futures.ThreadPoolExecutor(max_workers=10) as executor: future_work = [ executor.submit(fetch_bbox, file) for file in files_15 ] for f in tqdm(futures.as_completed(future_work), total=len(future_work)): pass values, dates = zip(*list(_filter_futures(future_work))) max_values = [ v[1] for v in values ] fig, ax1 = plt.subplots(dpi=300) fig.autofmt_xdate() ax1.plot(dates, max_values, label=\"No2\") ax1.xaxis.set_major_locator(mdates.YearLocator(1,7)) ax1.set_xlabel(\"Dates\") ax1.set_ylabel(\"No2\") ax1.legend() In\u00a0[\u00a0]: Copied!
with futures.ThreadPoolExecutor(max_workers=50) as executor:\n future_work = [\n executor.submit(fetch_bbox, file) for file in files\n ]\n\n for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n pass\n\nvalues, dates = zip(*list(_filter_futures(future_work)))\n\nmax_values = [\n v[1]\n for v in values\n]\n\nfig, ax1 = plt.subplots(dpi=150)\nfig.autofmt_xdate()\n\nax1.plot(dates, max_values, label=\"No2\")\nax1.xaxis.set_major_locator(mdates.YearLocator())\n\nax1.set_xlabel(\"Dates\")\nax1.set_ylabel(\"No2\")\n\nax1.legend()\nwith futures.ThreadPoolExecutor(max_workers=50) as executor: future_work = [ executor.submit(fetch_bbox, file) for file in files ] for f in tqdm(futures.as_completed(future_work), total=len(future_work)): pass values, dates = zip(*list(_filter_futures(future_work))) max_values = [ v[1] for v in values ] fig, ax1 = plt.subplots(dpi=150) fig.autofmt_xdate() ax1.plot(dates, max_values, label=\"No2\") ax1.xaxis.set_major_locator(mdates.YearLocator()) ax1.set_xlabel(\"Dates\") ax1.set_ylabel(\"No2\") ax1.legend() In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#working-with-cog-at-scale","title":"Working With COG - At Scale\u00b6","text":"
For this demo we will use the new Ozone Monitoring Instrument (OMI) / Aura NO2 Tropospheric Column Density
dataset hosted on AWS PDS: https://registry.opendata.aws/omi-no2-nasa/
Requirement: AWS credentials
"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#requirements","title":"Requirements\u00b6","text":"!pip install rasterio boto3 folium httpx tqdm
{endpoint}/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}?url={cog}&{otherquery params}
{endpoint}/cog/bbox/{minx},{miny},{maxx},{maxy}.{format}?url={cog}&{otherquery params}
{endpoint}/cog/point/{minx},{miny}?url={cog}&{otherquery params}
# Uncomment this line if you need to install the dependencies\n# !pip install folium httpx\n# Uncomment this line if you need to install the dependencies # !pip install folium httpx In\u00a0[\u00a0]: Copied!
import json\n\nimport httpx\n\nfrom folium import Map, TileLayer\n\n%matplotlib inline\nimport json import httpx from folium import Map, TileLayer %matplotlib inline In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. url = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\" In\u00a0[\u00a0]: Copied!
# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32)\nr = httpx.get(\n f\"{titiler_endpoint}/cog/info\",\n params = {\n \"url\": url,\n }\n).json()\n\nbounds = r[\"bounds\"]\nprint(r)\n# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32) r = httpx.get( f\"{titiler_endpoint}/cog/info\", params = { \"url\": url, } ).json() bounds = r[\"bounds\"] print(r) In\u00a0[\u00a0]: Copied!
# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32)\nr = httpx.get(\n f\"{titiler_endpoint}/cog/statistics\",\n params = {\n \"url\": url,\n }\n).json()\n\nprint(json.dumps(r, indent=4))\n# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32) r = httpx.get( f\"{titiler_endpoint}/cog/statistics\", params = { \"url\": url, } ).json() print(json.dumps(r, indent=4)) In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n }\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=13\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"DigitalGlobe OpenData\"\n).add_to(m)\n\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, } ).json() m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=13 ) TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"DigitalGlobe OpenData\" ).add_to(m) m In\u00a0[\u00a0]: Copied!
url = \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\"\n\n# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32)\nr = httpx.get(\n f\"{titiler_endpoint}/cog/info\",\n params = {\n \"url\": url,\n }\n).json()\n\nprint(r)\nprint(\"Data is of type:\", r[\"dtype\"])\n\n# This dataset has statistics metadata\nminv, maxv = r[\"band_metadata\"][0][1][\"STATISTICS_MINIMUM\"], r[\"band_metadata\"][0][1][\"STATISTICS_MAXIMUM\"]\nprint(\"With values from \", minv, \"to \", maxv)\nurl = \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\" # Fetch File Metadata to get min/max rescaling values (because the file is stored as float32) r = httpx.get( f\"{titiler_endpoint}/cog/info\", params = { \"url\": url, } ).json() print(r) print(\"Data is of type:\", r[\"dtype\"]) # This dataset has statistics metadata minv, maxv = r[\"band_metadata\"][0][1][\"STATISTICS_MINIMUM\"], r[\"band_metadata\"][0][1][\"STATISTICS_MAXIMUM\"] print(\"With values from \", minv, \"to \", maxv) In\u00a0[\u00a0]: Copied!
# We could get the min/max values using the statistics endpoint\nr = httpx.get(\n f\"{titiler_endpoint}/cog/statistics\",\n params = {\n \"url\": url,\n }\n).json()\n\nprint(json.dumps(r[\"b1\"], indent=4))\n# We could get the min/max values using the statistics endpoint r = httpx.get( f\"{titiler_endpoint}/cog/statistics\", params = { \"url\": url, } ).json() print(json.dumps(r[\"b1\"], indent=4)) In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"] + 1\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Swisstopo\"\n).add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] + 1 ) TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Swisstopo\" ).add_to(m) m
Apply ColorMap
Now that the data is rescaled to byte values (0 -> 255) we can apply a colormap
In\u00a0[\u00a0]: Copied!r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n \"rescale\": f\"{minv},{maxv}\",\n \"colormap_name\": \"terrain\"\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"] + 1\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Swisstopo\"\n).add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, \"rescale\": f\"{minv},{maxv}\", \"colormap_name\": \"terrain\" } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] + 1 ) TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Swisstopo\" ).add_to(m) m
Apply non-linear colormap (intervals)
see https://cogeotiff.github.io/rio-tiler/colormap/#intervals-colormaps
In\u00a0[\u00a0]: Copied!import json\n\ncmap = json.dumps(\n [\n # ([min, max], [r, g, b, a])\n ([0, 1500], [255,255,204, 255]),\n ([1500, 1700], [161,218,180, 255]),\n ([1700, 1900], [65,182,196, 255]),\n ([1900, 2000], [44,127,184, 255]),\n ([2000, 3000], [37,52,148, 255]),\n ]\n)\n# https://colorbrewer2.org/#type=sequential&scheme=YlGnBu&n=5\n\nr = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url,\n \"colormap\": cmap\n }\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"] + 1\n)\n\naod_layer = TileLayer(\n tiles=r[\"tiles\"][0],\n opacity=1,\n attr=\"Swisstopo\"\n)\naod_layer.add_to(m)\nm\nimport json cmap = json.dumps( [ # ([min, max], [r, g, b, a]) ([0, 1500], [255,255,204, 255]), ([1500, 1700], [161,218,180, 255]), ([1700, 1900], [65,182,196, 255]), ([1900, 2000], [44,127,184, 255]), ([2000, 3000], [37,52,148, 255]), ] ) # https://colorbrewer2.org/#type=sequential&scheme=YlGnBu&n=5 r = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = { \"url\": url, \"colormap\": cmap } ).json() bounds = r[\"bounds\"] m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=r[\"minzoom\"] + 1 ) aod_layer = TileLayer( tiles=r[\"tiles\"][0], opacity=1, attr=\"Swisstopo\" ) aod_layer.add_to(m) m In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#working-with-cog","title":"Working With COG\u00b6","text":"
For this demo we will use the new DigitalGlobe OpenData
dataset https://www.digitalglobe.com/ecosystem/open-data
pip install folium httpx
Note: By default if the metadata has min/max
statistics, titiler will use those to rescale the data
# Uncomment this line if you need to install the dependencies\n#!pip install rasterio folium tqdm httpx rio-tiler geojson_pydantic cogeo-mosaic\n# Uncomment this line if you need to install the dependencies #!pip install rasterio folium tqdm httpx rio-tiler geojson_pydantic cogeo-mosaic In\u00a0[\u00a0]: Copied!
import os\nimport json\nimport rasterio\nimport httpx\nfrom boto3.session import Session as boto3_session\n\nfrom concurrent import futures\nfrom rio_tiler.io import COGReader\nfrom rasterio.features import bounds as featureBounds\n\nfrom folium import Map, TileLayer, GeoJson\nimport os import json import rasterio import httpx from boto3.session import Session as boto3_session from concurrent import futures from rio_tiler.io import COGReader from rasterio.features import bounds as featureBounds from folium import Map, TileLayer, GeoJson In\u00a0[\u00a0]: Copied!
# To Be able to run this notebook you'll need to have AWS credential available in the environment\n\n# import os\n# os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\"\n# os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\"\n# To Be able to run this notebook you'll need to have AWS credential available in the environment # import os # os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\" # os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\" In\u00a0[\u00a0]: Copied!
session = boto3_session(region_name=\"us-west-2\")\nclient = session.client(\"s3\")\n\nbucket = \"noaa-eri-pds\" #https://registry.opendata.aws/omi-no2-nasa/\n\n\ndef list_objects(bucket, prefix):\n \"\"\"AWS s3 list objects.\"\"\"\n\n paginator = client.get_paginator('list_objects_v2')\n\n files = []\n for subset in paginator.paginate(Bucket=bucket, Prefix=prefix):\n files.extend(subset.get(\"Contents\", []))\n\n return [r[\"Key\"] for r in files]\n\nfiles = list_objects(bucket, \"2020_Nashville_Tornado/20200307a_RGB\")\nfiles = [f\"s3://{bucket}/{f}\" for f in files if f.endswith(\".tif\")]\n\nprint(f\"Number of GeoTIFF: {len(files)}\")\nsession = boto3_session(region_name=\"us-west-2\") client = session.client(\"s3\") bucket = \"noaa-eri-pds\" #https://registry.opendata.aws/omi-no2-nasa/ def list_objects(bucket, prefix): \"\"\"AWS s3 list objects.\"\"\" paginator = client.get_paginator('list_objects_v2') files = [] for subset in paginator.paginate(Bucket=bucket, Prefix=prefix): files.extend(subset.get(\"Contents\", [])) return [r[\"Key\"] for r in files] files = list_objects(bucket, \"2020_Nashville_Tornado/20200307a_RGB\") files = [f\"s3://{bucket}/{f}\" for f in files if f.endswith(\".tif\")] print(f\"Number of GeoTIFF: {len(files)}\") In\u00a0[\u00a0]: Copied!
print(files)\nprint(files) In\u00a0[\u00a0]: Copied!
# We can derive the `bbox` from the filename\n# s3://noaa-eri-pds/2020_Nashville_Tornado/20200307a_RGB/20200307aC0870130w361200n.tif\n# -> 20200307aC0870130w361200n.tif\n# -> 20200307aC \"0870130w\" \"361200n\" .tif\n# -> 0870130w -> 87.025 (West)\n# -> 361200n -> 36.2 (Top)\n# We also know each files cover ~0.025x~0.025 degrees\n\nimport re\nfrom geojson_pydantic.features import Feature\nfrom geojson_pydantic.geometries import Polygon\n\ndef dms_to_degree(v: str) -> float:\n \"\"\"convert degree minute second to decimal degrees.\n\n '0870130w' -> 87.025\n \"\"\"\n deg = int(v[0:3])\n minutes = int(v[3:5])\n seconds = int(v[5:7])\n direction = v[-1].upper()\n return (float(deg) + float(minutes)/60 + float(seconds)/(60*60)) * (-1 if direction in ['W', 'S'] else 1)\n\ndef fname_to_feature(src_path: str) -> Feature:\n bname = os.path.basename(src_path)\n lon_dms = bname[10:18]\n lat_dms = bname[18:25]\n\n lon = dms_to_degree(lon_dms)\n lat = dms_to_degree(\"0\" + lat_dms)\n\n return Feature(\n geometry=Polygon.from_bounds(\n lon, lat - 0.025, lon + 0.025, lat\n ),\n properties={\n \"path\": src_path,\n }\n )\nfeatures = [\n fname_to_feature(f).dict(exclude_none=True) for f in files\n]\n\n# OR We could use Rasterio/rio-tiler\n\n# def worker(src_path: str) -> Feature:\n# try:\n# with COGReader(src_path) as cog:\n# wgs_bounds = cog.geographic_bounds\n# except:\n# return {}\n#\n# return Feature(\n# geometry=Polygon.from_bounds(*wgs_bounds),\n# properties={\n# \"path\": src_path,\n# }\n# )\n#\n# with futures.ThreadPoolExecutor(max_workers=20) as executor:\n# features = [r.dict(exclude_none=True) for r in executor.map(worker, files) if r]\n# We can derive the `bbox` from the filename # s3://noaa-eri-pds/2020_Nashville_Tornado/20200307a_RGB/20200307aC0870130w361200n.tif # -> 20200307aC0870130w361200n.tif # -> 20200307aC \"0870130w\" \"361200n\" .tif # -> 0870130w -> 87.025 (West) # -> 361200n -> 36.2 (Top) # We also know each files cover ~0.025x~0.025 degrees import re from geojson_pydantic.features import Feature from geojson_pydantic.geometries import Polygon def dms_to_degree(v: str) -> float: \"\"\"convert degree minute second to decimal degrees. '0870130w' -> 87.025 \"\"\" deg = int(v[0:3]) minutes = int(v[3:5]) seconds = int(v[5:7]) direction = v[-1].upper() return (float(deg) + float(minutes)/60 + float(seconds)/(60*60)) * (-1 if direction in ['W', 'S'] else 1) def fname_to_feature(src_path: str) -> Feature: bname = os.path.basename(src_path) lon_dms = bname[10:18] lat_dms = bname[18:25] lon = dms_to_degree(lon_dms) lat = dms_to_degree(\"0\" + lat_dms) return Feature( geometry=Polygon.from_bounds( lon, lat - 0.025, lon + 0.025, lat ), properties={ \"path\": src_path, } ) features = [ fname_to_feature(f).dict(exclude_none=True) for f in files ] # OR We could use Rasterio/rio-tiler # def worker(src_path: str) -> Feature: # try: # with COGReader(src_path) as cog: # wgs_bounds = cog.geographic_bounds # except: # return {} # # return Feature( # geometry=Polygon.from_bounds(*wgs_bounds), # properties={ # \"path\": src_path, # } # ) # # with futures.ThreadPoolExecutor(max_workers=20) as executor: # features = [r.dict(exclude_none=True) for r in executor.map(worker, files) if r] In\u00a0[\u00a0]: Copied!
geojson = {'type': 'FeatureCollection', 'features': features}\n\nbounds = featureBounds(geojson)\n\nm = Map(\n tiles=\"OpenStreetMap\",\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=6\n)\n\ngeo_json = GeoJson(\n data=geojson,\n style_function=lambda x: {\n 'opacity': 1, 'dashArray': '1', 'fillOpacity': 0, 'weight': 1\n },\n)\ngeo_json.add_to(m)\nm\ngeojson = {'type': 'FeatureCollection', 'features': features} bounds = featureBounds(geojson) m = Map( tiles=\"OpenStreetMap\", location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=6 ) geo_json = GeoJson( data=geojson, style_function=lambda x: { 'opacity': 1, 'dashArray': '1', 'fillOpacity': 0, 'weight': 1 }, ) geo_json.add_to(m) m In\u00a0[\u00a0]: Copied!
from rio_tiler.io import COGReader\nfrom cogeo_mosaic.mosaic import MosaicJSON\nfrom cogeo_mosaic.backends import MosaicBackend\n\nwith COGReader(files[0]) as cog:\n info = cog.info()\n print(info.minzoom)\n print(info.maxzoom)\nfrom rio_tiler.io import COGReader from cogeo_mosaic.mosaic import MosaicJSON from cogeo_mosaic.backends import MosaicBackend with COGReader(files[0]) as cog: info = cog.info() print(info.minzoom) print(info.maxzoom) In\u00a0[\u00a0]: Copied!
# We are creating the mosaicJSON using the features we created earlier\n# by default MosaicJSON.from_feature will look in feature.properties.path to get the path of the dataset\nmosaicdata = MosaicJSON.from_features(features, minzoom=info.minzoom, maxzoom=info.maxzoom)\nwith MosaicBackend(\"NOAA_Nashville_Tornado.json.gz\", mosaic_def=mosaicdata) as mosaic:\n mosaic.write(overwrite=True)\n print(mosaic.info())\n# We are creating the mosaicJSON using the features we created earlier # by default MosaicJSON.from_feature will look in feature.properties.path to get the path of the dataset mosaicdata = MosaicJSON.from_features(features, minzoom=info.minzoom, maxzoom=info.maxzoom) with MosaicBackend(\"NOAA_Nashville_Tornado.json.gz\", mosaic_def=mosaicdata) as mosaic: mosaic.write(overwrite=True) print(mosaic.info()) In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n\nr = httpx.get(\n f\"{titiler_endpoint}/mosaicjson/WebMercatorQuad/tilejson.json\",\n params={\n # For this demo we are use the same mosaic but stored on the web\n \"url\": \"https://gist.githubusercontent.com/vincentsarago/c6ace3ccd29a82a4a5531693bbcd61fc/raw/e0d0174a64a9acd2fb820f2c65b1830aab80f52b/NOAA_Nashville_Tornado.json\"\n }\n).json()\nprint(r)\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=13\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"NOAA\"\n)\n\ngeo_json = GeoJson(\n data=geojson,\n style_function=lambda x: {\n 'opacity': 1, 'dashArray': '1', 'fillOpacity': 0, 'weight': 1\n },\n)\ntiles.add_to(m)\ngeo_json.add_to(m)\nm\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. r = httpx.get( f\"{titiler_endpoint}/mosaicjson/WebMercatorQuad/tilejson.json\", params={ # For this demo we are use the same mosaic but stored on the web \"url\": \"https://gist.githubusercontent.com/vincentsarago/c6ace3ccd29a82a4a5531693bbcd61fc/raw/e0d0174a64a9acd2fb820f2c65b1830aab80f52b/NOAA_Nashville_Tornado.json\" } ).json() print(r) m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=13 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"NOAA\" ) geo_json = GeoJson( data=geojson, style_function=lambda x: { 'opacity': 1, 'dashArray': '1', 'fillOpacity': 0, 'weight': 1 }, ) tiles.add_to(m) geo_json.add_to(m) m In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_MosaicJSON/#working-with-mosaicjson","title":"Working With MosaicJSON\u00b6","text":""},{"location":"examples/notebooks/Working_with_MosaicJSON/#mosaicjson","title":"MosaicJSON\u00b6","text":"
MosaicJSON is a specification created by DevelopmentSeed which aims to be an open standard for representing metadata about a mosaic of Cloud-Optimized GeoTIFF (COG) files.
MosaicJSON can be seen as a Virtual raster (see GDAL's VRT) enabling spatial and temporal processing for a list of Cloud-Optimized GeoTIFF.
Ref:https://github.com/developmentseed/mosaicjson-spec
"},{"location":"examples/notebooks/Working_with_MosaicJSON/#data","title":"Data\u00b6","text":"For this demo, we are going to use CloudOptimized GeoTIFF from NOAA/Emergency Response Imagery: https://registry.opendata.aws/noaa-eri/
Requirement: AWS credentials
"},{"location":"examples/notebooks/Working_with_MosaicJSON/#endpoint","title":"Endpoint\u00b6","text":"By default, TiTiler has mosaicjson
endpoints.
Docs: https://titiler.xyz/api.html#/MosaicJSON
"},{"location":"examples/notebooks/Working_with_MosaicJSON/#requirements","title":"Requirements\u00b6","text":"To be able to run this notebook you'll need the following requirements:
pip install rasterio folium tqdm httpx rio-tiler geojson_pydantic cogeo-mosaic
Read each file geo metadata
"},{"location":"examples/notebooks/Working_with_MosaicJSON/#5-create-mosaic","title":"5. Create Mosaic\u00b6","text":""},{"location":"examples/notebooks/Working_with_NumpyTile/","title":"NumpyTile","text":"In\u00a0[\u00a0]: Copied!# Uncomment this line if you need to install the dependencies\n# !pip install numpy mercantile\n# Uncomment this line if you need to install the dependencies # !pip install numpy mercantile In\u00a0[\u00a0]: Copied!
import httpx\nimport mercantile\nfrom io import BytesIO\nimport numpy\n\n%pylab inline\nimport httpx import mercantile from io import BytesIO import numpy %pylab inline In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. url = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\" In\u00a0[\u00a0]: Copied!
r = httpx.get(f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json?url={url}\").json()\nprint(r)\nr = httpx.get(f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json?url={url}\").json() print(r) In\u00a0[\u00a0]: Copied!
# Get a list of tiles for minzoom + 2\n\ntiles = list(mercantile.tiles(*r[\"bounds\"], r[\"minzoom\"] + 2))\n# Get a list of tiles for minzoom + 2 tiles = list(mercantile.tiles(*r[\"bounds\"], r[\"minzoom\"] + 2)) In\u00a0[\u00a0]: Copied!
# Call TiTiler endpoint using the first tile\n\ntile = tiles[0]\nr = httpx.get(f\"{titiler_endpoint}/cog/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}.npy?url={url}\")\n# Call TiTiler endpoint using the first tile tile = tiles[0] r = httpx.get(f\"{titiler_endpoint}/cog/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}.npy?url={url}\") In\u00a0[\u00a0]: Copied!
# Load result using numpy.load\n\narr = numpy.load(BytesIO(r.content))\nprint(type(arr))\nprint(arr.shape)\n# Load result using numpy.load arr = numpy.load(BytesIO(r.content)) print(type(arr)) print(arr.shape) In\u00a0[\u00a0]: Copied!
# By default we put the data and the mask in the same array\ntile, mask = arr[0:-1], arr[-1]\n# By default we put the data and the mask in the same array tile, mask = arr[0:-1], arr[-1] In\u00a0[\u00a0]: Copied!
print(tile.shape)\nprint(tile.shape) In\u00a0[\u00a0]: Copied!
print(mask.shape)\nprint(mask.shape) In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_NumpyTile/#numpytile","title":"NumpyTile\u00b6","text":"
Specification: https://github.com/planetlabs/numpytiles-spec
"},{"location":"examples/notebooks/Working_with_NumpyTile/#requirements","title":"Requirements\u00b6","text":"!pip install numpy mercantile
# Uncomment this line if you need to install the dependencies\n# !pip rasterio folium httpx tqdm\n# Uncomment this line if you need to install the dependencies # !pip rasterio folium httpx tqdm In\u00a0[\u00a0]: Copied!
import os\nimport json\nimport base64\nimport httpx\nimport datetime\nimport itertools\nimport urllib.parse\n\nfrom io import BytesIO\nfrom functools import partial\nfrom concurrent import futures\n\nfrom tqdm.notebook import tqdm\n\nfrom rasterio.plot import reshape_as_image\nfrom rasterio.features import bounds as featureBounds\n\nfrom folium import Map, TileLayer, GeoJson\n\n%pylab inline\nimport os import json import base64 import httpx import datetime import itertools import urllib.parse from io import BytesIO from functools import partial from concurrent import futures from tqdm.notebook import tqdm from rasterio.plot import reshape_as_image from rasterio.features import bounds as featureBounds from folium import Map, TileLayer, GeoJson %pylab inline In\u00a0[\u00a0]: Copied!
# Endpoint variables\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\nstac_endpoint = \"https://earth-search.aws.element84.com/v0/search\"\n# Endpoint variables titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. stac_endpoint = \"https://earth-search.aws.element84.com/v0/search\" In\u00a0[\u00a0]: Copied!
geojson = {\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [\n [\n [\n 30.810813903808594,\n 29.454247067148533\n ],\n [\n 30.88600158691406,\n 29.454247067148533\n ],\n [\n 30.88600158691406,\n 29.51879923863822\n ],\n [\n 30.810813903808594,\n 29.51879923863822\n ],\n [\n 30.810813903808594,\n 29.454247067148533\n ]\n ]\n ]\n }\n }\n ]\n}\n\nbounds = featureBounds(geojson)\n\nm = Map(\n tiles=\"OpenStreetMap\",\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=11\n)\n\ngeo_json = GeoJson(data=geojson)\ngeo_json.add_to(m)\nm\ngeojson = { \"type\": \"FeatureCollection\", \"features\": [ { \"type\": \"Feature\", \"properties\": {}, \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [ 30.810813903808594, 29.454247067148533 ], [ 30.88600158691406, 29.454247067148533 ], [ 30.88600158691406, 29.51879923863822 ], [ 30.810813903808594, 29.51879923863822 ], [ 30.810813903808594, 29.454247067148533 ] ] ] } } ] } bounds = featureBounds(geojson) m = Map( tiles=\"OpenStreetMap\", location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=11 ) geo_json = GeoJson(data=geojson) geo_json.add_to(m) m
start = datetime.datetime.strptime(\"2019-01-01\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT00:00:00Z\")\nend = datetime.datetime.strptime(\"2019-12-11\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT23:59:59Z\")\n\n# POST body\nquery = {\n \"collections\": [\"sentinel-s2-l2a-cogs\"],\n \"datetime\": f\"{start}/{end}\",\n \"query\": {\n \"eo:cloud_cover\": {\n \"lt\": 5\n },\n },\n \"intersects\": geojson[\"features\"][0][\"geometry\"],\n \"limit\": 100,\n \"fields\": {\n 'include': ['id', 'properties.datetime', 'properties.eo:cloud_cover'], # This will limit the size of returned body\n 'exclude': ['assets', 'links'] # This will limit the size of returned body\n }\n}\n\n# POST Headers\nheaders = {\n \"Content-Type\": \"application/json\",\n \"Accept-Encoding\": \"gzip\",\n \"Accept\": \"application/geo+json\",\n}\n\ndata = httpx.post(stac_endpoint, headers=headers, json=query).json()\nprint(\"Results context:\")\nprint(data[\"context\"])\nprint()\nprint(\"Example of item:\")\nprint(json.dumps(data[\"features\"][0], indent=4))\n\nsceneid = [f[\"id\"] for f in data[\"features\"]]\ncloudcover = [f[\"properties\"][\"eo:cloud_cover\"] for f in data[\"features\"]]\ndates = [f[\"properties\"][\"datetime\"][0:10] for f in data[\"features\"]]\nstart = datetime.datetime.strptime(\"2019-01-01\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT00:00:00Z\") end = datetime.datetime.strptime(\"2019-12-11\", \"%Y-%m-%d\").strftime(\"%Y-%m-%dT23:59:59Z\") # POST body query = { \"collections\": [\"sentinel-s2-l2a-cogs\"], \"datetime\": f\"{start}/{end}\", \"query\": { \"eo:cloud_cover\": { \"lt\": 5 }, }, \"intersects\": geojson[\"features\"][0][\"geometry\"], \"limit\": 100, \"fields\": { 'include': ['id', 'properties.datetime', 'properties.eo:cloud_cover'], # This will limit the size of returned body 'exclude': ['assets', 'links'] # This will limit the size of returned body } } # POST Headers headers = { \"Content-Type\": \"application/json\", \"Accept-Encoding\": \"gzip\", \"Accept\": \"application/geo+json\", } data = httpx.post(stac_endpoint, headers=headers, json=query).json() print(\"Results context:\") print(data[\"context\"]) print() print(\"Example of item:\") print(json.dumps(data[\"features\"][0], indent=4)) sceneid = [f[\"id\"] for f in data[\"features\"]] cloudcover = [f[\"properties\"][\"eo:cloud_cover\"] for f in data[\"features\"]] dates = [f[\"properties\"][\"datetime\"][0:10] for f in data[\"features\"]] In\u00a0[\u00a0]: Copied!
m = Map(\n tiles=\"OpenStreetMap\",\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=8\n)\n\ngeo_json = GeoJson(\n data=data,\n style_function=lambda x: {\n 'opacity': 1, 'dashArray': '1', 'fillOpacity': 0, 'weight': 1\n },\n)\ngeo_json.add_to(m)\nm\nm = Map( tiles=\"OpenStreetMap\", location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=8 ) geo_json = GeoJson( data=data, style_function=lambda x: { 'opacity': 1, 'dashArray': '1', 'fillOpacity': 0, 'weight': 1 }, ) geo_json.add_to(m) m
Plot Date / Cloud Cover
In\u00a0[\u00a0]: Copied!fig = plt.figure(dpi=100)\nfig.autofmt_xdate()\nax = fig.add_subplot(1, 1, 1)\nax.plot(dates, cloudcover, label=\"Cloud Cover\", color=\"tab:red\", linewidth=0.4, linestyle=\"-.\")\n\nax.legend()\nfig = plt.figure(dpi=100) fig.autofmt_xdate() ax = fig.add_subplot(1, 1, 1) ax.plot(dates, cloudcover, label=\"Cloud Cover\", color=\"tab:red\", linewidth=0.4, linestyle=\"-.\") ax.legend() In\u00a0[\u00a0]: Copied!
url_template = \"https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/{id}\"\nurl_template = \"https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/{id}\" In\u00a0[\u00a0]: Copied!
# Get Tile URL\nitem = url_template.format(id=sceneid[-1])\nprint(item)\nr = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = (\n (\"url\", item),\n # Simple RGB combination (True Color)\n (\"assets\", \"B04\"), # red, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\"assets\", \"B03\"), # green, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\"assets\", \"B02\"), # blue, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\"color_formula\", \"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\"), # We use a rio-color formula to make the tiles look nice\n (\"minzoom\", 8), # By default titiler will use 0\n (\"maxzoom\", 14), # By default titiler will use 24\n )\n).json()\nprint(r)\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=10\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"Digital Earth Africa\"\n)\ntiles.add_to(m)\nm\n# Get Tile URL item = url_template.format(id=sceneid[-1]) print(item) r = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = ( (\"url\", item), # Simple RGB combination (True Color) (\"assets\", \"B04\"), # red, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) (\"assets\", \"B03\"), # green, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) (\"assets\", \"B02\"), # blue, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) (\"color_formula\", \"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\"), # We use a rio-color formula to make the tiles look nice (\"minzoom\", 8), # By default titiler will use 0 (\"maxzoom\", 14), # By default titiler will use 24 ) ).json() print(r) m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=10 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"Digital Earth Africa\" ) tiles.add_to(m) m In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = (\n (\"url\", url_template.format(id=sceneid[0])),\n # False Color Infrared\n (\"assets\", \"B08\"), # nir, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\"assets\", \"B04\"), # red, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\"assets\", \"B03\"), # green, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\"color_formula\", \"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\"), # We use a rio-color formula to make the tiles look nice\n (\"minzoom\", 8), # By default titiler will use 0\n (\"maxzoom\", 14), # By default titiler will use 24\n )\n).json()\nprint(r)\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=10\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"Digital Earth Africa\"\n)\ntiles.add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = ( (\"url\", url_template.format(id=sceneid[0])), # False Color Infrared (\"assets\", \"B08\"), # nir, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) (\"assets\", \"B04\"), # red, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) (\"assets\", \"B03\"), # green, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) (\"color_formula\", \"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\"), # We use a rio-color formula to make the tiles look nice (\"minzoom\", 8), # By default titiler will use 0 (\"maxzoom\", 14), # By default titiler will use 24 ) ).json() print(r) m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=10 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"Digital Earth Africa\" ) tiles.add_to(m) m In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": url_template.format(id=sceneid[0]),\n \"expression\": \"(B08-B04)/(B08+B04)\", # NDVI (nir-red)/(nir+red), in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n # We need to tell rio-tiler that each asset is a Band\n # (so it will select the first band within each asset automatically)\n \"asset_as_band\": True,\n \"rescale\": \"-1,1\",\n \"minzoom\": 8, # By default titiler will use 0\n \"maxzoom\": 14, # By default titiler will use 24\n \"colormap_name\": \"viridis\",\n }\n).json()\nprint(r)\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=10\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"Digital Earth Africa\"\n)\ntiles.add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = { \"url\": url_template.format(id=sceneid[0]), \"expression\": \"(B08-B04)/(B08+B04)\", # NDVI (nir-red)/(nir+red), in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) # We need to tell rio-tiler that each asset is a Band # (so it will select the first band within each asset automatically) \"asset_as_band\": True, \"rescale\": \"-1,1\", \"minzoom\": 8, # By default titiler will use 0 \"maxzoom\": 14, # By default titiler will use 24 \"colormap_name\": \"viridis\", } ).json() print(r) m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=10 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"Digital Earth Africa\" ) tiles.add_to(m) m In\u00a0[\u00a0]: Copied!
def fetch_bbox_array(sceneid, bbox, assets = None, expression = None, **kwargs):\n \"\"\"Helper function to fetch and decode Numpy array using Titiler endpoint.\"\"\"\n # STAC ITEM URL\n stac_item = f\"https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/{sceneid}\"\n\n xmin, ymin, xmax, ymax = bbox\n\n # TiTiler required URL + asset or expression parameters\n params = ((\"url\", stac_item), (\"max_size\", 1024))\n if assets:\n for asset in assets:\n params += ((\"assets\", asset), )\n elif expression:\n params += ((\"expression\", expression), (\"asset_as_band\", True),)\n else:\n raise Exception(\"Missing band or expression input\")\n\n params += tuple(kwargs.items())\n\n # TITILER ENDPOINT\n url = f\"{titiler_endpoint}/stac/bbox/{xmin},{ymin},{xmax},{ymax}.npy\"\n r = httpx.get(url, params=params)\n data = numpy.load(BytesIO(r.content))\n\n return sceneid, data[0:-1], data[-1]\n\ndef _filter_futures(tasks):\n for future in tasks:\n try:\n yield future.result()\n except Exception:\n pass\n\ndef _stats(data, mask):\n arr = numpy.ma.array(data)\n arr.mask = mask == 0\n return arr.min().item(), arr.max().item(), arr.mean().item(), arr.std().item()\ndef fetch_bbox_array(sceneid, bbox, assets = None, expression = None, **kwargs): \"\"\"Helper function to fetch and decode Numpy array using Titiler endpoint.\"\"\" # STAC ITEM URL stac_item = f\"https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/{sceneid}\" xmin, ymin, xmax, ymax = bbox # TiTiler required URL + asset or expression parameters params = ((\"url\", stac_item), (\"max_size\", 1024)) if assets: for asset in assets: params += ((\"assets\", asset), ) elif expression: params += ((\"expression\", expression), (\"asset_as_band\", True),) else: raise Exception(\"Missing band or expression input\") params += tuple(kwargs.items()) # TITILER ENDPOINT url = f\"{titiler_endpoint}/stac/bbox/{xmin},{ymin},{xmax},{ymax}.npy\" r = httpx.get(url, params=params) data = numpy.load(BytesIO(r.content)) return sceneid, data[0:-1], data[-1] def _filter_futures(tasks): for future in tasks: try: yield future.result() except Exception: pass def _stats(data, mask): arr = numpy.ma.array(data) arr.mask = mask == 0 return arr.min().item(), arr.max().item(), arr.mean().item(), arr.std().item() In\u00a0[\u00a0]: Copied!
# Fetch one data\n_, data, mask = fetch_bbox_array(sceneid[0], bounds, assets=[\"B02\"], width=128, height=128)\n\nprint(data.shape)\nprint(mask.shape)\n\nimshow(data[0])\n# Fetch one data _, data, mask = fetch_bbox_array(sceneid[0], bounds, assets=[\"B02\"], width=128, height=128) print(data.shape) print(mask.shape) imshow(data[0]) In\u00a0[\u00a0]: Copied!
# Let's fetch the data over our AOI for all our Items\n# Here we use `futures.ThreadPoolExecutor` to run the requests in parallel\n# Note: it takes more time for the notebook to display the results than to fetch the data\n\nbbox_worker = partial(\n fetch_bbox_array,\n bbox=bounds,\n assets=(\"B04\", \"B03\", \"B02\"), #(\"red\", \"green\", \"blue\"), in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n color_formula=\"gamma RGB 3.5, saturation 1.7, sigmoidal RGB 15 0.35\",\n width=64,\n height=64,\n)\n\nwith futures.ThreadPoolExecutor(max_workers=10) as executor:\n future_work = [\n executor.submit(bbox_worker, scene) for scene in sceneid\n ]\n\n for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n pass\n\nresults_rgb = list(_filter_futures(future_work))\n\nprint(\"diplay all results\")\n\nfig = plt.figure(figsize=(10,20))\ncol = 5\nrow = math.ceil(len(dates) / col)\nfor i in range(1, len(results_rgb) + 1):\n fig.add_subplot(row, col, i)\n plt.imshow(reshape_as_image(results_rgb[i-1][1]))\n# Let's fetch the data over our AOI for all our Items # Here we use `futures.ThreadPoolExecutor` to run the requests in parallel # Note: it takes more time for the notebook to display the results than to fetch the data bbox_worker = partial( fetch_bbox_array, bbox=bounds, assets=(\"B04\", \"B03\", \"B02\"), #(\"red\", \"green\", \"blue\"), in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) color_formula=\"gamma RGB 3.5, saturation 1.7, sigmoidal RGB 15 0.35\", width=64, height=64, ) with futures.ThreadPoolExecutor(max_workers=10) as executor: future_work = [ executor.submit(bbox_worker, scene) for scene in sceneid ] for f in tqdm(futures.as_completed(future_work), total=len(future_work)): pass results_rgb = list(_filter_futures(future_work)) print(\"diplay all results\") fig = plt.figure(figsize=(10,20)) col = 5 row = math.ceil(len(dates) / col) for i in range(1, len(results_rgb) + 1): fig.add_subplot(row, col, i) plt.imshow(reshape_as_image(results_rgb[i-1][1])) In\u00a0[\u00a0]: Copied!
## Fetch NDVI\n\nbbox_worker = partial(\n fetch_bbox_array,\n bbox=bounds,\n expression=\"(B08-B04)/(B08+B04)\", # (nir-red)/(nir+red), in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n width=64,\n height=64,\n)\n\nwith futures.ThreadPoolExecutor(max_workers=10) as executor:\n future_work = [\n executor.submit(bbox_worker, scene) for scene in sceneid\n ]\n\n for f in tqdm(futures.as_completed(future_work), total=len(future_work)):\n pass\n\nresults_ndvi = list(_filter_futures(future_work))\n\nfig = plt.figure(figsize=(10,20))\ncol = 5\nrow = math.ceil(len(dates) / col)\nfor i in range(1, len(results_rgb) + 1):\n fig.add_subplot(row, col, i)\n plt.imshow(results_ndvi[i-1][1][0])\n## Fetch NDVI bbox_worker = partial( fetch_bbox_array, bbox=bounds, expression=\"(B08-B04)/(B08+B04)\", # (nir-red)/(nir+red), in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63) width=64, height=64, ) with futures.ThreadPoolExecutor(max_workers=10) as executor: future_work = [ executor.submit(bbox_worker, scene) for scene in sceneid ] for f in tqdm(futures.as_completed(future_work), total=len(future_work)): pass results_ndvi = list(_filter_futures(future_work)) fig = plt.figure(figsize=(10,20)) col = 5 row = math.ceil(len(dates) / col) for i in range(1, len(results_rgb) + 1): fig.add_subplot(row, col, i) plt.imshow(results_ndvi[i-1][1][0]) In\u00a0[\u00a0]: Copied!
stats = [_stats(data, mask) for _, data, mask in results_ndvi]\n\nfig, ax1 = plt.subplots(dpi=150)\nfig.autofmt_xdate()\n\nax1.plot(dates, [s[0] for s in stats], label=\"Min\")\nax1.plot(dates, [s[1] for s in stats], label=\"Max\")\nax1.plot(dates, [s[2] for s in stats], label=\"Mean\")\nax1.set_xlabel(\"Dates\")\nax1.set_ylabel(\"Normalized Difference Vegetation Index\")\n\nax1.legend()\nstats = [_stats(data, mask) for _, data, mask in results_ndvi] fig, ax1 = plt.subplots(dpi=150) fig.autofmt_xdate() ax1.plot(dates, [s[0] for s in stats], label=\"Min\") ax1.plot(dates, [s[1] for s in stats], label=\"Max\") ax1.plot(dates, [s[2] for s in stats], label=\"Mean\") ax1.set_xlabel(\"Dates\") ax1.set_ylabel(\"Normalized Difference Vegetation Index\") ax1.legend() In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_STAC/#working-with-stac-at-scale","title":"Working With STAC - At Scale\u00b6","text":""},{"location":"examples/notebooks/Working_with_STAC/#stac-spatiotemporal-asset-catalog","title":"STAC: SpatioTemporal Asset Catalog\u00b6","text":"
The SpatioTemporal Asset Catalog (STAC) specification aims to standardize the way geospatial assets are exposed online and queried. A 'spatiotemporal asset' is any file that represents information about the earth captured in a certain space and time. The initial focus is primarily remotely-sensed imagery (from satellites, but also planes, drones, balloons, etc), but the core is designed to be extensible to SAR, full motion video, point clouds, hyperspectral, LiDAR and derived data like NDVI, Digital Elevation Models, mosaics, etc.
Ref: https://github.com/radiantearth/stac-spechttps://github.com/radiantearth/stac-spec
Using STAC makes data indexation and discovery really easy. In addition to the Collection/Item/Asset (data) specifications, data providers are also encouraged to follow a STAC API specification: https://github.com/radiantearth/stac-api-spec
The API is compliant with the OGC API - Features standard (formerly known as OGC Web Feature Service 3), in that it defines many of the endpoints that STAC uses. A STAC API should be compatible and usable with any OGC API - Features clients. The STAC API can be thought of as a specialized Features API to search STAC Catalogs, where the features returned are STAC Items, that have common properties, links to their assets and geometries that represent the footprints of the geospatial assets.
"},{"location":"examples/notebooks/Working_with_STAC/#sentinel-2","title":"Sentinel 2\u00b6","text":"Thanks to Digital Earth Africa and in collaboration with Sinergise, Element 84, Amazon Web Services (AWS) and the Committee on Earth Observation Satellites (CEOS), Sentinel 2 (Level 2) data over Africa, usually stored as JPEG2000, has been translated to COG more important a STAC database and API has been setup.
https://www.digitalearthafrica.org/news/operational-and-ready-use-satellite-data-now-available-across-africa
The API is provided by @element84 and follows the latest specification: https://earth-search.aws.element84.com/v0
"},{"location":"examples/notebooks/Working_with_STAC/#titiler-stac-cog","title":"TiTiler: STAC + COG\u00b6","text":"Docs: https://github.com/developmentseed/titiler/blob/main/docs/endpoints/stac.md
TiTiler was first designed to work with single COG by passing the file URL to the tiler. e.g : https://myendpoint/cog/tiles/1/2/3?url=https://somewhere.com/mycog.tif
With STAC is a bit different because we first have to read the STAC items and then know which assets to read.
Example of STAC Item
{\n \"type\": \"Feature\",\n \"id\": \"S2A_34SGA_20200318_0_L2A\",\n\n \"geometry\": {...},\n \"properties\": {\n \"datetime\": \"2020-03-18T09:11:33Z\",\n ...\n },\n \"collection\": \"sentinel-s2-l2a-cogs\",\n \"assets\": {\n \"thumbnail\": {\n \"title\": \"Thumbnail\",\n \"type\": \"image/png\",\n \"href\": \"https://myurl.com/preview.jpg\"\n },\n ...\n \"B03\": {\n \"title\": \"Band 3 (green)\",\n \"type\": \"image/tiff; application=geotiff; profile=cloud-optimized\",\n \"href\": \"https://myurl.com/B03.tif\",\n \"proj:shape\": [\n 10980,\n 10980\n ],\n \"proj:transform\": [\n 10,\n 0,\n 699960,\n 0,\n -10,\n 3600000,\n 0,-*\n 0,\n 1\n ]\n },\n ...\n },\n \"links\": [...]\n}\n
To be able to create Web Map tile from the B03
asset you'll need to pass the STAC Item url and the asset name:
https://myendpoint/stac/tiles/1/2/3?url=https://somewhere.com/item.json&assets=B03
To be able to run this notebook you'll need the following requirements:
!pip install rasterio folium httpx tqdm
See https://github.com/radiantearth/stac-api-spec for more documentation about the stac API
You can use geojson.io to define your search AOI
"},{"location":"examples/notebooks/Working_with_STAC/#use-titiler-endpoint","title":"Use Titiler endpoint\u00b6","text":"https://titiler.xyz/api.html#/SpatioTemporal%20Asset%20Catalog
{endpoint}/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}?url={stac_item}&{otherquery params}
{endpoint}/stac/bbox/{minx},{miny},{maxx},{maxy}.{format}?url={stac_item}&{otherquery params}
{endpoint}/stac/point/{minx},{miny}?url={stac_item}&{otherquery params}
titiler doesn't return only png or jpeg but can also return Numpy array directly
"},{"location":"examples/notebooks/Working_with_STAC_simple/","title":"Working With STAC","text":"In\u00a0[\u00a0]: Copied!# Uncomment this line if you need to install the dependencies\n# !pip install folium requests rasterio\n# Uncomment this line if you need to install the dependencies # !pip install folium requests rasterio In\u00a0[\u00a0]: Copied!
import httpx\n\nfrom rasterio.features import bounds as featureBounds\n\nfrom folium import Map, TileLayer, GeoJson\n\n%pylab inline\nimport httpx from rasterio.features import bounds as featureBounds from folium import Map, TileLayer, GeoJson %pylab inline In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\nstac_item = \"https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a/items/S2A_30TVT_20221112_0_L2A\"\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. stac_item = \"https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a/items/S2A_30TVT_20221112_0_L2A\" In\u00a0[\u00a0]: Copied!
item = httpx.get(stac_item).json()\nprint(item)\nitem = httpx.get(stac_item).json() print(item) In\u00a0[\u00a0]: Copied!
for it, asset in item[\"assets\"].items():\n print(\"Name:\", it, \"| Format:\", asset[\"type\"])\nfor it, asset in item[\"assets\"].items(): print(\"Name:\", it, \"| Format:\", asset[\"type\"]) In\u00a0[\u00a0]: Copied!
bounds = featureBounds(item)\n\nm = Map(\n tiles=\"OpenStreetMap\",\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=8\n)\n\ngeo_json = GeoJson(data=item)\ngeo_json.add_to(m)\nm\nbounds = featureBounds(item) m = Map( tiles=\"OpenStreetMap\", location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=8 ) geo_json = GeoJson(data=item) geo_json.add_to(m) m In\u00a0[\u00a0]: Copied!
# Get Tile URL\nr = httpx.get(\n f\"{titiler_endpoint}/stac/info\",\n params = (\n (\"url\", stac_item),\n # Get info for multiple assets\n (\"assets\",\"visual\"), (\"assets\",\"red\"), (\"assets\",\"blue\"), (\"assets\",\"green\"),\n )\n).json()\nprint(r)\n# Get Tile URL r = httpx.get( f\"{titiler_endpoint}/stac/info\", params = ( (\"url\", stac_item), # Get info for multiple assets (\"assets\",\"visual\"), (\"assets\",\"red\"), (\"assets\",\"blue\"), (\"assets\",\"green\"), ) ).json() print(r) In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": stac_item,\n \"assets\": \"visual\",\n \"minzoom\": 8, # By default titiler will use 0\n \"maxzoom\": 14, # By default titiler will use 24\n }\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=10\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"ESA\"\n)\ntiles.add_to(m)\nm\nr = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = { \"url\": stac_item, \"assets\": \"visual\", \"minzoom\": 8, # By default titiler will use 0 \"maxzoom\": 14, # By default titiler will use 24 } ).json() m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=10 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"ESA\" ) tiles.add_to(m) m In\u00a0[\u00a0]: Copied!
# Get Tile URL\nr = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = {\n \"url\": stac_item,\n \"assets\": \"visual\",\n \"asset_bidx\": \"visual|3,1,2\",\n \"minzoom\": 8, # By default titiler will use 0\n \"maxzoom\": 14, # By default titiler will use 24\n }\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=12\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"ESA\"\n)\ntiles.add_to(m)\nm\n# Get Tile URL r = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = { \"url\": stac_item, \"assets\": \"visual\", \"asset_bidx\": \"visual|3,1,2\", \"minzoom\": 8, # By default titiler will use 0 \"maxzoom\": 14, # By default titiler will use 24 } ).json() m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=12 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"ESA\" ) tiles.add_to(m) m In\u00a0[\u00a0]: Copied!
# Get Tile URL\nr = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = (\n (\"url\", stac_item),\n (\"assets\", \"red\"),\n (\"assets\", \"green\"),\n (\"assets\", \"blue\"),\n # Most of the Sentinel L2A Assets have only one band\n # So we don't have to pass the bidx\n # (\"assets_bidx\", \"red|1\"),\n # (\"assets_bidx\", \"green|1\"),\n # (\"assets_bidx\", \"blue|\"),\n (\"minzoom\", 8),\n (\"maxzoom\", 14),\n (\"rescale\", \"0,2000\"),\n )\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=11\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"ESA\"\n)\ntiles.add_to(m)\nm\n# Get Tile URL r = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = ( (\"url\", stac_item), (\"assets\", \"red\"), (\"assets\", \"green\"), (\"assets\", \"blue\"), # Most of the Sentinel L2A Assets have only one band # So we don't have to pass the bidx # (\"assets_bidx\", \"red|1\"), # (\"assets_bidx\", \"green|1\"), # (\"assets_bidx\", \"blue|\"), (\"minzoom\", 8), (\"maxzoom\", 14), (\"rescale\", \"0,2000\"), ) ).json() m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=11 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"ESA\" ) tiles.add_to(m) m
Use an expression to calculate a band index (NDVI) based on information contained in multiple assets.
In\u00a0[\u00a0]: Copied!# Get Tile URL\nr = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = (\n (\"url\", stac_item),\n (\"expression\", \"(nir-red)/(nir+red)\"), # NDVI\n # We need to tell rio-tiler that each asset is a Band\n # (so it will select the first band within each asset automatically)\n (\"asset_as_band\", True),\n (\"rescale\", \"-1,1\"),\n (\"minzoom\", 8),\n (\"maxzoom\", 14),\n (\"colormap_name\", \"viridis\"),\n )\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=10\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"ESA\"\n)\ntiles.add_to(m)\nm\n# Get Tile URL r = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = ( (\"url\", stac_item), (\"expression\", \"(nir-red)/(nir+red)\"), # NDVI # We need to tell rio-tiler that each asset is a Band # (so it will select the first band within each asset automatically) (\"asset_as_band\", True), (\"rescale\", \"-1,1\"), (\"minzoom\", 8), (\"maxzoom\", 14), (\"colormap_name\", \"viridis\"), ) ).json() m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=10 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"ESA\" ) tiles.add_to(m) m
If you don't use the asset_as_band=True
option, you need to append the band to the asset name within the expression. For example, nir
becomes nir_b1
.
# Get Tile URL\nr = httpx.get(\n f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\",\n params = (\n (\"url\", stac_item),\n (\"expression\", \"(nir_b1-red_b1)/(nir_b1+red_b1)\"), # NDVI\n (\"rescale\", \"-1,1\"),\n (\"minzoom\", 8),\n (\"maxzoom\", 14),\n (\"colormap_name\", \"viridis\"),\n )\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2),\n zoom_start=10\n)\n\ntiles = TileLayer(\n tiles=r[\"tiles\"][0],\n min_zoom=r[\"minzoom\"],\n max_zoom=r[\"maxzoom\"],\n opacity=1,\n attr=\"ESA\"\n)\ntiles.add_to(m)\nm\n# Get Tile URL r = httpx.get( f\"{titiler_endpoint}/stac/WebMercatorQuad/tilejson.json\", params = ( (\"url\", stac_item), (\"expression\", \"(nir_b1-red_b1)/(nir_b1+red_b1)\"), # NDVI (\"rescale\", \"-1,1\"), (\"minzoom\", 8), (\"maxzoom\", 14), (\"colormap_name\", \"viridis\"), ) ).json() m = Map( location=((bounds[1] + bounds[3]) / 2,(bounds[0] + bounds[2]) / 2), zoom_start=10 ) tiles = TileLayer( tiles=r[\"tiles\"][0], min_zoom=r[\"minzoom\"], max_zoom=r[\"maxzoom\"], opacity=1, attr=\"ESA\" ) tiles.add_to(m) m In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_STAC_simple/#working-with-stac","title":"Working With STAC\u00b6","text":""},{"location":"examples/notebooks/Working_with_STAC_simple/#stac-spatiotemporal-asset-catalog","title":"STAC: SpatioTemporal Asset Catalog\u00b6","text":"
The SpatioTemporal Asset Catalog (STAC) specification aims to standardize the way geospatial assets are exposed online and queried. A 'spatiotemporal asset' is any file that represents information about the earth captured in a certain space and time. The initial focus is primarily remotely-sensed imagery (from satellites, but also planes, drones, balloons, etc), but the core is designed to be extensible to SAR, full motion video, point clouds, hyperspectral, LiDAR and derived data like NDVI, Digital Elevation Models, mosaics, etc.
Ref: https://github.com/radiantearth/stac-spechttps://github.com/radiantearth/stac-spec
Using STAC makes data indexation and discovery really easy. In addition to the Collection/Item/Asset (data) specifications, data providers are also encouraged to follow a STAC API specification: https://github.com/radiantearth/stac-api-spec
The API is compliant with the OGC API - Features standard (formerly known as OGC Web Feature Service 3), in that it defines many of the endpoints that STAC uses. A STAC API should be compatible and usable with any OGC API - Features clients. The STAC API can be thought of as a specialized Features API to search STAC Catalogs, where the features returned are STAC Items, that have common properties, links to their assets and geometries that represent the footprints of the geospatial assets.
"},{"location":"examples/notebooks/Working_with_STAC_simple/#requirements","title":"Requirements\u00b6","text":"To be able to run this notebook you'll need the following requirements:
!pip install folium httpx rasterio
# setup\nimport httpx\nimport json\n\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\ncog_url = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\n# setup import httpx import json titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. cog_url = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\" In\u00a0[10]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/bounds\",\n params = {\n \"url\": cog_url,\n }\n).json()\n\nbounds = r[\"bounds\"]\nprint(r)\nr = httpx.get( f\"{titiler_endpoint}/cog/bounds\", params = { \"url\": cog_url, } ).json() bounds = r[\"bounds\"] print(r)
{'bounds': [57.664053823239804, -20.55473177712791, 57.84021477996238, -20.25261582755764]}\n
For a bit more information, you can get summary statistics from the /info
endpoint. This includes info such as:
/bounds
endpoint)These are statistics available in the metadata of the image, so should be fast to read.
In\u00a0[11]: Copied!r = httpx.get(\n f\"{titiler_endpoint}/cog/info\",\n params = {\n \"url\": cog_url,\n }\n).json()\n\nprint(json.dumps(r))\nr = httpx.get( f\"{titiler_endpoint}/cog/info\", params = { \"url\": cog_url, } ).json() print(json.dumps(r))
{\"bounds\": [57.664053823239804, -20.55473177712791, 57.84021477996238, -20.25261582755764], \"minzoom\": 10, \"maxzoom\": 18, \"band_metadata\": [[\"b1\", {}], [\"b2\", {}], [\"b3\", {}]], \"band_descriptions\": [[\"b1\", \"\"], [\"b2\", \"\"], [\"b3\", \"\"]], \"dtype\": \"uint8\", \"nodata_type\": \"Mask\", \"colorinterp\": [\"red\", \"green\", \"blue\"], \"count\": 3, \"width\": 38628, \"driver\": \"GTiff\", \"overviews\": [2, 4, 8, 16, 32, 64, 128], \"height\": 66247}\nIn\u00a0[12]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/statistics\",\n params = {\n \"url\": cog_url,\n }\n).json()\n\nprint(json.dumps(r))\nr = httpx.get( f\"{titiler_endpoint}/cog/statistics\", params = { \"url\": cog_url, } ).json() print(json.dumps(r))
{\"b1\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 36.94901407469342, \"count\": 574080.0, \"sum\": 21211690.0, \"std\": 48.282133573955264, \"median\": 3.0, \"majority\": 1.0, \"minority\": 246.0, \"unique\": 256.0, \"histogram\": [[330584.0, 54820.0, 67683.0, 57434.0, 30305.0, 14648.0, 9606.0, 5653.0, 2296.0, 1051.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": 0.0, \"percentile_98\": 171.0}, \"b2\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 57.1494356187291, \"count\": 574080.0, \"sum\": 32808348.0, \"std\": 56.300819175100656, \"median\": 37.0, \"majority\": 5.0, \"minority\": 0.0, \"unique\": 256.0, \"histogram\": [[271018.0, 34938.0, 54030.0, 69429.0, 70260.0, 32107.0, 29375.0, 9697.0, 2001.0, 1225.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": 5.0, \"percentile_98\": 180.0}, \"b3\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 51.251764562430324, \"count\": 574080.0, \"sum\": 29422613.0, \"std\": 39.65505035854822, \"median\": 36.0, \"majority\": 16.0, \"minority\": 252.0, \"unique\": 254.0, \"histogram\": [[203263.0, 150865.0, 104882.0, 42645.0, 30652.0, 25382.0, 12434.0, 2397.0, 1097.0, 463.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": 14.0, \"percentile_98\": 158.0}}\n
This endpoint is far more configurable than /bounds
and info
. You can specify which bands to analyse, how to generate the histogram, and pre-process the image.
For example, if you wanted to get the statistics of the VARI of the image you can use the expression
parameter to conduct simple band math:
r = httpx.get(\n f\"{titiler_endpoint}/cog/statistics\",\n params = {\n \"url\": cog_url,\n \"expression\": \"(b2-b1)/(b1+b2-b3)\", # expression for the VARI\n \"histogram_range\": \"-1,1\"\n }\n).json()\n\nprint(json.dumps(r))\nr = httpx.get( f\"{titiler_endpoint}/cog/statistics\", params = { \"url\": cog_url, \"expression\": \"(b2-b1)/(b1+b2-b3)\", # expression for the VARI \"histogram_range\": \"-1,1\" } ).json() print(json.dumps(r))
{\"(b2-b1)/(b1+b2-b3)\": {\"min\": -1.7976931348623157e+308, \"max\": 1.7976931348623157e+308, \"mean\": null, \"count\": 574080.0, \"sum\": null, \"std\": null, \"median\": -0.15384615384615385, \"majority\": -0.4, \"minority\": -149.0, \"unique\": 18718.0, \"histogram\": [[5646.0, 10176.0, 130905.0, 97746.0, 50184.0, 95842.0, 60322.0, 21478.0, 13552.0, 12204.0], [-1.0, -0.8, -0.6, -0.3999999999999999, -0.19999999999999996, 0.0, 0.20000000000000018, 0.40000000000000013, 0.6000000000000001, 0.8, 1.0]], \"valid_percent\": 93.75, \"masked_pixels\": 38272.0, \"valid_pixels\": 574080.0, \"percentile_2\": -3.5, \"percentile_98\": 3.3870967741935485}}\n
Alternatively, if you would like to get statistics for only a certain area, you can specify an area via a feature or a feature collection.
(Note: this endpoint is not available in the mosaicjson endpoint, only /cog
and /stac
)
mahebourg = \"\"\"\n{\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"coordinates\": [\n [\n [\n 57.70358910197049,\n -20.384114558699935\n ],\n [\n 57.68564920588395,\n -20.384114558699935\n ],\n [\n 57.68209507552771,\n -20.39855066753664\n ],\n [\n 57.68666467170024,\n -20.421074640746554\n ],\n [\n 57.70341985766697,\n -20.434397129770545\n ],\n [\n 57.72999121319131,\n -20.42392955694521\n ],\n [\n 57.70358910197049,\n -20.384114558699935\n ]\n ]\n ],\n \"type\": \"Polygon\"\n }\n }\n ]\n}\n\"\"\"\nmahebourg = \"\"\" { \"type\": \"FeatureCollection\", \"features\": [ { \"type\": \"Feature\", \"properties\": {}, \"geometry\": { \"coordinates\": [ [ [ 57.70358910197049, -20.384114558699935 ], [ 57.68564920588395, -20.384114558699935 ], [ 57.68209507552771, -20.39855066753664 ], [ 57.68666467170024, -20.421074640746554 ], [ 57.70341985766697, -20.434397129770545 ], [ 57.72999121319131, -20.42392955694521 ], [ 57.70358910197049, -20.384114558699935 ] ] ], \"type\": \"Polygon\" } } ] } \"\"\" In\u00a0[15]: Copied!
# NOTE: This is a POST request, unlike all other requests in this example\nr = httpx.post(\n f\"{titiler_endpoint}/cog/statistics\",\n data=mahebourg,\n params = {\n \"url\": cog_url,\n }\n).json()\n\nprint(json.dumps(r))\n# NOTE: This is a POST request, unlike all other requests in this example r = httpx.post( f\"{titiler_endpoint}/cog/statistics\", data=mahebourg, params = { \"url\": cog_url, } ).json() print(json.dumps(r))
{\"type\": \"FeatureCollection\", \"features\": [{\"type\": \"Feature\", \"geometry\": {\"type\": \"Polygon\", \"coordinates\": [[[57.70358910197049, -20.384114558699935], [57.68564920588395, -20.384114558699935], [57.68209507552771, -20.39855066753664], [57.68666467170024, -20.421074640746554], [57.70341985766697, -20.434397129770545], [57.72999121319131, -20.42392955694521], [57.70358910197049, -20.384114558699935]]]}, \"properties\": {\"statistics\": {\"b1\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 88.5634794986129, \"count\": 619641.0, \"sum\": 54877563.0, \"std\": 55.18714964714274, \"median\": 77.0, \"majority\": 52.0, \"minority\": 253.0, \"unique\": 256.0, \"histogram\": [[67233.0, 110049.0, 129122.0, 90849.0, 77108.0, 44091.0, 44606.0, 37790.0, 18033.0, 760.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 62.0, \"masked_pixels\": 379783.0, \"valid_pixels\": 619641.0, \"percentile_2\": 4.0, \"percentile_98\": 208.0}, \"b2\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 112.07155594933195, \"count\": 619641.0, \"sum\": 69444131.0, \"std\": 42.64508357271268, \"median\": 107.0, \"majority\": 103.0, \"minority\": 1.0, \"unique\": 256.0, \"histogram\": [[6004.0, 31108.0, 107187.0, 126848.0, 130731.0, 73650.0, 107827.0, 33264.0, 2403.0, 619.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 62.0, \"masked_pixels\": 379783.0, \"valid_pixels\": 619641.0, \"percentile_2\": 34.0, \"percentile_98\": 189.0}, \"b3\": {\"min\": 0.0, \"max\": 255.0, \"mean\": 84.54690377170006, \"count\": 619641.0, \"sum\": 52388728.0, \"std\": 44.64862735915829, \"median\": 77.0, \"majority\": 53.0, \"minority\": 254.0, \"unique\": 256.0, \"histogram\": [[40704.0, 130299.0, 138014.0, 85866.0, 86381.0, 91182.0, 41872.0, 4116.0, 993.0, 214.0], [0.0, 25.5, 51.0, 76.5, 102.0, 127.5, 153.0, 178.5, 204.0, 229.5, 255.0]], \"valid_percent\": 62.0, \"masked_pixels\": 379783.0, \"valid_pixels\": 619641.0, \"percentile_2\": 11.0, \"percentile_98\": 170.0}}}}]}\nIn\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_Statistics/#working-with-statistics","title":"Working with Statistics\u00b6","text":""},{"location":"examples/notebooks/Working_with_Statistics/#intro","title":"Intro\u00b6","text":"
Titiler allows you to get statistics and summaries of your data without having to load the entire dataset yourself. These statistics can be summaries of entire COG files, STAC items, or individual parts of the file, specified using GeoJSON.
Below, we will go over some of the statistical endpoints in Titiler - /bounds
, /info
, and /statistics
.
(Note: these examples will be using the /cog
endpoint, but everything is also available for /stac
and /mosaicjson
unless otherwise noted)
The /bounds
endpoint returns the bounding box of the image/asset. These bounds are returned in the projection EPSG:4326 (WGS84), in the format (minx, miny, maxx, maxy)
.
For even more statistics of the image, you can use the /statistics
endpoint. This includes even more info, including:
Statistics are generated both for the image as a whole and for each band individually.
"},{"location":"examples/notebooks/Working_with_nonWebMercatorTMS/","title":"Working With TileMatrixSets (other than WebMercator)","text":"This Notebook shows how to use and display tiles with non-webmercator TileMatrixSet
In\u00a0[\u00a0]: Copied!# Uncomment if you need to install those module within the notebook\n# !pip install ipyleaflet httpx\n# Uncomment if you need to install those module within the notebook # !pip install ipyleaflet httpx In\u00a0[\u00a0]: Copied!
import json\n\nimport httpx\n\nfrom ipyleaflet import (\n Map,\n basemaps,\n basemap_to_tiles,\n TileLayer,\n WMSLayer,\n GeoJSON,\n projections\n)\nimport json import httpx from ipyleaflet import ( Map, basemaps, basemap_to_tiles, TileLayer, WMSLayer, GeoJSON, projections ) In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\nurl = \"https://s3.amazonaws.com/opendata.remotepixel.ca/cogs/natural_earth/world.tif\" # Natural Earth WORLD tif\ntitiler_endpoint = \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. url = \"https://s3.amazonaws.com/opendata.remotepixel.ca/cogs/natural_earth/world.tif\" # Natural Earth WORLD tif In\u00a0[\u00a0]: Copied!
r = httpx.get(f\"{titiler_endpoint}/tileMatrixSets\").json()\n\nprint(\"Supported TMS:\")\nfor tms in r[\"tileMatrixSets\"]:\n print(\"-\", tms[\"id\"])\nr = httpx.get(f\"{titiler_endpoint}/tileMatrixSets\").json() print(\"Supported TMS:\") for tms in r[\"tileMatrixSets\"]: print(\"-\", tms[\"id\"]) In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = {\"url\": url}\n).json()\n\nm = Map(center=(0, 0), zoom=2, basemap={}, crs=projections.EPSG3857)\n\nlayer = TileLayer(url=r[\"tiles\"][0], opacity=1)\nm.add_layer(layer)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\", params = {\"url\": url} ).json() m = Map(center=(0, 0), zoom=2, basemap={}, crs=projections.EPSG3857) layer = TileLayer(url=r[\"tiles\"][0], opacity=1) m.add_layer(layer) m In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/WorldCRS84Quad/tilejson.json\", params = {\"url\": url}\n).json()\n\nm = Map(center=(0, 0), zoom=1, basemap={}, crs=projections.EPSG4326)\n\nlayer = TileLayer(url=r[\"tiles\"][0], opacity=1)\nm.add_layer(layer)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/WorldCRS84Quad/tilejson.json\", params = {\"url\": url} ).json() m = Map(center=(0, 0), zoom=1, basemap={}, crs=projections.EPSG4326) layer = TileLayer(url=r[\"tiles\"][0], opacity=1) m.add_layer(layer) m In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/cog/EuropeanETRS89_LAEAQuad/tilejson.json\", params = {\"url\": url}\n).json()\n\nmy_projection = {\n 'name': 'EPSG:3035',\n 'custom': True, #This is important, it tells ipyleaflet that this projection is not on the predefined ones.\n 'proj4def': '+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',\n 'origin': [6500000.0, 5500000.0],\n 'resolutions': [\n 8192.0,\n 4096.0,\n 2048.0,\n 1024.0,\n 512.0,\n 256.0\n ]\n}\n\nm = Map(center=(50, 65), zoom=0, basemap={}, crs=my_projection)\n\nlayer = TileLayer(url=r[\"tiles\"][0], opacity=1)\nm.add_layer(layer)\nm\nr = httpx.get( f\"{titiler_endpoint}/cog/EuropeanETRS89_LAEAQuad/tilejson.json\", params = {\"url\": url} ).json() my_projection = { 'name': 'EPSG:3035', 'custom': True, #This is important, it tells ipyleaflet that this projection is not on the predefined ones. 'proj4def': '+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs', 'origin': [6500000.0, 5500000.0], 'resolutions': [ 8192.0, 4096.0, 2048.0, 1024.0, 512.0, 256.0 ] } m = Map(center=(50, 65), zoom=0, basemap={}, crs=my_projection) layer = TileLayer(url=r[\"tiles\"][0], opacity=1) m.add_layer(layer) m In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_nonWebMercatorTMS/#working-with-tilematrixsets-other-than-webmercator","title":"Working With TileMatrixSets (other than WebMercator)\u00b6","text":"
TiTiler has builtin support for serving tiles in multiple Projections by using rio-tiler and morecantile.
"},{"location":"examples/notebooks/Working_with_nonWebMercatorTMS/#requirements","title":"Requirements\u00b6","text":"https://epsg.io/3857
"},{"location":"examples/notebooks/Working_with_nonWebMercatorTMS/#wgs-84-wgs84-world-geodetic-system-1984-epsg4326","title":"WGS 84 -- WGS84 - World Geodetic System 1984 - EPSG:4326\u00b6","text":"https://epsg.io/4326
"},{"location":"examples/notebooks/Working_with_nonWebMercatorTMS/#etrs89-extended-laea-europe-epsg3035","title":"ETRS89-extended / LAEA Europe - EPSG:3035\u00b6","text":"https://epsg.io/3035
"}]}