{"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.

"},{"location":"#features","title":"Features","text":""},{"location":"#packages","title":"Packages","text":"

Starting with version 0.3.0, the TiTiler python module has been split into a set of python namespace packages: titiler.{package}.

Package Version Description titiler.core The 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

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.

"},{"location":"dynamic_tiling/#static-tiling","title":"Static tiling","text":"

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.

"},{"location":"dynamic_tiling/#pro","title":"Pro","text":""},{"location":"dynamic_tiling/#cons","title":"Cons","text":""},{"location":"dynamic_tiling/#dynamic-tiling","title":"Dynamic tiling","text":"

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.

"},{"location":"dynamic_tiling/#pro_1","title":"Pro","text":""},{"location":"dynamic_tiling/#cons_1","title":"Cons","text":""},{"location":"dynamic_tiling/#summary","title":"Summary","text":"

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":""},{"location":"external_links/#titiler-extensionsplugins","title":"TiTiler extensions/plugins","text":""},{"location":"external_links/#projects-demo-using-titiler","title":"Projects / Demo using TiTiler","text":""},{"location":"external_links/#conferences-presentations-videos","title":"Conferences / presentations / videos","text":""},{"location":"intro/","title":"Intro","text":"

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.

"},{"location":"intro/#default-application","title":"Default Application","text":"

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:

"},{"location":"intro/#settings","title":"Settings","text":"

The default application can be customized using environment variables defined in titiler.application.settings.ApiSettings class. Each variable needs to be prefixed with TITILER_API_.

"},{"location":"intro/#customized-minimal-app","title":"Customized, minimal app","text":"

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":""},{"location":"output_format/","title":"Output data format","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:

"},{"location":"output_format/#numpytile","title":"NumpyTile","text":"

While .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).

"},{"location":"release-notes/","title":"Release Notes","text":""},{"location":"release-notes/#unreleased","title":"Unreleased","text":""},{"location":"release-notes/#0185-2024-07-03","title":"0.18.5 (2024-07-03)","text":""},{"location":"release-notes/#0184-2024-06-26","title":"0.18.4 (2024-06-26)","text":""},{"location":"release-notes/#0183-2024-05-20","title":"0.18.3 (2024-05-20)","text":""},{"location":"release-notes/#0182-2024-05-07","title":"0.18.2 (2024-05-07)","text":""},{"location":"release-notes/#0181-2024-04-12","title":"0.18.1 (2024-04-12)","text":""},{"location":"release-notes/#titilercore","title":"titiler.core","text":""},{"location":"release-notes/#0180-2024-03-22","title":"0.18.0 (2024-03-22)","text":""},{"location":"release-notes/#titilercore_1","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic","title":"titiler.mosaic","text":""},{"location":"release-notes/#misc","title":"Misc","text":""},{"location":"release-notes/#0173-2024-03-21","title":"0.17.3 (2024-03-21)","text":""},{"location":"release-notes/#titilerapplication","title":"titiler.application","text":""},{"location":"release-notes/#0172-2024-03-15","title":"0.17.2 (2024-03-15)","text":""},{"location":"release-notes/#titilercore_2","title":"titiler.core","text":""},{"location":"release-notes/#0171-2024-03-13","title":"0.17.1 (2024-03-13)","text":""},{"location":"release-notes/#titilercore_3","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_1","title":"titiler.application","text":""},{"location":"release-notes/#0170-2024-01-17","title":"0.17.0 (2024-01-17)","text":""},{"location":"release-notes/#titilercore_4","title":"titiler.core","text":""},{"location":"release-notes/#0162-2024-01-17","title":"0.16.2 (2024-01-17)","text":""},{"location":"release-notes/#titilercore_5","title":"titiler.core","text":""},{"location":"release-notes/#0161-2024-01-08","title":"0.16.1 (2024-01-08)","text":""},{"location":"release-notes/#titilercore_6","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_1","title":"titiler.mosaic","text":""},{"location":"release-notes/#0160-2024-01-08","title":"0.16.0 (2024-01-08)","text":""},{"location":"release-notes/#titilercore_7","title":"titiler.core","text":""},{"location":"release-notes/#titilerextensions","title":"titiler.extensions","text":""},{"location":"release-notes/#titilerapplication_2","title":"titiler.application","text":""},{"location":"release-notes/#0158-2024-01-08","title":"0.15.8 (2024-01-08)","text":""},{"location":"release-notes/#titilercore_8","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_2","title":"titiler.mosaic","text":""},{"location":"release-notes/#0157-2024-01-08","title":"0.15.7 (2024-01-08)","text":""},{"location":"release-notes/#titilercore_9","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_3","title":"titiler.application","text":""},{"location":"release-notes/#0156-2023-11-16","title":"0.15.6 (2023-11-16)","text":""},{"location":"release-notes/#titilercore_10","title":"titiler.core","text":""},{"location":"release-notes/#0155-2023-11-09","title":"0.15.5 (2023-11-09)","text":""},{"location":"release-notes/#titilercore_11","title":"titiler.core","text":""},{"location":"release-notes/#0154-2023-11-06","title":"0.15.4 (2023-11-06)","text":""},{"location":"release-notes/#titilercore_12","title":"titiler.core","text":""},{"location":"release-notes/#0153-2023-11-02","title":"0.15.3 (2023-11-02)","text":""},{"location":"release-notes/#0152-2023-10-23","title":"0.15.2 (2023-10-23)","text":""},{"location":"release-notes/#titilercore_13","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_3","title":"titiler.mosaic","text":""},{"location":"release-notes/#cdk-application","title":"cdk application","text":""},{"location":"release-notes/#0151-2023-10-17","title":"0.15.1 (2023-10-17)","text":""},{"location":"release-notes/#0150-2023-09-28","title":"0.15.0 (2023-09-28)","text":""},{"location":"release-notes/#titilercore_14","title":"titiler.core","text":"

breaking changes

"},{"location":"release-notes/#0141-2023-09-14","title":"0.14.1 (2023-09-14)","text":""},{"location":"release-notes/#titilerextension","title":"titiler.extension","text":""},{"location":"release-notes/#0140-2023-08-30","title":"0.14.0 (2023-08-30)","text":""},{"location":"release-notes/#titilercore_15","title":"titiler.core","text":""},{"location":"release-notes/#titilerextension_1","title":"titiler.extension","text":""},{"location":"release-notes/#0133-2023-08-27","title":"0.13.3 (2023-08-27)","text":""},{"location":"release-notes/#0132-2023-08-24","title":"0.13.2 (2023-08-24)","text":""},{"location":"release-notes/#titilerextensions_1","title":"titiler.extensions","text":""},{"location":"release-notes/#0131-2023-08-21","title":"0.13.1 (2023-08-21)","text":""},{"location":"release-notes/#titilercore_16","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_4","title":"titiler.application","text":""},{"location":"release-notes/#0130-2023-07-27","title":"0.13.0 (2023-07-27)","text":""},{"location":"release-notes/#titilercore_17","title":"titiler.core","text":""},{"location":"release-notes/#titilerextension_2","title":"titiler.extension","text":""},{"location":"release-notes/#titilermosaic_4","title":"titiler.mosaic","text":""},{"location":"release-notes/#titilerapplication_5","title":"titiler.application","text":""},{"location":"release-notes/#0120-2023-07-17","title":"0.12.0 (2023-07-17)","text":""},{"location":"release-notes/#titilercore_18","title":"titiler.core","text":""},{"location":"release-notes/#titilerextensions_2","title":"titiler.extensions","text":""},{"location":"release-notes/#titilermosaic_5","title":"titiler.mosaic","text":""},{"location":"release-notes/#0117-2023-05-18","title":"0.11.7 (2023-05-18)","text":""},{"location":"release-notes/#titilercore_19","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_6","title":"titiler.mosaic","text":""},{"location":"release-notes/#titilerextensions_3","title":"titiler.extensions","text":""},{"location":"release-notes/#0116-2023-04-14","title":"0.11.6 (2023-04-14)","text":""},{"location":"release-notes/#0115-2023-03-22","title":"0.11.5 (2023-03-22)","text":""},{"location":"release-notes/#0114-2023-03-20","title":"0.11.4 (2023-03-20)","text":""},{"location":"release-notes/#0113-2023-03-14","title":"0.11.3 (2023-03-14)","text":""},{"location":"release-notes/#0112-2023-03-08","title":"0.11.2 (2023-03-08)","text":""},{"location":"release-notes/#0111-2023-03-01","title":"0.11.1 (2023-03-01)","text":""},{"location":"release-notes/#0111a0-2023-03-01","title":"0.11.1a0 (2023-03-01)","text":""},{"location":"release-notes/#titilercore_20","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_7","title":"titiler.mosaic","text":""},{"location":"release-notes/#0110-2023-01-27","title":"0.11.0 (2023-01-27)","text":""},{"location":"release-notes/#titilermosaic_8","title":"titiler.mosaic","text":"

breaking change

"},{"location":"release-notes/#titilercore_21","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_6","title":"titiler.application","text":""},{"location":"release-notes/#titilermosaic_9","title":"titiler.mosaic","text":""},{"location":"release-notes/#0102-2022-12-17","title":"0.10.2 (2022-12-17)","text":""},{"location":"release-notes/#0101-2022-12-15","title":"0.10.1 (2022-12-15)","text":""},{"location":"release-notes/#0100-2022-12-09","title":"0.10.0 (2022-12-09)","text":"

breaking change

"},{"location":"release-notes/#titilercore_22","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_7","title":"titiler.application","text":""},{"location":"release-notes/#090-2022-12-05","title":"0.9.0 (2022-12-05)","text":""},{"location":"release-notes/#titilercore_23","title":"titiler.core","text":""},{"location":"release-notes/#081-2022-12-01","title":"0.8.1 (2022-12-01)","text":""},{"location":"release-notes/#titilercore_24","title":"titiler.core","text":""},{"location":"release-notes/#080-2022-12-01","title":"0.8.0 (2022-12-01)","text":""},{"location":"release-notes/#titilercore_25","title":"titiler.core","text":"

breaking changes

"},{"location":"release-notes/#titilermosaic_10","title":"titiler.mosaic","text":"

breaking changes

"},{"location":"release-notes/#titilerapplication_8","title":"titiler.application","text":"

breaking changes

"},{"location":"release-notes/#071-2022-09-21","title":"0.7.1 (2022-09-21)","text":""},{"location":"release-notes/#titilermosaic_11","title":"titiler.mosaic","text":""},{"location":"release-notes/#titilerapplication_9","title":"titiler.application","text":""},{"location":"release-notes/#helm-charts","title":"Helm charts","text":""},{"location":"release-notes/#070-2022-06-08","title":"0.7.0 (2022-06-08)","text":"
# 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":""},{"location":"release-notes/#060-2022-05-13","title":"0.6.0 (2022-05-13)","text":""},{"location":"release-notes/#060a2-2022-05-11","title":"0.6.0a2 (2022-05-11)","text":""},{"location":"release-notes/#060a1-2022-05-11","title":"0.6.0a1 (2022-05-11)","text":""},{"location":"release-notes/#titilercore_26","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_12","title":"titiler.mosaic","text":""},{"location":"release-notes/#titilerapplication_11","title":"titiler.application","text":""},{"location":"release-notes/#051-2022-03-07","title":"0.5.1 (2022-03-07)","text":""},{"location":"release-notes/#050-2022-02-22","title":"0.5.0 (2022-02-22)","text":"

breaking change

# before\nexpression = \"b1+b2,b2\"\n\n# new\nexpression = \"b1+b2;b2\"\n
# 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":""},{"location":"release-notes/#042-2022-01-25","title":"0.4.2 (2022-01-25)","text":""},{"location":"release-notes/#titilercore_27","title":"titiler.core","text":""},{"location":"release-notes/#041-2022-01-25","title":"0.4.1 (2022-01-25)","text":""},{"location":"release-notes/#titilercore_28","title":"titiler.core","text":""},{"location":"release-notes/#k8s","title":"k8s","text":""},{"location":"release-notes/#040-2021-11-30","title":"0.4.0 (2021-11-30)","text":""},{"location":"release-notes/#titilercore_29","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_13","title":"titiler.mosaic","text":""},{"location":"release-notes/#040a2-2021-11-24","title":"0.4.0a2 (2021-11-24)","text":""},{"location":"release-notes/#titilercore_30","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_14","title":"titiler.mosaic","text":""},{"location":"release-notes/#040a1-2021-11-12","title":"0.4.0a1 (2021-11-12)","text":""},{"location":"release-notes/#040a0-2021-11-12","title":"0.4.0a0 (2021-11-12)","text":""},{"location":"release-notes/#titilercore_31","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_15","title":"titiler.mosaic","text":""},{"location":"release-notes/#titilerapplication_12","title":"titiler.application","text":""},{"location":"release-notes/#0312-2021-10-20","title":"0.3.12 (2021-10-20)","text":""},{"location":"release-notes/#titilercore_32","title":"titiler.core","text":""},{"location":"release-notes/#0311-2021-10-07","title":"0.3.11 (2021-10-07)","text":""},{"location":"release-notes/#titilerapplication_13","title":"titiler.application","text":""},{"location":"release-notes/#0310-2021-09-23","title":"0.3.10 (2021-09-23)","text":""},{"location":"release-notes/#titilercore_33","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_14","title":"titiler.application","text":""},{"location":"release-notes/#039-2021-09-07","title":"0.3.9 (2021-09-07)","text":""},{"location":"release-notes/#titilercore_34","title":"titiler.core","text":""},{"location":"release-notes/#038-2021-09-02","title":"0.3.8 (2021-09-02)","text":""},{"location":"release-notes/#titilercore_35","title":"titiler.core","text":""},{"location":"release-notes/#037-2021-09-01","title":"0.3.7 (2021-09-01)","text":""},{"location":"release-notes/#titilercore_36","title":"titiler.core","text":""},{"location":"release-notes/#036-2021-08-23","title":"0.3.6 (2021-08-23)","text":""},{"location":"release-notes/#titilercore_37","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_15","title":"titiler.application","text":""},{"location":"release-notes/#035-2021-08-17","title":"0.3.5 (2021-08-17)","text":""},{"location":"release-notes/#titilermosaic_16","title":"titiler.mosaic","text":""},{"location":"release-notes/#034-2021-08-02-not-published-on-pypi-355","title":"0.3.4 (2021-08-02) - Not published on PyPi #355","text":""},{"location":"release-notes/#titilercore_38","title":"titiler.core","text":""},{"location":"release-notes/#titilerapplication_16","title":"titiler.application","text":""},{"location":"release-notes/#033-2021-06-29-not-published-on-pypi-355","title":"0.3.3 (2021-06-29) - Not published on PyPi #355","text":""},{"location":"release-notes/#titilercore_39","title":"titiler.core","text":""},{"location":"release-notes/#titilermosaic_17","title":"titiler.mosaic","text":""},{"location":"release-notes/#titilerapplication_17","title":"titiler.application","text":""},{"location":"release-notes/#code-and-repo","title":"code and repo","text":""},{"location":"release-notes/#032-2021-05-26","title":"0.3.2 (2021-05-26)","text":""},{"location":"release-notes/#titilercore_40","title":"titiler.core","text":"
# 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":""},{"location":"release-notes/#titilerapplication_18","title":"titiler.application","text":""},{"location":"release-notes/#031-2021-04-27","title":"0.3.1 (2021-04-27)","text":""},{"location":"release-notes/#030-2021-04-19","title":"0.3.0 (2021-04-19)","text":"

breaking change

"},{"location":"release-notes/#020-2021-03-09","title":"0.2.0 (2021-03-09)","text":"

breaking change

"},{"location":"release-notes/#010-2021-02-17","title":"0.1.0 (2021-02-17)","text":"

breaking change

"},{"location":"release-notes/#010a14-2021-01-05","title":"0.1.0a14 (2021-01-05)","text":""},{"location":"release-notes/#010a13-2020-12-20","title":"0.1.0a13 (2020-12-20)","text":""},{"location":"release-notes/#010a12-2020-11-18","title":"0.1.0a12 (2020-11-18)","text":"
/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":""},{"location":"release-notes/#010a10-2020-11-09","title":"0.1.0a10 (2020-11-09)","text":""},{"location":"release-notes/#010a9-2020-10-26","title":"0.1.0a9 (2020-10-26)","text":""},{"location":"release-notes/#010a8-2020-10-13","title":"0.1.0a8 (2020-10-13)","text":"

breaking changes

Note: We changed the versioning scheme to {major}.{minor}.{path}{pre}{prenum}

"},{"location":"release-notes/#010-alpha7-2020-10-13","title":"0.1.0-alpha.7 (2020-10-13)","text":""},{"location":"release-notes/#010-alpha6-2020-10-05","title":"0.1.0-alpha.6 (2020-10-05)","text":"
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
"},{"location":"release-notes/#010-alpha5-2020-09-22","title":"0.1.0-alpha.5 (2020-09-22)","text":"

breaking 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
"},{"location":"release-notes/#010-alpha4-2020-09-14","title":"0.1.0-alpha.4 (2020-09-14)","text":""},{"location":"release-notes/#010-alpha3-2020-09-03","title":"0.1.0-alpha.3 (2020-09-03)","text":"

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/*\")

"},{"location":"release-notes/#010-alpha2-2020-09-01","title":"0.1.0-alpha.2 (2020-09-01)","text":""},{"location":"release-notes/#010-alpha1-2020-09-01","title":"0.1.0-alpha.1 (2020-09-01)","text":"

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":""},{"location":"release-notes/#breaking-changes","title":"Breaking 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":"

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":"

link: developmentseed/titiler@8b63fc6

"},{"location":"release-notes/#200-2020-06-09","title":"2.0.0 (2020-06-09)","text":"

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:

"},{"location":"advanced/Algorithms/#usage","title":"Usage","text":"

# 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:

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":""},{"location":"advanced/Extensions/#cogviewerextension","title":"cogViewerExtension","text":""},{"location":"advanced/Extensions/#stacviewerextension","title":"stacViewerExtension","text":""},{"location":"advanced/Extensions/#stacextension","title":"stacExtension","text":""},{"location":"advanced/Extensions/#wmsextension","title":"wmsExtension","text":""},{"location":"advanced/Extensions/#how-to","title":"How To","text":""},{"location":"advanced/Extensions/#use-extensions","title":"Use extensions","text":"

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.

"},{"location":"advanced/customization/#custom-colormap","title":"Custom Colormap","text":"

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

"},{"location":"advanced/customization/#custom-tms","title":"Custom TMS","text":"
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.

"},{"location":"advanced/dependencies/#assetsparams","title":"AssetsParams","text":"

Define assets.

Name Type Required Default assets Query (str) No None
@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.

Name Type Required Default assets Query (str) No None asset_indexes Query (str) No None asset_expression Query (str) No False
@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.

Name Type Required Default assets Query (str) No* None expression Query (str) No* None asset_indexes Query (str) No None asset_as_band Query (bool) No False

* 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.

Name Type Required Default assets Query (str) No None expression Query (str) No None asset_indexes Query (str) No None asset_as_band Query (bool) No False
@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.

Name Type Required Default bands Query (str) No None
@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.

Name Type Required Default bands Query (str) No* None expression Query (str) No* None

* 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.

Name Type Required Default bands Query (str) No None expression Query (str) No None
@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 None
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":"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 None
cmap = {}\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 None
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":"advanced/dependencies/#datasetparams","title":"DatasetParams","text":"

Overwrite nodata value, apply rescaling and change the I/O or Warp resamplings.

Name Type Required Default nodata Query (str, int, float) No None unscale Query (bool) No False resampling Query (str) No 'nearest' reproject Query (str) No 'nearest'
@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 None
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":"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.

Name Type Required Default max_size Query (int) No None height Query (int) No None width Query (int) No None
@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.

Name Type Required Default pixel_selection Query (str) No 'first'
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 None
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        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.

Name Type Required Default buffer Query (float) No None padding Query (int) No None
@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 None
algorithms = {}\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":""},{"location":"advanced/endpoints_factories/#attributes","title":"Attributes","text":""},{"location":"advanced/endpoints_factories/#tilerfactory","title":"TilerFactory","text":"

class: titiler.core.factory.TilerFactory

Factory meant to create endpoints for single dataset using rio-tiler's Reader.

"},{"location":"advanced/endpoints_factories/#attributes_1","title":"Attributes","text":""},{"location":"advanced/endpoints_factories/#endpoints","title":"Endpoints","text":"
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).

"},{"location":"advanced/endpoints_factories/#attributes_2","title":"Attributes","text":""},{"location":"advanced/endpoints_factories/#endpoints_1","title":"Endpoints","text":"
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.

"},{"location":"advanced/endpoints_factories/#attributes_3","title":"Attributes","text":""},{"location":"advanced/endpoints_factories/#endpoints_2","title":"Endpoints","text":"
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":""},{"location":"advanced/endpoints_factories/#endpoints_3","title":"Endpoints","text":"Method URL Output Description 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.

"},{"location":"advanced/endpoints_factories/#attributes_5","title":"Attributes","text":"
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":"
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":"
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.

"},{"location":"advanced/performance_tuning/#gdal_disable_readdir_on_open","title":"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.

"},{"location":"advanced/performance_tuning/#cpl_vsil_curl_allowed_extensions","title":"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

"},{"location":"advanced/performance_tuning/#gdal_ingested_bytes_at_open","title":"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.

"},{"location":"advanced/performance_tuning/#gdal_cachemax","title":"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

"},{"location":"advanced/performance_tuning/#proj_network","title":"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.

"},{"location":"advanced/performance_tuning/#gdal_data","title":"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.

"},{"location":"advanced/performance_tuning/#proj_lib","title":"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.

"},{"location":"advanced/performance_tuning/#aws-configuration","title":"AWS Configuration","text":""},{"location":"advanced/performance_tuning/#aws_request_payer","title":"AWS_REQUEST_PAYER","text":""},{"location":"advanced/performance_tuning/#recommended-configuration-for-dynamic-tiling","title":"Recommended Configuration for dynamic tiling","text":"

In addition to GDAL_DISABLE_READDIR_ON_OPEN, we set the allowed extensions to .tif to only enable tif files. (OPTIONAL)

200 Mb Cache.

200 Mb VSI Cache.

Maybe the most important variable. Setting it to EMPTY_DIR reduce the number of GET/LIST requests.

Tells GDAL to merge consecutive range GET requests.

Both Multiplex and HTTP_VERSION will only have impact if the files are stored in an environment which support HTTP 2 (e.g cloudfront).

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:

  1. Adjust band values using basic color-oriented image operations
  2. Apply color maps to create heat maps, colorful terrain based on band value
  3. Rescale images on a per-band basis
"},{"location":"advanced/rendering/#color-map","title":"Color Map","text":"

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.

"},{"location":"advanced/rendering/#custom-colormaps","title":"Custom Colormaps","text":"

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):

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":"AssetsBidxExprParams dataclass","text":"

Bases: AssetsParams, BidxParams

Assets, Expression and Asset's band Indexes parameters.

Source code in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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 in titiler/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":"AlgorithmFactory dataclass","text":"

Algorithm endpoints Factory.

"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseTilerFactory","title":"BaseTilerFactory dataclass","text":"

BaseTiler Factory.

Abstract Base Class which defines most inputs used by dynamic tiler.

Attributes:

"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseTilerFactory.add_route_dependencies","title":"add_route_dependencies","text":"
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_routes abstractmethod","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":"ColorMapFactory dataclass","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":"FactoryExtension dataclass","text":"

Factory Extension.

"},{"location":"api/titiler/core/factory/#titiler.core.factory.FactoryExtension.register","title":"register abstractmethod","text":"
register(factory: BaseTilerFactory)\n

Register extension to the factory.

"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBandTilerFactory","title":"MultiBandTilerFactory dataclass","text":"

Bases: TilerFactory

Custom Tiler Factory for MultiBandReader classes.

Note

To 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":"MultiBaseTilerFactory dataclass","text":"

Bases: TilerFactory

Custom Tiler Factory for MultiBaseReader classes.

Note

To 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.

"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBaseTilerFactory.info","title":"info","text":"
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":"TMSFactory dataclass","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":"TilerFactory dataclass","text":"

Bases: BaseTilerFactory

Tiler Factory.

Attributes:

"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.bounds","title":"bounds","text":"
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.

"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.point","title":"point","text":"
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:

"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.LoggerMiddleware","title":"LoggerMiddleware","text":"

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:

"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.LowerCaseQueryStringMiddleware","title":"LowerCaseQueryStringMiddleware","text":"

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:

"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.TotalTimeMiddleware","title":"TotalTimeMiddleware","text":"

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:

"},{"location":"api/titiler/core/routing/","title":"routing","text":""},{"location":"api/titiler/core/routing/#titiler.core.routing","title":"titiler.core.routing","text":"

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

"},{"location":"api/titiler/core/models/responses/#titiler.core.models.responses.StatisticsInGeoJSON","title":"StatisticsInGeoJSON","text":"

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":"cogValidateExtension dataclass","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":"stacExtension dataclass","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":"cogViewerExtension dataclass","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":"stacViewerExtension dataclass","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":"MosaicTilerFactory dataclass","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).

"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.assets","title":"assets","text":"
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

"},{"location":"deployment/azure/","title":"Azure","text":""},{"location":"deployment/azure/#function","title":"Function","text":"

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":""},{"location":"deployment/azure/#deployment","title":"Deployment","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":""},{"location":"deployment/k8s/","title":"k8s / Helm Deployment","text":""},{"location":"deployment/k8s/#k8s-helm-deployment","title":"k8s / Helm Deployment","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.

  1. 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
  2. Generate CloudFormation template

    $ npm run cdk -- synth  # Synthesizes and prints the CloudFormation template for this stack\n
  3. 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
  4. Deploy

    # Deploys the stack(s) mytiler-ecs-dev in cdk/app.py\n$ npm run cdk -- deploy mytiler-ecs-dev\n
"},{"location":"deployment/aws/intro/","title":"Amazon Web Services deployments","text":"

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.

  1. 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
  2. Pre-Generate CFN template

    $ npm run cdk -- synth  # Synthesizes and prints the CloudFormation template for this stack\n
  3. 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
  4. 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
"},{"location":"deployment/aws/sam/","title":"AWS Serverless Application (SAM)","text":"

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.

"},{"location":"endpoints/algorithms/#algorithms","title":"Algorithms","text":""},{"location":"endpoints/algorithms/#api","title":"API","text":"Method URL Output Description 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.

"},{"location":"endpoints/cog/#cloud-optimized-geotiff","title":"Cloud Optimized GeoTIFF","text":"

The /cog routes are based on titiler.core.factory.TilerFactory but with cogValidateExtension and cogViewerExtension extensions.

"},{"location":"endpoints/cog/#api","title":"API","text":"Method URL Output Description 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}]

Example:

"},{"location":"endpoints/cog/#preview","title":"Preview","text":"

:endpoint:/cog/preview[.{format}]

Important

if height and width are provided max_size will be ignored.

Example:

"},{"location":"endpoints/cog/#bboxfeature","title":"BBOX/Feature","text":"

:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}.{format}

:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}

Important

if height and width are provided max_size will be ignored.

Example:

:endpoint:/cog/feature[/{width}x{height}][].{format}] - [POST]

Important

if height and width are provided max_size will be ignored.

Example:

Note: if height and width are provided max_size will be ignored.

"},{"location":"endpoints/cog/#point","title":"Point","text":"

:endpoint:/cog/point/{lon},{lat}

Example:

"},{"location":"endpoints/cog/#tilesjson","title":"TilesJSON","text":"

:endpoint:/cog/{tileMatrixSetId}/tilejson.json tileJSON document

Example:

"},{"location":"endpoints/cog/#map","title":"Map","text":"

:endpoint:/cog/{tileMatrixSetId}/map Simple viewer

Example:

"},{"location":"endpoints/cog/#bounds","title":"Bounds","text":"

:endpoint:/cog/bounds general image bounds

Example:

"},{"location":"endpoints/cog/#info","title":"Info","text":"

:endpoint:/cog/info general raster info

:endpoint:/cog/info.geojson general raster info as a GeoJSON feature

Example:

"},{"location":"endpoints/cog/#statistics","title":"Statistics","text":"

Advanced raster statistics

:endpoint:/cog/statistics - [GET]

Example:

:endpoint:/cog/statistics - [POST]

Example:

"},{"location":"endpoints/cog/#viewer","title":"Viewer","text":"

:endpoint:/cog/viewer - COG Viewer

Example:

"},{"location":"endpoints/cog/#validate","title":"Validate","text":"

:endpoint:/cog/validate - COG Viewer

Example:

"},{"location":"endpoints/cog/#stac","title":"Stac","text":"

:endpoint:/cog/stac - Create STAC Item

Example:

"},{"location":"endpoints/colormaps/","title":"/colormaps","text":"

In addition to the /cog, /stac and /mosaicjson endpoints, the titiler.application package FastAPI application commes with additional metadata endpoints.

"},{"location":"endpoints/colormaps/#algorithms","title":"Algorithms","text":""},{"location":"endpoints/colormaps/#api","title":"API","text":"Method URL Output Description 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

$ 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.

"},{"location":"endpoints/mosaic/#mosaicjson","title":"MosaicJSON","text":"

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.

"},{"location":"endpoints/mosaic/#api","title":"API","text":"Method URL Output Description 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.

"},{"location":"endpoints/stac/#spatiotemporal-asset-catalog","title":"SpatioTemporal Asset Catalog","text":"

The /stac routes are based on titiler.core.factory.MultiBaseTilerFactory but with stacViewerExtension extension.

"},{"location":"endpoints/stac/#api","title":"API","text":"Method URL Output Description 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}]

Important

assets OR expression is required

Example:

"},{"location":"endpoints/stac/#preview","title":"Preview","text":"

:endpoint:/stac/preview[.{format}]

Important

Example:

"},{"location":"endpoints/stac/#bboxfeature","title":"BBOX/Feature","text":"

:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}.{format}

:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}

Important

Example:

:endpoint:/stac/feature[/{width}x{height}][].{format}] - [POST]

Important

Example:

"},{"location":"endpoints/stac/#point","title":"Point","text":"

:endpoint:/cog/point/{lon},{lat}

Important

assets OR expression is required

Example:

"},{"location":"endpoints/stac/#tilesjson","title":"TilesJSON","text":"

:endpoint:/stac/{tileMatrixSetId}/tilejson.json tileJSON document

Important

assets OR expression is required

Example:

"},{"location":"endpoints/stac/#map","title":"Map","text":"

:endpoint:/stac/{tileMatrixSetId}/map Simple viewer

Important

assets OR expression is required

Example:

"},{"location":"endpoints/stac/#bounds","title":"Bounds","text":"

:endpoint:/stac/bounds - Return the bounds of the STAC item.

Example:

"},{"location":"endpoints/stac/#info","title":"Info","text":"

:endpoint:/stac/info - Return basic info on STAC item's COG.

Example:

:endpoint:/stac/info.geojson - Return basic info on STAC item's COG as a GeoJSON feature

Example:

:endpoint:/stac/assets - Return the list of available assets

Example:

"},{"location":"endpoints/stac/#statistics","title":"Statistics","text":"

:endpoint:/stac/asset_statistics - [GET]

Example:

:endpoint:/stac/statistics - [GET]

Example:

:endpoint:/stac/statistics - [POST]

Example:

"},{"location":"endpoints/stac/#viewer","title":"Viewer","text":"

:endpoint:/stac/viewer - STAC viewer

Example:

"},{"location":"endpoints/tms/","title":"/tileMatrixSets","text":"

In addition to the /cog, /stac and /mosaicjson endpoints, the titiler.application package FastAPI application comes with additional metadata endpoints.

"},{"location":"endpoints/tms/#tilematrixsets","title":"TileMatrixSets","text":""},{"location":"endpoints/tms/#api","title":"API","text":"Method URL Output Description 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)

"},{"location":"examples/code/mini_cog_tiler/","title":"Minimal COG Tiler","text":"

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
  1. Run and Use
$ uvicorn app:app --reload\n\n$ curl http://127.0.0.1:8000/tilejson.json?url=cog1.tif,cog2.tif\n

Gotcha

"},{"location":"examples/code/tiler_for_sentinel2/","title":"Custom Sentinel 2 Tiler","text":"

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":"
  1. 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

  2. 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

  3. 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:

"},{"location":"examples/code/working_with_signed_urls/#1-url-encoding","title":"1. URL Encoding","text":""},{"location":"examples/code/working_with_signed_urls/#11-full-url","title":"1.1 Full URL","text":"

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\n
import json import httpx from folium import Map, TileLayer In\u00a0[\u00a0]: Copied!
titiler_endpoint = \"https://titiler.xyz\"  # Developmentseed Demo endpoint. Please be kind.\n
titiler_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\"\n
url = \"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\n
r = 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\n
r = 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\n
r = 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\n
r = 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\n
r = 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

"},{"location":"examples/notebooks/Working_with_Algorithm/#get-cog-info","title":"Get COG Info\u00b6","text":""},{"location":"examples/notebooks/Working_with_Algorithm/#display-tiles","title":"Display Tiles\u00b6","text":"

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\n
import 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.\n
titiler_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\n
m = 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)\")\n
session = 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])\n
print(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))\n
files_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)\n
files_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}\"\n
def _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\n
r = 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\n
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() 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()\n
with 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()\n
with 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

"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#define-your-area-of-interest-aoi","title":"Define your area of interest (AOI)\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#list-available-files-on-aws-s3","title":"List available files on AWS S3\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#data-endpoint","title":"DATA Endpoint\u00b6","text":"

{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}

"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#visualize-one-item","title":"Visualize One Item\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#create-time-series-of-no2","title":"Create time series of NO2\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#get-no2-max-for-day-15th-of-each-month","title":"Get NO2 Max for day 15th of each month\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#same-but-for-all-the-days-for-the-last-16-years","title":"Same but for all the days for the last 16 years\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/","title":"Working With COG","text":"In\u00a0[\u00a0]: Copied!
# 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\n
import 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\"\n
titiler_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\n
r = 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)\n
url = \"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\n
r = 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\n
r = 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\n
import 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

"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#requirements","title":"Requirements\u00b6","text":"

pip install folium httpx

"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#get-cog-info","title":"Get COG Info\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#get-cog-metadata","title":"Get COG Metadata\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#display-tiles","title":"Display Tiles\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#work-with-non-byte-data","title":"Work with non-byte data\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#display-tiles","title":"Display Tiles\u00b6","text":"

Note: By default if the metadata has min/max statistics, titiler will use those to rescale the data

"},{"location":"examples/notebooks/Working_with_MosaicJSON/","title":"Working With MosaicJSON","text":"In\u00a0[\u00a0]: Copied!
# 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\n
import 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)}\")\n
session = 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)\n
print(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\n
geojson = {'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)\n
from 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\n
titiler_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

"},{"location":"examples/notebooks/Working_with_MosaicJSON/#get-the-data","title":"Get the Data\u00b6","text":""},{"location":"examples/notebooks/Working_with_MosaicJSON/#1-fetch-and-parse-page","title":"1. Fetch and parse page\u00b6","text":""},{"location":"examples/notebooks/Working_with_MosaicJSON/#2-create-features-and-viz-optional","title":"2. Create Features and Viz (Optional)\u00b6","text":"

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\n
import 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\"\n
titiler_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)\n
r = 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)\n
print(tile.shape) In\u00a0[\u00a0]: Copied!
print(mask.shape)\n
print(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

"},{"location":"examples/notebooks/Working_with_STAC/","title":"Working With STAC - At Scale","text":"In\u00a0[\u00a0]: Copied!
# 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\n
import 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\n
geojson = { \"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
  1. Define dates and other filters
In\u00a0[\u00a0]: Copied!
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\"]]\n
start = 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\n
m = 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()\n
fig = 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}\"\n
url_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\n
r = 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\n
r = 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()\n
def 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()\n
stats = [_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

"},{"location":"examples/notebooks/Working_with_STAC/#requirements","title":"Requirements\u00b6","text":"

To be able to run this notebook you'll need the following requirements:

!pip install rasterio folium httpx tqdm

"},{"location":"examples/notebooks/Working_with_STAC/#search-for-stac-items","title":"Search for STAC Items\u00b6","text":"

See https://github.com/radiantearth/stac-api-spec for more documentation about the stac API

  1. AOI

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}

"},{"location":"examples/notebooks/Working_with_STAC/#visualize-one-item","title":"Visualize One Item\u00b6","text":""},{"location":"examples/notebooks/Working_with_STAC/#more","title":"More\u00b6","text":"

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\n
import 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\"\n
titiler_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)\n
item = 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\"])\n
for 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\n
bounds = 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\n
r = 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.

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_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

"},{"location":"examples/notebooks/Working_with_STAC_simple/#display-one-asset","title":"Display one asset\u00b6","text":""},{"location":"examples/notebooks/Working_with_STAC_simple/#select-indexes-for-assets","title":"Select Indexes for assets\u00b6","text":""},{"location":"examples/notebooks/Working_with_Statistics/","title":"Working with Statistics","text":"In\u00a0[9]: Copied!
# 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)\n
r = 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:

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))\n
r = 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}\n
In\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))\n
r = 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:

In\u00a0[13]: Copied!
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))\n
r = 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)

In\u00a0[14]: Copied!
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\"\"\"\n
mahebourg = \"\"\" { \"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}}}}]}\n
In\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)

"},{"location":"examples/notebooks/Working_with_Statistics/#bounds","title":"Bounds\u00b6","text":"

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).

"},{"location":"examples/notebooks/Working_with_Statistics/#statistics","title":"Statistics\u00b6","text":"

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)\n
import 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\n
titiler_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\"])\n
r = 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\n
r = 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\n
r = 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\n
r = 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":""},{"location":"examples/notebooks/Working_with_nonWebMercatorTMS/#list-supported-tilematrixsets","title":"List Supported TileMatrixSets\u00b6","text":""},{"location":"examples/notebooks/Working_with_nonWebMercatorTMS/#webmercator-epsg3857","title":"WebMercator - EPSG:3857\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

"}]}