{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"TiTiler","text":"
A modern dynamic tile server built on top of FastAPI and Rasterio/GDAL.
Documentation: devseed.com/titiler/
Source Code: developmentseed/titiler
TiTiler, pronounced tee-tiler (ti is the diminutive version of the french petit which means small), is a set of python modules that focus on creating FastAPI application for dynamic tiling.
Note: This project is the descendant of cogeo-tiler and cogeo-mosaic-tiler.
morecantile.Starting with version 0.3.0, the TiTiler python module has been split into a set of python namespace packages: titiler.{package}.
Core package contains libraries to help create a dynamic tiler for COG and STAC titiler.xarray The xarray package contains libraries to help create a dynamic tiler for Zarr/NetCDF datasets 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, Zarr, STAC and MosaicJSON"},{"location":"#installation","title":"Installation","text":"[!WARNING]
Do not install the package named titiler from PyPI. In late 2025, we dropped support for this metapackage; now you must install TiTiler from the package names shown below.
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.xarray\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, xarray 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\nuv sync --group server\nuv run uvicorn titiler.application.main:app --reload\n"},{"location":"#docker","title":"Docker","text":"Ready to use/deploy images can be found on Github registry.
docker run \\\n --platform=linux/amd64 \\\n -p 8000:8000 \\\n --rm -it ghcr.io/developmentseed/titiler:latest \\\n uvicorn titiler.application.main:app --host 0.0.0.0 --port 8000 --workers 1\n git clone https://github.com/developmentseed/titiler.git\ncd titiler\n\ndocker compose up --build titiler\nsrc/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 xarray/ - Titiler's `Xarray` 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
We recommand using uv as project manager for development.
See docs.astral.sh/uv/getting-started/installation/ for installation
dev install
git clone https://github.com/developmentseed/titiler.git\ncd titiler\n\n# Install the package in editable mode, plus the \"dev\" dependency group.\n# You can add `--group` arguments to add more groups, e.g. `--group notebook`.\nuv sync\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.
uv run pre-commit install\n\n# If needed, you can run pre-commit script manually \nuv run pre-commit run --all-files \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\nuv run pytest src/titiler/core --cov=titiler.core --cov-report=xml --cov-append --cov-report=term-missing\n\n# titiler.extensions\nuv run pytest src/titiler/extensions --cov=titiler.extensions --cov-report=xml --cov-append --cov-report=term-missing\n\n# titiler.mosaic\nuv run pytest src/titiler/mosaic --cov=titiler.mosaic --cov-report=xml --cov-append --cov-report=term-missing\n\n# titiler.xarray\nuv run pytest src/titiler/xarray --cov=titiler.xarray --cov-report=xml --cov-append --cov-report=term-missing\n\n# titiler.application\nuv run 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\n\n# Build docs\nuv run --group docs mkdocs build -f docs/mkdocs.yml\n Hot-reloading docs:
uv run --group docs mkdocs serve -f docs/mkdocs.yml --livereload\n To manually deploy docs (note you should never need to do this because Github Actions deploys automatically for new commits.):
uv run --group docs mkdocs gh-deploy -f docs/mkdocs.yml\n"},{"location":"external_links/","title":"External links","text":"Tip
If you have an article, project, tool, or anything related to TiTiler that is not yet listed here, create a Pull Request adding it.
"},{"location":"external_links/#mentions","title":"Mentions","text":"Sparkgeo's Terradactile
Chris Holmes Cloud Native Geospatial Sprint Awards & Bounties
Tobin Bradley awesome video about COGs Youtube
David McCracken's Blog on Plotly Dash Interactive Mapping - Dash Leaflet & TiTiler
stac-utils/titiler-pgstac: TiTiler extension which connects to a PgSTAC database to create dynamic mosaics based on search queries.
developmentseed/titiler-xarray: TiTiler extension for xarray
developmentseed/titiler-image: TiTiler demo application for Sentinel-2 Digital Twin dataset
ESA Charter Mapper geobrowser
developmentseed/titiler-digitaltwin: TiTiler demo application for Sentinel-2 Digital Twin dataset
developmentseed/titiler-mvt: TiTiler demo application to create Mapbox Vector Tiles from COG
developmentseed/titiler-pds: TiTiler demo application for Sentinel-2 and Landsat-8 AWS Public Datasets
stac-utils/stac-fastapi: STAC API implementation with FastAPI.
c-core-labs/stac-api: STAC compliant API implementation (built from stac-fastapi)
developmentseed/titiler-lambda-layer: TiTiler Lambda layers for easy deployment on AWS
Terradue/Stars: Spatio Temporal Asset Runtime Services
developmentseed/rio-viz: Visualize Cloud Optimized GeoTIFF in browser
developmentseed/pearl-backend: PEARL (Planetary Computer Land Cover Mapping) Platform API and Infrastructure
microsoft/planetary-computer-apis: Microsoft Planetary Computer APIs
assets optional in RenderItem modelvalid: True|False key to the RenderItemWithLinks modelasset_bix and asset_expression keys to {asset}|indexes=...&expression=.../map.html endpointMultiBandTilerFactory factory@{scale}x suffix for tile endpointstile_scale option in /tilejson.json and /map.html endpointstileHeight x tileWidth for tile endpointstilesize=512 for /tilejson.json endpointstilesize=256 for /map.html endpointsband_descriptions instead of band_namestilesize optional query-parameter for tile and tilejson endpoints>=9.0.0a4,<10.0bidx option is now ignored by MultiBaseFactory endpointsassets option is now required for MultiBaseFactory endpointsassets=:all: in MultiBaseFactory's /info and /statistics endpointsexpression cannot be used to declare assets in MultiBaseFactory endpoints. Use assets=Red&assets=Green&expression=b1/b2.asset_indexes and asset_expression options in dependencies.pydependencies.AssetsBidxExprParams to dependencies.AssetsExprParams titiler.core.dependencies.AssetsBidxParamstitiler.core.dependencies.AssetsBidxExprParamsOptionaltitiler.core.dependencies.BandsParamstitiler.core.dependencies.BandsExprParamsOptionaltitiler.core.dependencies.BandsExprParamstitiler.core.dependencies.parse_asset_indexes()titiler.core.dependencies.parse_asset_expression()titiler.core.routing.apiroute_factory()tilesize=256 in cog and stac viewerstile_scale option in /WMTSCapabilities.xml endpointstile_scale option in /WMTSCapabilities.xml endpoints/zarr endpointsfloat16, int64 and uint64 datatype in viewersh5py in optional dependencyget_renders attribute to wmtsExtension extensionswmts.xml template breaking changedem algorithms by using numpy functions (author @manand881, developmentseed/titiler!1294)bump-my-version from dev dependenciesutils.tms_limits options to avoid code duplication>=8.0,<9.0UINT8 datatype JPEG/PNG when no output format is specified breaking change/{tileMatrixSetId}/WMTSCapabilities.xml endpoints from factories breaking changelinux/arm64 docker image band_description attribute to Point output model (returned by /point endpoints) breaking change7.0,<8.0wmtsExtension which adds /WMTSCapabilities.xml to factoriesWMTSCapabilities.xml response now support all TileMatrixSets as separate layers breaking changechange Response model for /point endpoint breaking change
# before\nclass Point(BaseModel):\n coordinates: List[float]\n values: List[Tuple[str, List[Optional[float]], List[str]]]\n\n# now\nclass AssetPoint(BaseModel):\n name: str\n values: list[float | None]\n band_names: list[str]\n band_descriptions: list[str] | None = None\n\nclass Point(BaseModel):\n coordinates: list[float]\n assets: list[AssetPoint]\n add /feature, /bbox and /statistics optional endpoints
cogeo-mosaic an optional dependency breaking changeMosaicTilerFactory.backend attribute breaking changetitiler.mosaic.extensions.mosaicjson.MosaicJSONExtension which adds MosaicJSON specific / and /validate endpointstitiler.mosaic.extension.wmts.wmtsExtension which adds /WMTSCapabilities.xml endpoint/map endpointuse sel={dim}={method}::{value} notation to specify selector method instead of sel-method query-parameter breaking change
# before\n.../info?tore.zarr?sel=time=2023-01-01&sel_method=nearest`\n\n# now\n.../info?tore.zarr?sel=time=nearest::2023-01-01`\n add /validate endpoint via ValidateExtension extension
Latitude and Longitude as compatible spatial dimensions (@abarciauskas-bgse, developmentseed/titiler!1268)mosaic_def.center and calculate from bounds/bounds endpoints breaking changeuv for developmenthatch for python package build-systemtitiler metapackage breaking changeopener_options arg to titiler.xarray.io.Reader to allow users to pass args through to a custom opener function ([#1248(developmentseed/titiler!1248)])obstore and zarr-python as dependency and add open_zarr dataset openertitiler.xarray.io.open_zarr for titiler.xarray.io.Reader.dataset_opener attributetitiler.xarray.io.xarray_open_dataset to fs_open_datasetFsReader which use fs_open_dataset as dataset_openertitiler.xarray.main:app/ and /validate to a MosaicJSONExtensionTileJSON spec from 2.2.0 to 3.0.0histogram_range examples (@guillemc23, developmentseed/titiler!1239)grayscale and bitonal algorithmstransform and crs for tiff outputs/tilejson.json response. Controled with TITILER_DEFAULT_ATTRIBUTION environment variable.jinja2.autoescape for HTML/XML templates (ref: jinja.palletsprojects.com/en/stable/api/#autoescaping)description in ApiSettingstitiler.core.templating submodule breaking changecreate_html_response function to titiler.core.utils submoduletitiler/core/templates directory breaking changeColorMapsList -> ColorMapList and change it's attibutes to colormaps breaking changetemplates in the BaseFactory class definitionsum algorithmOGC Maps API support (/map endpoint)starlette-cramjam requirement to >=0.4,<0.6add_preview in factory attribute (default to False)rel values for tiling scheme link (OGC Tiles specification)dtype to cast user selectionxarray_open_dataset for cloud hosted filesreader and path_dependency type informations/map endpoint to /map.html breaking changename attribute to BaseFactory to define endpoint's operationIdoperationId on all endpoints/preview/{width}x{height}.{format} endpoints>=7.7,<8.0width or heigh size parameters for preview, part and feature requestsminZoom instead of minNativeZoom in the /map.html html template>=1.1.2,<3.0 and change featureCollection iterationtitiler.core templates for Landing page/ landing page/conformance endpointadd conforms_to attribute to BaseFactory to indicate which conformance the TileFactory implement
remove deprecated ColorFormulaParams and RescalingParams dependencies breaking change
remove deprecated DefaultDependency dict-unpacking feature breaking change
add min, max, mean, median, std and var algorithms
Fix TerrainRGB algorithm and param user-controlled nodata-height (@jo-chemla, developmentseed/titiler!1116)
add output_min and output_max metadata attributes to slope algorithm (@tayden, developmentseed/titiler!1089)
add point value query on right-click to map viewer (@hrodmn, developmentseed/titiler!1100)
refactor middlewares to use python's dataclasses
update LoggerMiddleware output format and options breaking change
from fastapi import FastAPI\n\nfrom titiler.core.middlewares import LoggerMiddleware\n\n# before\napp = FastAPI()\napp.add_middlewares(LoggerMiddleware, querystrings=True, headers=True)\n\n# now\napp = FastAPI()\napp.add_middlewares(\n LoggerMiddleware,\n # custom Logger\n logger=logging.getLogger(\"mytiler.requests\"), # default to logging.getLogger(\"titiler.requests\")\n)\n Note: logger needs then to be configured at runtime. e.g :
from logging import config\nconfig.dictConfig(\n {\n \"version\": 1,\n \"disable_existing_loggers\": False,\n \"formatters\": {\n \"detailed\": {\n \"format\": \"%(asctime)s - %(levelname)s - %(name)s - %(message)s\"\n },\n \"request\": {\n \"format\": (\n \"%(asctime)s - %(levelname)s - %(name)s - %(message)s \"\n + json.dumps(\n {\n k: f\"%({k})s\"\n for k in [\n \"method\",\n \"referer\",\n \"origin\",\n \"route\",\n \"path\",\n \"path_params\",\n \"query_params\",\n \"headers\",\n ]\n }\n )\n ),\n },\n },\n \"handlers\": {\n \"console_request\": {\n \"class\": \"logging.StreamHandler\",\n \"level\": \"DEBUG\",\n \"formatter\": \"request\",\n \"stream\": \"ext://sys.stdout\",\n },\n },\n \"loggers\": {\n \"mytiler.requests\": {\n \"level\": \"INFO\",\n \"handlers\": [\"console_request\"],\n \"propagate\": False,\n },\n },\n }\n)\n wms extension to remove usage of ColorFormulaParams and RescalingParams dependenciesrender extension to better validate query-parameters from render expressionrio-tiler requirement to >=7.6.1add sel and sel_method options to select dimension
# before\nhttps://.../0/0/0.png?url=dataset.zarr&drop_dim=time=2023-01-01\n\n# now\nhttps://.../0/0/0.png?url=dataset.zarr&sel=time=2023-01-01\n\n# method\nhttps://.../0/0/0.png?url=dataset.zarr&sel=time=2023-01-02&sel_method=nearest\n\n# Can use `slice` when providing 2 values\nhttps://.../0/0/0.png?url=dataset.zarr&sel=time=2023-01-01&time=2023-01-31\n * add support for bidx parameter * remove first time dim selection breaking change * add support for 3D dataset * remove drop_dim option breaking change * remove datetime option breaking change * deprecate VariablesExtension extension * add DatasetMetadataExtension extension (/dataset/keys, /dataset/ and /dataset/dict endpoints) /bbox prefix to /{minx},{miny},{maxx},{maxy}/assets endpoint -> /bbox/{minx},{miny},{maxx},{maxy}/assets breaking change/point prefix to {lon},{lat}/assets endpoint -> /point/{lon},{lat}/assets breaking change/tiles prefix to /{tileMatrixSetId}/{z}/{x}/{y}/assets endpoint -> /tiles/{tileMatrixSetId}/{z}/{x}/{y}/assets breaking changeassets_accessor_dependency dependency to the MosaicTileFactory to pass options to the backend's get_assets method.slope algorithm (@tayden, developmentseed/titiler!1088)use URN style CRS notation in WMTS document
Unify Docker images (deprecate titiler-uvicorn)
# Uvicorn\n# before\ndocker run \\\n --platform=linux/amd64 \\\n -p 8000:8000 \\\n --env PORT=8000 \\\n --rm -it ghcr.io/developmentseed/titiler-uvicorn:latest\n\n# now\ndocker run \\\n --platform=linux/amd64 \\\n -p 8000:8000 \\\n --rm -it ghcr.io/developmentseed/titiler:latest \\\n uvicorn titiler.application.main:app --host 0.0.0.0 --port 8000 --workers 1\n\n# Gunicorn\n# before\ndocker run \\\n --platform=linux/amd64 \\\n -p 8000:8000 \\\n --env PORT=8000 \\\n --rm -it ghcr.io/developmentseed/titiler:latest\n\n# now\ndocker run \\\n --platform=linux/amd64 \\\n -p 8000:8000 \\\n --rm -it ghcr.io/developmentseed/titiler:latest \\\n gunicorn -k uvicorn.workers.UvicornWorker titiler.application.main:app --bind 0.0.0.0:8000 --workers 1\n zarr to >2,<3.0 to avoid zarr 3.0 breaking changestitiler.core.utils.bounds_to_geometry and reduce code duplication in factories (author @PratapVardhan, developmentseed/titiler!1047)render_image (author @PratapVardhan, developmentseed/titiler!1046)rescale_dependency and color_formula_dependency attributes in TilerFactory class breaking changerescale and color_formula QueryParameters dependencies in ImageRenderingParams class breaking changetitiler.core.utils.render_image function breaking changerender_func: Callable[..., Tuple[bytes, str]] = render_image attribute in TilerFactory classcastToInt, Floor, Ceil algorithms/healthz endpoint to return dependencies versions (titiler, rasterio, gdal, ...) (author @scottyhq, developmentseed/titiler!1056)templates/index.html to bootstrap5, remove unused css, reuse bs classes (author @PratapVardhan, developmentseed/titiler!1048)rescale_dependency and color_formula_dependency attributes in MosaicTilerFactory class breaking changerender_func: Callable[..., Tuple[bytes, str]] = render_image attribute in MosaicTilerFactory class breaking changefactory.render_func as render function in wmsExtension endpointsstacRenderExtension which adds two endpoints: /renders (lists all renders) and /renders/<render_id> (render metadata and links) (author @alekzvik, developmentseed/titiler!1038)python3.8 support (author @pratapvardhan, developmentseed/titiler!1058)python3.13 support (author @pratapvardhan, developmentseed/titiler!1058)drop python 3.8 and add python 3.13 support (author @pratapvardhan, developmentseed/titiler!1058)
Update package build backend from pdm-pep517 to pdm-backend (backend.pdm-project.org/#migrate-from-pdm-pep517)
Update namespace package from using . to - as separator to comply with PEP-625 (peps.python.org/pep-0625/)
MOSAIC_CONCURRENCY and MOSAIC_STRICT_ZOOM) from env-variable outside endpoint codetitiler links in Map attributionsRemove default WebMercatorQuad tile matrix set in /tiles, /tilesjson.json, /map and /WMTSCapabilities.xml endpoints breaking change
# Before\n/tiles/{z}/{x}/{y}\n/tilejson.json\n/map\n/WMTSCapabilities.xml\n\n# Now\n/tiles/WebMercatorQuad/{z}/{x}/{y}\n/WebMercatorQuad/tilejson.json\n/WebMercatorQuad/map\n/WebMercatorQuad/WMTSCapabilities.xml\n Use @attrs.define instead of dataclass for factories breaking change
@attrs.define instead of dataclass for factory extensions breaking changenumpy types in JSON/GeoJSON responsemap.html template, use the tilejson's minzoom and maxzoom to populate minNativeZoom and maxNativeZoom parameters in leaflet tileLayer instead of minZoom and maxZoomUpdate rio-tiler dependency to >=7.0,<8.0
Update geojson-pydantic dependency to >=1.1.2,<2.0 which better handle antimeridian crossing dataset
handle antimeridian crossing bounds in /info.geojson endpoints (returning MultiPolygon instead of Polygon)
Improve XSS security for HTML templates (author @jcary741, developmentseed/titiler!953)
Remove all default values to the dependencies breaking change
DatasetParams.unscale: False -> None (default to False in rio-tiler)DatasetParams.resampling_method: nearest -> None (default to nearest in rio-tiler)DatasetParams.reproject_method: nearest -> None (default to nearest in rio-tiler)ImageRenderingParams.add_mask: True -> None (default to True in rio-tiler)StatisticsParams.categorical: False -> None (default to False in rio-tiler)Add as_dict(exclude_none=True/False) method to the DefaultDependency class.
from typing import Optional\nfrom titiler.core.dependencies import DefaultDependency\nfrom dataclasses import dataclass\n\n@dataclass\nclass Deps(DefaultDependency):\n value: Optional[int] = None\n\nprint({**Deps().__dict__.items()})\n>> {'value': None}\n\nDeps().as_dict() # `exclude_none` defaults to True\n>> {}\n\nDeps(value=1).as_dict()\n>> {'value': 1}\n Fix Hillshade algorithm (bad azimuth angle)
Set default azimuth and altitude angles to 45\u00ba for the Hillshade algorithm breaking change
Use .as_dict() method when passing option to rio-tiler Reader's methods to avoid parameter conflicts when using custom Readers.
Rename BaseTilerFactory to BaseFactory breaking change
Remove useless attribute in BaseFactory (and moved them to TilerFactory) breaking change
Add crs option to /bounds endpoints to enable geographic_crs selection by the user
/bounds endpoints now return a crs: str attribute in the response
update wmts.xml template to support multiple layers
re-order endpoints parameters
avoid lat/lon overflow in map viewer
add OGC Tiles /tiles and /tiles/{tileMatrixSet} endpoints
add gif media type
/point endpoint returned masked values (None is nodata)
Rename reader attribute to backend in MosaicTilerFactory breaking change
Add crs option to /bounds endpoints to enable geographic_crs selection by the user
/bounds endpoints now return a crs: str attribute in the response
Update cogeo-mosaic dependency to >=8.0,<9.0
re-order endpoints parameters
add OGC Tiles /tiles and /tiles/{tileMatrixSet} endpoints
/point endpoint returned masked values (None is nodata)
Encode URL for cog_viewer and stac_viewer (author @guillemc23, developmentseed/titiler!961)
Add links for render parameters and /map link to viewer dashboard (author @hrodmn, developmentseed/titiler!987)
Update viewers to use /info.geojson endpoint instead of /info
starlette-cramjam dependency and set compression-level default to 6azimuth angle) (developmentseed/titiler!985) [Backported]fastapi instead of fastapi-slim and use >=0.109.0 version>=0.111.0WMTSCapabilities.xml response for ArcMap compatibilityCloud Optimized GeoTIFF with dataset URL or TiTiler for the ows:ServiceIdentification titlecogeo with Dataset for the layer ows:Identifierfastapi-slim to avoid unwanted dependencies (author @n8sty, developmentseed/titiler!815)TerrainRGB algorithm name (author @JinIgarashi, developmentseed/titiler!804)RescalingParams and HistogramParams dependencies204 Error codeColorMapFactory to create colorMap metadata endpoints (developmentseed/titiler!796)Deprecation remove default WebMercatorQuad tile matrix set in /tiles, /tilesjson.json, /map and /WMTSCapabilities.xml endpoints (developmentseed/titiler!802)
# Before\n/tiles/{z}/{x}/{y}\n/tilejson.json\n/map\n/WMTSCapabilities.xml\n\n# Now\n/tiles/WebMercatorQuad/{z}/{x}/{y}\n/WebMercatorQuad/tilejson.json\n/WebMercatorQuad/map\n/WebMercatorQuad/WMTSCapabilities.xml\n Deprecation default_tms attribute in BaseTilerFactory (because tileMatrixSetId is now required in endpoints).
Deprecation remove default WebMercatorQuad tile matrix set in /tiles, /tilesjson.json, /map and /WMTSCapabilities.xml endpoints (developmentseed/titiler!802)
# Before\n/tiles/{z}/{x}/{y}\n/tilejson.json\n/map\n/WMTSCapabilities.xml\n\n# Now\n/tiles/WebMercatorQuad/{z}/{x}/{y}\n/WebMercatorQuad/tilejson.json\n/WebMercatorQuad/map\n/WebMercatorQuad/WMTSCapabilities.xml\n Deprecation default_tms attribute in MosaicTilerFactory (because tileMatrixSetId is now required in endpoints).
request as first argument in TemplateResponse to adapt with latest starlette versionextra=\"ignore\" option ApiSettings to fix pydantic issue when using .env file (author @imanshafiei540, developmentseed/titiler!800)use_epsg parameter to WMTS endpoint to resolve ArcMAP issues and fix XML formating (author @gadomski, developmentseed/titiler!782)rio-tiler version to >6.3.0align_bounds_with_dataset=True rio-tiler option in GeoJSON statistics methods for more precise calculation/map endpoint (author @Firefishy, developmentseed/titiler!749)TileMatrixSet.cellSize property instead of deprecated/private TileMatrixSet._resolution methodTileMatrixSet.cellSize property instead of deprecated/private TileMatrixSet._resolution method>=0.107.0jinja2.Environment argument (author @jasongi, developmentseed/titiler!744)jinja2.Environment argument (author @jasongi, developmentseed/titiler!744)jinja2.Environment argument (author @jasongi, developmentseed/titiler!744)TileMatrixSet.cellSize property instead of deprecated/private TileMatrixSet._resolution method [backported from 0.16.1]TileMatrixSet.cellSize property instead of deprecated/private TileMatrixSet._resolution method [backported from 0.16.1]<0.107.0 to avoid starlette breaking change (0.28)global_access_token string, set with TITILER_API_GLOBAL_ACCESS_TOKEN environment variable (author @DeflateAwning, developmentseed/titiler!735)/map HTML response, add Lat/Lon buffer to AOI to avoid creating wrong AOI (when data covers the whole world).add algorithm options for /statistics endpoints
switch from BaseReader.statistics() method to a combination of BaseReader.preview() and ImageData.statistics() methods to get the statistics
update rio-tiler requirement to >=6.2.5,<7.0
allow bidx option in titiler.core.dependencies.AssetsBidxExprParams and titiler.core.dependencies.AssetsBidxParams
# merge band 1 form asset1 and asset2\n# before\nhttpx.get(\n \"/stac/preview\",\n params=(\n (\"url\", \"stac.json\"),\n (\"assets\", \"asset1\"),\n (\"assets\", \"asset2\"),\n (\"asset_bidx\", \"asset1|1\"),\n (\"asset_bidx\", \"asset2|1\"),\n )\n)\n\n# now\nhttpx.get(\n \"/stac/preview\",\n params=(\n (\"url\", \"stac.json\"),\n (\"assets\", \"asset1\"),\n (\"assets\", \"asset2\"),\n (\"bidx\", 1),\n )\n)\n fix openapi examples
dst_crs options in /statistics [POST] and /feature [POST] endpointsdependencies.TileParams dependency with buffer and padding optionstile_dependency attribute in TilerFactory class (defaults to TileParams)reproject (alias to reproject_method) option in DatasetParams dependencyHTTP_404_NOT_FOUND to HTTP_204_NO_CONTENT when no asset is found or tile is empty (author @simouel, developmentseed/titiler!713)tile_dependency attribute in MosaicTilerFactory class (defaults to TileParams)color_formula parameter to be set via a dependency (author @samn, developmentseed/titiler!707)titiler.core.dependencies.create_colormap_dependency to create ColorMapParams dependency from rio_tiler.colormap.ColorMaps objectpy.typed files in titiler submodules (peps.python.org/pep-0561)PartFeatureParams dependencybreaking changes
max_size is now set to None for /statistics [POST], /bbox and /feature endpoints, meaning the tiler will create image from the highest resolution.
renamed titiler.core.dependencies.ImageParams to PreviewParams
split TileFactory img_dependency attribute in two:
img_preview_dependency: used in /preview and /statistics [GET], default to PreviewParams (with max_size=1024)
img_part_dependency: used in /bbox, /feature and /statistics [POST], default to PartFeatureParams (with max_size=None)
renamed /crop endpoints to /bbox/... or /feature/...
/crop/{minx},{miny},{maxx},{maxy}.{format} -> /bbox/{minx},{miny},{maxx},{maxy}.{format}
/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format} -> /bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}
/crop [POST] -> /feature [POST]
/crop.{format} [POST] -> /feature.{format} [POST]
/crop/{width}x{height}.{format} [POST] -> /feature/{width}x{height}.{format} [POST]
update rio-tiler requirement to >=6.2.1
Take coverage weights in account when generating statistics from GeoJSON features
GetFeatureInfo capability in wmsExtension (author @benjaminleighton, developmentseed/titiler!698)- by _ in query parameters breaking changecoord-crs -> coord_crsdst-crs -> dst_crs
replace buffer and color_formula endpoint parameters by external dependencies (BufferParams and ColorFormulaParams)
add titiler.core.utils.render_image which allow non-binary alpha band created with custom colormap. render_image replace ImageData.render method.
# before\nif cmap := colormap or dst_colormap:\n image = image.apply_colormap(cmap)\n\nif not format:\n format = ImageType.jpeg if image.mask.all() else ImageType.png\n\ncontent = image.render(\n img_format=format.driver,\n **format.profile,\n **render_params,\n)\n\n# now\n# render_image will:\n# - apply the colormap\n# - choose the right output format if `None`\n# - create the binary data\ncontent, media_type = render_image(\n image,\n output_format=format,\n colormap=colormap or dst_colormap,\n **render_params,\n)\n geom-densify-pts to geometry_densify breaking changegeom-precision to geometry_precision breaking changeurl_for method and avoid changing Request.path_params objectLowerCaseQueryStringMiddleware unexpectedly truncating query parameters (authors @jthetzel and @jackharrhy, @developmentseed/titiler!677)cors_allow_methods in ApiSettings to control the CORS allowed methods (author @ubi15, developmentseed/titiler!684)>=0.95.1 \u2192 >=0.100.0~=1.0 \u2192 ~=2.0>=5.0,<6.0 \u2192 >=6.0,<7.0>=4.3,<5.0 \u2192 >=5.0,<6.0>=0.4,<0.7 \u2192 >=1.0,<2.0>=4.6.1>=4.0,<5.0\" \u2192 >=5.0,<6.0\">=6.0,<7.0 \u2192 >=7.0,<8.0/api and /api.html for documentation (instead of /openapi.json and /docs)Annotated Type for Query/Path parametersTileMatrixSetId by tileMatrixSetId>=0.95.1pydantic dependency to ~=1.0rio-tiler dependency to >=5.0,<6.0/validate endpoint to avoid issue when COG has NaN nodata valuerio-cogeo dependency to >=4.0,<5.0rio-stac requirement to >=0.8,<0.9 and add geom-densify-pts and geom-precision optionscogeo-mosaic dependency to >=6.0,<7.0titiler.mosaic.resources.enum.PixelSelectionMethod and use rio_tiler.mosaic.methods.PixelSelectionMethodWebMercatorQuad)templates configurable in the factoriesindex.html to map.htmldependencies.CRSParams to dependencies.CoordCRSParamsdst-crs option for /preview and /crop endpoints to specify the output Coordinate Reference System.templates configurable in the factoriestemplates configurable in the factoriescog_index.html to cog_viewer.htmlstac_index.html to stac_viewer.htmlzoom to point in stac and cog viewers (author @dchirst, developmentseed/titiler!614)rescale parameter to be set via a dependency (author @samn, developmentseed/titiler!619)coord-crs parameter for /point, /part and /feature endpointsTerrainRGB (change interval from 1.0 to 0.1)url_for method to make sure we return a string (developmentseed/titiler!607)/map viewer when using WebMercator TMShatch and pdm-pep517 as build system and use pyproject.toml for python module metadataruff for python lintingasset_bidxrio-tiler minimum version to 4.1.6titiler.extensions package (cogValidateExtension, stacExtension, cogViewerExtension, stacViewerExtension, wmsExtension)cogeo-mosaic version requirement to >=5.0,<5.2 (allow using az:// prefix from uri)MOSAIC_STRICT_ZOOM environment variable to control if the application should allow fetching tiles outside mosaic min/max zoomsbreaking change
"},{"location":"release-notes/#titilercore_34","title":"titiler.core","text":"extensions option to the BaseTilerFactory to specify a list of extension we want to register. Each extension will be then registered in the __post_init__ object creation step.BaseHTTPMiddleware class inheritance for middleware (write pure ASGI middleware)titiler.core factoriesviewer code into titiler.extensions/cog/stac endpoint from titiler.extension.stacExtension to create STAC Items from raster dataset/ is the correct route path, which enable prefixed and non-prefixed mosaic application)router_prefix in BaseTilerFactory.url_for/map endpoint and template to support multiple TMS (developmentseed/titiler!560)breaking change
=={currentVersion}wmts.xml template to work with non-epsg based CRSdefault_tms in BaseTilerFactory to set the default TMS identifier supported by the tiler (e.g WebMercatorQuad)titiler.core.version file>=0.87>=4.1,<4.2rescale and color_formula from the post_process dependencyalgorithm support and introduce new algorithm and algorithm_params query parametersbreaking changes
timing headers and titiler.core.utils submoduleasset_expression (except in /asset_statistics endpoint) (see cogeotiff.github.io/rio-tiler/v4_migration/#multibasereader-expressions)band_namesb (e.g b1) (ref: cogeotiff.github.io/rio-tiler/v4_migration/#band-names)/map endpoint in TilerFactory to display tiles given query-parametersTMSParams and WebMercatorTMSParams dependencies.TilerFactory.tms_dependency attribute by TilerFactory.supported_tms. This attribute gets a morecantile.defaults.TileMatrixSets store and will create the tms dependencies dynamicallyTMSFactory.tms_dependency attribute by TMSFactory.supported_tms. This attribute gets a morecantile.defaults.TileMatrixSets store and will create the tms dependencies dynamicallystats_dependency and histogram_dependency from BaseTilerFactory to TilerFactory; has be to used in expression to indicate multiple bands. b1*2,b2+b3,b1/b3 -> b1*2;b2+b3;b1/b3>=4.2,<4.3breaking changes
timing headersMosaicTilerFactory.tms_dependency attribute by MosaicTilerFactory.supported_tms. This attribute gets a morecantile.defaults.TileMatrixSets store and will create the tms dependencies dynamicallybreaking changes
pixel_selection_dependency options in MosaicTilerFactory to allow default method override (author @samn, developmentseed/titiler!495)interval colormaps in titiler.applicationenvironment_dependency option in BaseTilerFactory to define GDAL environment at runtime.gdal_config option in BaseTilerFactory breaking# before\nrouter = TilerFactory(gdal_config={\"GDAL_DISABLE_READDIR_ON_OPEN\": \"FALSE\"}).router\n\n# now\nrouter = TilerFactory(environment_dependency=lambda: {\"GDAL_DISABLE_READDIR_ON_OPEN\": \"FALSE\"}).router\n\n\nclass ReaddirType(str, Enum):\n\n false = \"false\"\n true = \"true\"\n empty_dir = \"empty_dir\"\n\n\n# or at endpoint call. The user could choose between false/true/empty_dir\ndef gdal_env(disable_read: ReaddirType = Query(ReaddirType.false)):\n return {\"GDAL_DISABLE_READDIR_ON_OPEN\": disable_read.value.upper()}\n\nrouter = TilerFactory(environment_dependency=gdal_env).router\n"},{"location":"release-notes/#titilerapplication_17","title":"titiler.application","text":"starlette-cramjam requirement0.6.0a2setup.py + setuptools instead of pyproject.toml + flit because it broke namespace packages (developmentseed/titiler!472)exception_handler_factoryreader_dependency to enable passing Reader's option defined by Query/Header/Path parameters.pyproject.tomltitiler.core.__version__ breakingLowerCaseQueryStringMiddleware (author @samn, developmentseed/titiler!464)backend_dependency to enable passing Backend's option defined by Query/Header/Path parameters.backend_options MosaicTilerFactory argument in favor of the use of backend_dependency breakingpyproject.tomltitiler.mosaic.__version__ breakingpyproject.tomltitiler.application.__version__ breakingcachecontrol_max_http_code option to CacheControlMiddleware to avoid adding cache-control headers for API errors (Author @sharkinsspatial, developmentseed/titiler!444)/asset_statistics which will return per asset statistics. Returns response in form of Dict[{asset name}, Dict[{band name}, BandStatistics]]breaking change
; instead of colon (,) as separator. Note: proper urlencoding might be needed.# before\nexpression = \"b1+b2,b2\"\n\n# new\nexpression = \"b1+b2;b2\"\n /statistics now returns merged statistics in form of Dict[{asset_band or expression}, BandStatistics] (instead of Dict[{asset name}, Dict[{band name}, BandStatistics]])# before\nresponse = httpx.get(f\"/stac/statistics?url=item.json\").json()\nprint(response)\n>>> {\n \"asset1\": {\n \"1\": {\n \"min\": ...,\n \"max\": ...,\n ...\n },\n \"2\": {\n \"min\": ...,\n \"max\": ...,\n ...\n }\n }\n}\n\n# now\nresponse = httpx.get(f\"/stac/statistics?url=item.json\").json()\nprint(response)\n>>> {\n \"asset1_1\": {\n \"min\": ...,\n \"max\": ...,\n ...\n },\n \"asset1_2\": {\n \"min\": ...,\n \"max\": ...,\n ...\n },\n}\n"},{"location":"release-notes/#043-2022-02-08","title":"0.4.3 (2022-02-08)","text":"buffer option to match rio-tiler tile options (developmentseed/titiler!427)>=0.73 (developmentseed/titiler!425)colormap_dependencyDockerfile to Dockerfile.gunicornDockerfile.uvicornrio-tiler version requirement to >=3.0cogeo-mosaic version to >=4.0rio-tiler version (>=3.0.0a6) with new colormap types information and base classesadditional_dependency attribute in BaseTileFactory. This also remove **kwargs in endpoints breakingreader_options attribute in BaseTileFactory breakingtms_dependency default to titiler.core.dependencies.TMSParams which should supports all morecantile's TMS.route_dependencies attribute to BaseTilerFactory to allow customizing route dependencies (author @alukach, developmentseed/titiler!406)cogeo-mosaic version (>=4.0.0a2) with updated Backend type hints informationrio-tiler/morecantile requirement (>=3.0)utils.bbox_to_feature (replaced by geojson_pydantic native function Feature(geometry=Polygon.from_bounds(*bounds), properties=info))utils.data_stats (replaced by rio-tiler new statistics method)metadata endpoints breaking APIstatistics endpoints with histogram optionsstatistics endpoint responses breaking APIband_expression in BandsExprParams dependency breaking APImorecantile requirement definition in setup.py and defers to rio-tiler supported versiontitiler.core.dependencies.DefaultDependency (allows dict unpacking and remove .kwargs) breaking APIbidx=1&bidx=2&bidx instead of bidx=1,2,3) breaking APIasset_bidx query parameter in replacement of bidx in MultiBaseFactory dependencies and switch to new format: {asset name}|{bidx,bidx,bidx} breaking APIasset_expression to the new format: {asset name}|{expression} (e.g data|b1+b2) breaking APIassets QueryParameter to List (e.g assets=COG&assets=Data) breaking APIbands QueryParameter to List (e.g bands=B01&bands=B02) breaking APIRenderParams dependency into:PostProcessParams: rescale and color_formula parametersImageRenderingParams: return_maskprocess_dependency attribute in BaseTilerFactory (defaults to PostProcessParams)resampling alias instead of resampling_method for QueryParameter breaking APIassets option is not provided for MultiBaseTilerFactory info and statistics endpoints.bands option is not provided for MultiBandsTilerFactory info and statistics endpoints.bounds, minzoom and maxzoom in /info responsedataset in /info response to better follow the Info model/statistics endpoint by defaultcogeo-mosaic requirement (>=4.0)/info endpoint to match the model.3.0 (author @robintw, developmentseed/titiler!389)3.0titiler.core.resources.responses.JSONResponse as default response for info, metadata, statistics and point endpoints (ref: developmentseed/titiler!374)starlette_cramjam compression middleware (ref: developmentseed/titiler#369)>=0.65,<0.68 (ref: developmentseed/titiler#366)asset_expression and band_expression in Multi*TilerFactory (ref: developmentseed/titiler#367)titiler.application.middleware to titiler.core.middleware (developmentseed/titiler!365)/{z}/{x}/{y}/assets, /{lon},{lat}/assets, /{minx},{miny},{maxx},{maxy}/assets GET endpoints to return a list of assets that intersect a given geometry (author @mackdelany, developmentseed/titiler!351)/crop POST endpoint to return an image from a GeoJSON feature (developmentseed/titiler!339)/statistics (GET and POST) endpoints to return advanced images statistics (developmentseed/titiler!347)root_path setting to specify a url path prefix to use when running the app behind a reverse proxy (developmentseed/titiler!343)tilejson and WMTSCapabilities.xml endpoints to allow list querystrings (as done previously in developmentseed/titiler#319)titiler.application.middleware.LowerCaseQueryStringMiddleware to cast all query string parameter to lowercase (author @lorenzori, developmentseed/titiler!321)titiler code to src/titiler>=2.1 version and update rescale query-parameter (developmentseed/titiler#319)# before\n# previously, rio-tiler was splitting a list of input range in tuple of 2\nrescale=0,1000,0,1000,0,1000\n\n# now\n# rio-tiler 2.1 now expect sequence of tuple in form of Sequence[Tuple[Num, Num]]\nrescale=0,1000&rescale=0,1000&rescale=0,1000\n"},{"location":"release-notes/#titilermosaic_28","title":"titiler.mosaic","text":"cogeo-mosaic version to >=3.0,<3.1.>=2.2 and use rio_cogeo.models instead of custom ones.exclude_path options in titiler.application.middleware.CacheControlMiddleware to avoid adding cache-control headers to specific paths.histogram_bins to be a single value or a , delimited scalar (developmentseed/titiler!307)change error status from 404 to 500 for RasterioIOError exception (author @kylebarron, developmentseed/titiler!300)
Sometimes GDAL/Rasterio can lose track of the file handler (might be related to cache issue + threading) and raise RasterioIOError: file not found, while the file exists for real. To avoid caching this, we changed the error code to 500 (errors >= 500 do not get cache-control header on titiler.application).
.jpg and .jpeg extensions (developmentseed/titiler!271)breaking change
split titiler into a set of namespaces packages (developmentseed/titiler!284)
titiler.core
The core package host the low level tiler factories.
# before\nfrom titiler.endpoints.factory import TilerFactory\n\n# now\nfrom titiler.core.factory import TilerFactory\n titiler.mosaic
The mosaic package is a plugin to titiler.core which adds support for MosaicJSON
# before\nfrom titiler.endpoints.factory import MosaicTilerFactory\n\n# now\nfrom titiler.mosaic.factory import MosaicTilerFactory\n titiler.application
The application package is a full ready to use FastAPI application with support of STAC, COG and MosaicJSON.
# before\n$ pip install titiler\n$ uvicorn titiler.main:app --reload\n\n# now\n$ pip install titiler.application uvicorn\n$ uvicorn titiler.application.main:app --reload\n 3.0.0rc2 and add backend_options attribute in MosaicTilerFactory (developmentseed/titiler!247)**render_params.kwargs to pass custom render params in image.render method (developmentseed/titiler!259)/ping to /healthz in k8s deploymentbreaking change
OptionalHeaders, MimeTypes and ImageDrivers enums to the singular form (developmentseed/titiler!258)ColorMapName, ResamplingName and TileMatrixSetName) to the singular form (developmentseed/titiler!260)MimeType to MediaType (developmentseed/titiler!258)ColorMapParams dependency to ease the creation of custom colormap dependency (developmentseed/titiler!252)PathParams to DatasetPathParams and also made it a simple callable (developmentseed/titiler!260)renamed colormap query-parameter to colormap_name (developmentseed/titiler!262)
# before\n/cog/preview.png?colormap=viridis\n\n# now\n/cog/preview.png?colormap_name=viridis\n use colormap query-parameter to pass custom colormap (developmentseed/titiler!262)
/cog/preview.png?colormap={\"0\": \"#FFFF00FF\", ...}\n validate in MosaicTilerFactory (developmentseed/titiler!206, author @drnextgis)ressources package to resources (developmentseed/titiler!210, author @drnextgis)TITILER_STACK as prefix to CDK and TITILER_API as prefix to API (developmentseed/titiler!211, author @fredliporace)create and update endpoints (developmentseed/titiler!218)titiler.models.mosaics because the models are not used anymore (developmentseed/titiler!221)rio_tiler.io.MultiBandReader bands dependencies (developmentseed/titiler!226)MultiBaseTilerFactory and MultiBandTilerFactory custom tiler factories (developmentseed/titiler!230)MultiBaseTilerFactory factorydebug configuration and make reponse headers metadata optional (developmentseed/titiler!232)breaking change
titiler.dependencies.AssetsBidxParams to make asset a required parameter (developmentseed/titiler!230/info endpoint now expect the assets parameter to be passed. To ge the list of available assets we added a new /assets endpoint within the tiler factoryCOGReader as default reader in titiler.endpoints.factory.BaseTilerFactoryrio_tiler.errors.MissingBands in known errors.titiler.endpoints.factory.TMSFactory to enable custom TMS endpoints.BaseFactory to BaseTilerFactory in titiler.endpoints.factoryAPI_DISABLE_{COG/STAC/MOSAIC} environment variables to control default endpoints in titiler main app (developmentseed/titiler#156)overwriting=False/True on MosaicJSON creation (developmentseed/titiler#164)gdal_config option to Tiler factories to replace custom APIRoute class (developmentseed/titiler#168)info.geojson endpoint to return dataset info as a GeoJSON feature (developmentseed/titiler#166)rio-tiler, cogeo-mosaic and optional dependenciesrio-tiler>=2.0.0rc2width/height in /crop endpoint path/crop/{minx},{miny},{maxx},{maxy}.{format}\n/crop/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}\n"},{"location":"release-notes/#010a11post1-2020-11-12","title":"0.1.0a11.post1 (2020-11-12)","text":"\"rio-cogeo~=2.0\"\n\"rio-tiler>=2.0.0rc1,<2.1\"\n\"cogeo-mosaic>=3.0.0a17,<3.1\"\n"},{"location":"release-notes/#010a11-2020-11-12","title":"0.1.0a11 (2020-11-12)","text":"tile() for MosaicTilerFactory method (developmentseed/titiler#147)tile() factory method (developmentseed/titiler#141, author @fredliporace)breaking changes
Note: We changed the versioning scheme to {major}.{minor}.{path}{pre}{prenum}
pkg_resources (pypa/setuptools#510)X-Server-Timings to Server-Timing.total;dur={} in response header Server-Timing, using new titiler.middleware.TotalTimeMiddleware middleware (113)from titiler.middleware import CacheControlMiddleware, TotalTimeMiddleware\nfrom fastapi import FastAPI\n\napp.add_middleware(CacheControlMiddleware, cachecontrol=\"public, max-age=3600\")\napp.add_middleware(TotalTimeMiddleware)\n tests/ an stack/ in titiler python package.EPSG6933 in TMSbreaking changes * [FACTORY] the additional_dependency should be a Callable which return a dict.
```python\n@dataclass # type: ignore\nclass BaseFactory(metaclass=abc.ABCMeta):\n \"\"\"BaseTiler Factory.\"\"\"\n ...\n # provide custom dependency\n additional_dependency: Callable[..., Dict] = field(default=lambda: dict())\n```\n\n```python\ndef AssetsParams(\n assets: Optional[str] = Query(\n None,\n title=\"Asset indexes\",\n description=\"comma (',') delimited asset names (might not be an available options of some readers)\",\n )\n) -> Dict:\n \"\"\"Assets Dependency.\"\"\"\n kwargs = {}\n if assets:\n kwargs[\"assets\"] = assets.split(\",\")\n return kwargs\n```\n _ prefix in factory methods (e.g _tile -> tile)[FACTORY] refactor dependencies to better align with rio_tiler.io.BaseReader method definition.
Example:
In the metadata, the MetadataParams will be used to pass pmin and pmax because they are the only required parameters for the metadata method. All other params will be passed to a kwargs dict.
@dataclass\nclass MetadataParams(DefaultDependency):\n \"\"\"Common Metadada parameters.\"\"\"\n # Required params\n pmin: float = Query(2.0, description=\"Minimum percentile\")\n pmax: float = Query(98.0, description=\"Maximum percentile\")\n # Optional parameters\n bidx: Optional[str] = Query(\n None, title=\"Band indexes\", description=\"comma (',') delimited band indexes\",\n )\n ...\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n\n if self.bidx is not None:\n self.kwargs[\"indexes\"] = tuple(\n int(s) for s in re.findall(r\"\\d+\", self.bidx)\n )\n ...\n\n# metadata method in factory\ndef metadata(\n src_path=Depends(self.path_dependency),\n metadata_params=Depends(self.metadata_dependency),\n kwargs: Dict = Depends(self.additional_dependency),\n):\n \"\"\"Return metadata.\"\"\"\n reader = src_path.reader or self.reader\n with reader(src_path.url, **self.reader_options) as src_dst:\n info = src_dst.metadata(\n metadata_params.pmin,\n metadata_params.pmax,\n **metadata_params.kwargs,\n **kwargs,\n )\n return info\n * [FACTORY] refactor dependencies definition @dataclass # type: ignore\nclass BaseFactory(metaclass=abc.ABCMeta):\n \"\"\"BaseTiler Factory.\"\"\"\n\n reader: default_readers_type = field(default=COGReader)\n reader_options: Dict = field(default_factory=dict)\n\n # FastAPI router\n router: APIRouter = field(default_factory=APIRouter)\n\n # Path Dependency\n path_dependency: Type[PathParams] = field(default=PathParams)\n\n # Rasterio Dataset Options (nodata, unscale, resampling)\n dataset_dependency: default_deps_type = field(default=DatasetParams)\n\n # Indexes/Expression Dependencies\n layer_dependency: default_deps_type = field(default=BidxExprParams)\n\n # Image rendering Dependencies\n render_dependency: default_deps_type = field(default=RenderParams)\n\n # TileMatrixSet dependency\n tms_dependency: Callable[..., TileMatrixSet] = WebMercatorTMSParams\n\n # provide custom dependency\n additional_dependency: Callable[..., Dict] = field(default=lambda: dict())\n remove PathParams.reader attribute. This option was not used and would have been technically difficult to use.
@dataclass\nclass PathParams:\n \"\"\"Create dataset path from args\"\"\"\n\n url: str = Query(..., description=\"Dataset URL\")\n Update .npy output format to follow the numpyTile format (#103)
import numpy\nimport requests\nfrom io import BytesIO\n\nendpoint = ...\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\n\nr = requests.get(f\"{endpoint}/cog/tiles/14/10818/9146.npy\",\n params = {\n \"url\": url,\n }\n)\ndata = numpy.load(BytesIO(r.content))\nprint(data.shape)\n> (4, 256, 256)\n Add titiler.custom.routing.apiroute_factory. This function enable the creation of custom fastapi.routing.APIRoute class with rasterio.Env() block.
from fastapi import FastAPI, APIRouter\nfrom rasterio._env import get_gdal_config\nfrom titiler.custom.routing import apiroute_factory\n\napp = FastAPI()\nroute_class = apiroute_factory({\"GDAL_DISABLE_READDIR_ON_OPEN\": \"FALSE\"})\nrouter = APIRouter(route_class=route_class)\n\n@router.get(\"/simple\")\ndef simple():\n \"\"\"should return FALSE.\"\"\"\n res = get_gdal_config(\"GDAL_DISABLE_READDIR_ON_OPEN\")\n return {\"env\": res}\n\napp.include_router(router)\n Note: This has only be tested for python 3.6 and 3.7.
url_for method in TilerFactory to retrieve prefixed endpoint URL (#95)titiler.dependencies.PathParams mosaicid path translation, where a user could pass url=mosaicid:// to the endpoint.switch to pydantic.BaseSettings for FastAPI application setting management.
List of Settings:
name: str = \"titiler\"\ncors_origins: str = \"*\"\ncachecontrol: str = \"public, max-age=3600\"\n API Settings can now be set by adding a .env file in your local project or by setting environment variables (e.g API_CORS_ORIGIN=\"https://mywebsite.com/*\")
/part GeoTIFF output>=3.0b4,<3.1 and cogeo-mosaic to >=3.0a10,<3.1e.g Create a Landsat 8 Tiler
from titiler.endpoints.factory import TilerFactory, MosaicTilerFactory\nfrom titiler.dependencies import BandsParams\n\nfrom rio_tiler_pds.landsat.aws.landsat8 import L8Reader # Not in TiTiler dependencies\n\nfrom fastapi import FastAPI\n\napp = FastAPI(title=\"Landsat Tiler\", openapi_url=\"/api/v1/openapi.json\")\nscene = TilerFactory(\n reader=L8Reader, additional_dependency=BandsParams, router_prefix=\"scenes\"\n)\nmosaic = MosaicTilerFactory(\n dataset_reader=L8Reader,\n additional_dependency=BandsParams,\n add_update=False,\n add_create=False,\n router_prefix=\"mosaic\",\n)\napp.include_router(scene.router, prefix=\"/scenes\", tags=[\"Scenes\"])\napp.include_router(mosaic.router, prefix=\"/mosaic\", tags=[\"Mosaic\"])\n"},{"location":"release-notes/#01a0-2020-08-31","title":"0.1a0 (2020-08-31)","text":"First release on pypi
"},{"location":"release-notes/#tiler-factory","title":"Tiler Factory","text":"For this release we created new Tiler Factories class which handle creation of FastAPI routers for a given rio_tiler Readers.
from titiler.endpoints.factory import TilerFactory\nfrom rio_tiler.io import COGReader, STACReader\n\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\ncog = TilerFactory()\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n"},{"location":"release-notes/#readers-tilematrixsets","title":"Readers / TileMatrixSets","text":"The titiler.endpoints.factory.TilerFactory class will create a tiler with Web Mercator as uniq supported Tile Matrix Set.
For other TMS support, tiler needs to be created with titiler.endpoints.factory.TMSTilerFactory and with a TMS friendly reader (e.g rio_tiler_crs.COGReader).
Simple tiler with only Web Mercator support
from rio_tiler.io import COGReader\n\nfrom titiler.endpoints import factory\nfrom titiler.dependencies import WebMercatorTMSParams\n\napp = factory.TilerFactory(reader=COGReader)\nassert app.tms_dependency == WebMercatorTMSParams\n Tiler with more TMS support (from morecantile)
from rio_tiler_crs import COGReader\n\nfrom titiler.endpoints import factory\nfrom titiler.dependencies import TMSParams\n\napp = factory.TMSTilerFactory(reader=COGReader)\nassert app.tms_dependency == TMSParams\n"},{"location":"release-notes/#other-changes","title":"Other changes","text":"template_dir = pkg_resources.resource_filename(\"titiler\", \"templates\")\ntemplates = Jinja2Templates(directory=template_dir)\n\ncog_template = templates.TemplateResponse(\n name=\"cog_index.html\",\n context={\n \"request\": request,\n \"tilejson\": request.url_for(\"cog_tilejson\"),\n \"metadata\": request.url_for(\"cog_metadata\"),\n },\n media_type=\"text/html\",\n)\n\nstac_template = templates.TemplateResponse(\n name=\"stac_index.html\",\n context={\n \"request\": request,\n \"tilejson\": request.url_for(\"stac_tilejson\"),\n \"metadata\": request.url_for(\"stac_info\"),\n },\n media_type=\"text/html\",\n)\n"},{"location":"release-notes/#pre-pypi-releases","title":"Pre Pypi releases","text":""},{"location":"release-notes/#212-2020-06-24","title":"2.1.2 (2020-06-24)","text":"width & height parameters in API docs to force output size for part/preview endpoints.resampling_method in API docs.link: developmentseed/titiler@725da5f
"},{"location":"release-notes/#211-2020-06-22","title":"2.1.1 (2020-06-22)","text":"link: developmentseed/titiler@95b98a3
"},{"location":"release-notes/#210-2020-06-11","title":"2.1.0 (2020-06-11)","text":"/preview, /crop, /point endpointslink: developmentseed/titiler@8b63fc6
"},{"location":"release-notes/#200-2020-06-09","title":"2.0.0 (2020-06-09)","text":"/stac/viewer, /cog/viewer)link: developmentseed/titiler@fa2cb78
"},{"location":"release-notes/#100-2020-06-04","title":"1.0.0 (2020-06-04)","text":"Initial release
link: developmentseed/titiler@f4fdc02
"},{"location":"security/","title":"Security Policy","text":""},{"location":"security/#reporting-a-vulnerability","title":"Reporting a Vulnerability","text":"If you find any vulnerabilities in titiler, don't hesitate to report them.
see docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability
If you have a fix, that is most welcome -- please attach or summarize it in your message!
Please do not disclose the vulnerability publicly until a fix is released!
TiTiler is built on top of Rasterio which is a python wrapper for the GDAL C++ library. At the time of writing, GDAL is responsible for most of the I/O and thus is where vulnerabilities could be harmful. For any I/O issues please first check GDAL documentation.
There is a known security vulnerability with the VRT Driver:
It can be used to access any valid GDAL dataset. If a hostile party, with knowledge of the location on the filesystem of a valid GDAL dataset, convinces a user to run gdal_translate a VRT file and give it back the result, it might be able to steal data. That could potentially be able for a web service accepting data from the user, converting it, and sending back the result.
see gdal.org/en/stable/user/security.html#gdal-vrt-driver
Thus we recommend deploying titiler in infrastructure with limited access to the filesystem. Users can also disable the VRT driver completely by using GDAL_SKIP=VRT environment variable.
In GDAL 3.12, new environment variables might be introduced to enable more control over the VRT driver: OSGeo/gdal!12669
"},{"location":"security/#limit-sources-host","title":"Limit source's host","text":"If users want to limit the sources that the application can access, they can also create custom path_dependency such as this one which limits valid sources to a list of known hosts:
from urllib.parse import urlparse\n\nfrom typing import Annotated\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI, Query, HTTPException\n\n# List of known host where dataset can be read from\nknown_host = [\n \"devseed.org\",\n]\n\ndef DatasetPathParams(url: Annotated[str, Query(description=\"Dataset URL\")]) -> str:\n \"\"\"Create dataset path from args\"\"\"\n # validate Dataset host\n parsed = urlparse(url)\n if parsed.netloc not in known_host:\n raise HTTPException(\n status_code=400,\n detail=\"Nope, this is not a valid File - Please Try Again\",\n )\n\n return url\n\n\napp = FastAPI(title=\"My simple app\")\napp.include_router(TilerFactory(path_dependency=DatasetPathParams).router)\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\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/#titilerextensionscogvalidateextension","title":"titiler.extensions.cogValidateExtension","text":"/validate endpoint which return the content of rio-cogeo info methodtitiler.extensions[\"cogeo\"] (installs rio-cogeo)/viewer endpoint which return an HTML viewer for simple COGs/viewer endpoint which return an HTML viewer for STAC item/stac endpoint which return an HTML viewer for STAC itemtitiler.extensions[\"stac\"] (installs rio-stac)/wms endpoint to support OGC WMS specification (GetCapabilities and GetMap)/WMTSCapabilities.xml endpoint to support OGC WMTS RESTFULL specification (GetCapabilities and GetTile)/render and /render/{render_id} endpoints which return the contents of STAC render extension and links to tileset.json and WMTS service/dataset/, /dataset/keys and /datasets/dict endpoints which return metadata about a multidimensional Dataset (not a DataArray)/WMTSCapabilities.xml to support OGC WMTS RESTFULL specification (GetCapabilities and GetTile)/ and /validate endpoints to return MosaicJSON content and validate external mosaics.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"},{"location":"advanced/Extensions/#create-your-own","title":"Create your own","text":"from dataclasses import dataclass, field\nfrom typing import Tuple, List, Optional\nimport rasterio\nfrom starlette.responses import Response\nfrom fastapi import Depends, FastAPI, Query\nfrom titiler.core.factory import TilerFactory, FactoryExtension\nfrom titiler.core.dependencies import ImageRenderingParams\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 TilerFactory object as input\n def register(self, factory: TilerFactory):\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 reader_params=Depends(factory.reader_dependency),\n layer_params=Depends(factory.layer_dependency),\n dataset_params=Depends(factory.dataset_dependency),\n post_process=Depends(factory.process_dependency),\n colormap=Depends(factory.colormap_dependency),\n render_params=Depends(factory.render_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 format = ImageType.jpeg if image.mask.all() else ImageType.png\n\n if post_process:\n image = post_process(image)\n\n content, media_type = factory.render_func(\n image,\n colormap=colormap,\n **render_params.as_dict(),\n )\n\n return Response(content, media_type=media_type)\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 users customize input/output for each endpoint. This section goes over some simple customization examples.
Add user defined colormap to the default colormaps provided by rio-tiler
from fastapi import FastAPI\n\nfrom rio_tiler.colormap import cmap as default_cmap\n\nfrom titiler.core.dependencies import create_colormap_dependency\nfrom titiler.core.factory import TilerFactory\n\n\napp = FastAPI(title=\"My simple app with custom TMS\")\n\ncmap_values = {\n \"cmap1\": {6: (4, 5, 6, 255)},\n}\n# add custom colormap `cmap1` to the default colormaps\ncmap = default_cmap.register(cmap_values)\nColorMapParams = create_colormap_dependency(cmap)\n\n\ncog = TilerFactory(colormap_dependency=ColorMapParams)\napp.include_router(cog.router)\n"},{"location":"advanced/customization/#custom-datasetpathparams-for-reader_dependency","title":"Custom DatasetPathParams for reader_dependency","text":"One common customization could be to create your own path_dependency. This dependency is used on all endpoint and pass inputs to the Readers (MosaicBackend, COGReader, STACReader...).
Here an example which allow a mosaic to be passed by a mosaic name instead of a full S3 url.
import os\nimport re\n\nfrom fastapi import FastAPI, HTTPException, Query\n\nfrom titiler.mosaic.factory import MosaicTilerFactory\n\n\nMOSAIC_BACKEND = os.getenv(\"TITILER_MOSAIC_BACKEND\")\nMOSAIC_HOST = os.getenv(\"TITILER_MOSAIC_HOST\")\n\n\ndef MosaicPathParams(\n mosaic: str = Query(..., description=\"mosaic name\")\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n # mosaic name should be in form of `{user}.{layername}`\n if not re.match(self.mosaic, r\"^[a-zA-Z0-9-_]{1,32}\\.[a-zA-Z0-9-_]{1,32}$\"):\n raise HTTPException(\n status_code=400,\n detail=f\"Invalid mosaic name {self.input}.\",\n )\n\n return f\"{MOSAIC_BACKEND}{MOSAIC_HOST}/{self.input}.json.gz\"\n\n\napp = FastAPI()\nmosaic = MosaicTilerFactory(path_dependency=MosaicPathParams)\napp.include_router(mosaic.router)\n The endpoint url will now look like: {endpoint}/mosaic/tilejson.json?mosaic=vincent.mosaic
from morecantile import tms, TileMatrixSet\nfrom pyproj import CRS\n\nfrom titiler.core.factory import TilerFactory\n\n# 1. Create Custom TMS\nEPSG6933 = TileMatrixSet.custom(\n (-17357881.81713629, -7324184.56362408, 17357881.81713629, 7324184.56362408),\n CRS.from_epsg(6933),\n identifier=\"EPSG6933\",\n matrix_scale=[1, 1],\n)\n\n# 2. Register TMS\ntms = tms.register([EPSG6933])\n\n# 3. Create Tiler\nCOGTilerWithCustomTMS = TilerFactory(supported_tms=tms)\n"},{"location":"advanced/customization/#add-a-mosaicjson-creation-endpoint","title":"Add a MosaicJSON creation endpoint","text":"from typing import List, Optional\n\nfrom attrs import define\n\nfrom cogeo_mosaic.backends import MosaicBackend as MosaicJSONBackend\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@define(kw_only=True)\nclass CustomMosaicFactory(MosaicTilerFactory):\n\n backend: Type[MosaicJSONBackend] = MosaicJSONBackend\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.backend(\n body.url, \n mosaic_def=mosaic, \n 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.backend(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 typing import Annotated\n\nfrom dataclasses import dataclass\nfrom fastapi import Depends, FastAPI, Query\nfrom titiler.core.dependencies import DefaultDependency\nfrom rio_tiler.io import Reader\n\n@dataclass\nclass ImageParams(DefaultDependency):\n max_size: Annotated[\n int, Query(description=\"Maximum image size to read onto.\")\n ] = 1024\n\napp = FastAPI()\n\n# Simple preview endpoint\n@app.get(\"/preview.png\")\ndef preview(\n url: str = Query(..., description=\"data set URL\"),\n params: ImageParams = Depends(),\n):\n with Reader(url) as cog:\n img = cog.preview(**params.as_dict()) # we use `DefaultDependency().as_dict()` to pass only non-None parameters\n # or\n img = cog.preview(max_size=params.max_size)\n ...\n Important
In the example above, we create a custom ImageParams dependency which will then be injected to the preview endpoint to add max_size, height and width query string parameters.
Using titiler.core.dependencies.DefaultDependency, we can use .as_dict(exclude_none=True/False) method to unpack the object parameters. This can be useful if method or reader do not take the same parameters.
Define assets.
@dataclass\nclass AssetsParams(DefaultDependency):\n \"\"\"Assets parameters.\"\"\"\n\n assets: Annotated[\n list[str],\n Query(\n title=\"Asset names\",\n description=\"Asset's names.\",\n openapi_examples={\n \"user-provided\": {\"value\": None},\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 ]\n"},{"location":"advanced/dependencies/#assetsexprparams","title":"AssetsExprParams","text":"Define assets.
* assets is required.
@dataclass\nclass AssetsExprParams(AssetsParams, ExpressionParams):\n \"\"\"Assets and Expression parameters.\"\"\"\n\n asset_as_band: Annotated[\n bool | None,\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:\n raise MissingAssets(\"assets must be defined\")\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={\n \"user-provided\": {\"value\": None},\n \"one-band\": {\"value\": [1]},\n \"multi-bands\": {\"value\": [1, 2, 3]},\n },\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 \"user-provided\": {\"value\": None},\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/#colormapparams","title":"ColorMapParams","text":"Colormap options. See titiler.core.dependencies.
Name Type Required Default colormap_name Query (str) No None colormap Query (encoded json) No Nonecmap = {}\n\ndef ColorMapParams(\n colormap_name: Annotated[ # type: ignore\n Literal[tuple(cmap.list())],\n Query(description=\"Colormap name\"),\n ] = None,\n colormap: Annotated[\n Optional[str], Query(description=\"JSON encoded custom Colormap\")\n ] = None,\n):\n if colormap_name:\n return cmap.get(colormap_name)\n\n if colormap:\n try:\n c = json.loads(\n colormap,\n object_hook=lambda x: {\n int(k): parse_color(v) for k, v in x.items()\n },\n )\n\n # Make sure to match colormap type\n if isinstance(c, Sequence):\n c = [(tuple(inter), parse_color(v)) for (inter, v) in c]\n\n return c\n except json.JSONDecodeError as e:\n raise HTTPException(\n status_code=400, detail=\"Could not parse the colormap value.\"\n ) from e\n\n return None\n"},{"location":"advanced/dependencies/#coordcrsparams","title":"CoordCRSParams","text":"Define input Coordinate Reference System.
Name Type Required Default crs Query (str) No Nonedef CoordCRSParams(\n crs: Annotated[\n Optional[str],\n Query(\n alias=\"coord_crs\",\n description=\"Coordinate Reference System of the input coords. Default to `epsg:4326`.\",\n ),\n ] = None,\n) -> Optional[CRS]:\n \"\"\"Coordinate Reference System Coordinates Param.\"\"\"\n if crs:\n return CRS.from_user_input(crs)\n\n return None\n"},{"location":"advanced/dependencies/#datasetparams","title":"DatasetParams","text":"Overwrite nodata value, apply rescaling and change the I/O or Warp resamplings.
@dataclass\nclass DatasetParams(DefaultDependency):\n \"\"\"Low level WarpedVRT Optional parameters.\"\"\"\n\n nodata: Annotated[\n Optional[Union[str, int, float]],\n Query(\n title=\"Nodata value\",\n description=\"Overwrite internal Nodata value\",\n ),\n ] = None\n unscale: Annotated[\n bool,\n Query(\n title=\"Apply internal Scale/Offset\",\n description=\"Apply internal Scale/Offset. Defaults to `False` in rio-tiler.\",\n ),\n ] = False\n resampling_method: Annotated[\n Optional[RIOResampling],\n Query(\n alias=\"resampling\",\n description=\"RasterIO resampling algorithm. Defaults to `nearest` in rio-tiler.\",\n ),\n ] = None\n reproject_method: Annotated[\n Optional[WarpResampling],\n Query(\n alias=\"reproject\",\n description=\"WarpKernel resampling algorithm (only used when doing re-projection). Defaults to `nearest` in rio-tiler.\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.nodata is not None:\n self.nodata = numpy.nan if self.nodata == \"nan\" else float(self.nodata)\n\n if self.unscale is not None:\n self.unscale = bool(self.unscale)\n"},{"location":"advanced/dependencies/#datasetpathparams","title":"DatasetPathParams","text":"Set dataset path.
Name Type Required Default url Query (str) Yes -def DatasetPathParams(\n url: Annotated[str, Query(description=\"Dataset URL\")]\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n return url\n"},{"location":"advanced/dependencies/#dstcrsparams","title":"DstCRSParams","text":"Define output Coordinate Reference System.
Name Type Required Default crs Query (str) No Nonedef DstCRSParams(\n crs: Annotated[\n Optional[str],\n Query(\n alias=\"dst_crs\",\n description=\"Output Coordinate Reference System.\",\n ),\n ] = None,\n) -> Optional[CRS]:\n \"\"\"Coordinate Reference System Coordinates Param.\"\"\"\n if crs:\n return CRS.from_user_input(crs)\n\n return None\n"},{"location":"advanced/dependencies/#histogramparams","title":"HistogramParams","text":"Define numpy's histogram options.
Name Type Required Default histogram_bins Query (encoded list of Number) No 10 histogram_range Query (encoded list of Number) No None@dataclass\nclass HistogramParams(DefaultDependency):\n \"\"\"Numpy Histogram options.\"\"\"\n\n bins: Annotated[\n Optional[str],\n Query(\n alias=\"histogram_bins\",\n title=\"Histogram bins.\",\n description=\"\"\"\nDefines the number of equal-width bins in the given range (10, by default).\n\nIf bins is a sequence (comma `,` delimited values), it defines a monotonically increasing array of bin edges, including the rightmost edge, allowing for non-uniform bin widths.\n\nlink: https://numpy.org/doc/stable/reference/generated/numpy.histogram.html\n \"\"\",\n openapi_examples={\n \"user-provided\": {\"value\": None},\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 rescale Query (str, comma delimited Numer) No None color_formula Query (str) No None return_mask Query (bool) No False@dataclass\nclass ImageRenderingParams(DefaultDependency):\n \"\"\"Image Rendering options.\"\"\"\n\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\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\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\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.rescale:\n rescale_array = []\n for r in self.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: {self.rescale}, should be of form ['min,max', 'min,max'] or [[min,max], [min, max]]\"\n rescale_array.append(parsed)\n\n self.rescale: RescaleType = rescale_array # Noqa\n"},{"location":"advanced/dependencies/#partfeatureparams","title":"PartFeatureParams","text":"Same as PreviewParams but without default max_size.
@dataclass\nclass PreviewParams(DefaultDependency):\n \"\"\"Common Preview parameters.\"\"\"\n\n # NOTE: sizes dependency can either be a Query or a Path Parameter\n max_size: Annotated[int, Field(description=\"Maximum image size to read onto.\")] = (\n 1024\n )\n height: Annotated[\n Optional[int], Field(description=\"Force output image height.\")\n ] = None\n width: Annotated[Optional[int], Field(description=\"Force output image width.\")] = (\n None\n )\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width or self.height:\n self.max_size = None\n"},{"location":"advanced/dependencies/#pixelselectionparams","title":"PixelSelectionParams","text":"In titiler.mosaic, define pixel-selection method to apply.
def PixelSelectionParams(\n pixel_selection: Annotated[ # type: ignore\n Literal[tuple([e.name for e in PixelSelectionMethod])],\n Query(description=\"Pixel selection method.\"),\n ] = \"first\",\n) -> MosaicMethodBase:\n \"\"\"\n Returns the mosaic method used to combine datasets together.\n \"\"\"\n return PixelSelectionMethod[pixel_selection].value()\n"},{"location":"advanced/dependencies/#previewparams","title":"PreviewParams","text":"Define image output size.
Name Type Required Default max_size Query (int) No 1024 height Query (int) No None width Query (int) No None@dataclass\nclass PreviewParams(DefaultDependency):\n \"\"\"Common Preview parameters.\"\"\"\n\n max_size: Annotated[int, \"Maximum image size to read onto.\"] = 1024\n height: Annotated[Optional[int], \"Force output image height.\"] = None\n width: Annotated[Optional[int], \"Force output image width.\"] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width and self.height:\n self.max_size = None\n"},{"location":"advanced/dependencies/#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":"Define buffer and padding to apply at tile creation.
@dataclass\nclass TileParams(DefaultDependency):\n \"\"\"Tile options.\"\"\"\n\n buffer: Annotated[\n Optional[float],\n Query(\n gt=0,\n title=\"Tile buffer.\",\n description=\"Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).\",\n ),\n ] = None\n\n padding: Annotated[\n Optional[int],\n Query(\n gt=0,\n title=\"Tile padding.\",\n description=\"Padding to apply to each tile edge. Helps reduce resampling artefacts along edges. Defaults to `0`.\",\n ),\n ] = None\n"},{"location":"advanced/dependencies/#algorithmdependency","title":"algorithm.dependency","text":"Control which algorithm to apply to the data.
See titiler.core.algorithm.
Name Type Required Default algorithm Query (str) No None algorithm_params Query (encoded json) No Nonealgorithms = {}\n\ndef post_process(\n algorithm: Annotated[\n Literal[tuple(algorithms.keys())],\n Query(description=\"Algorithm name\"),\n ] = None,\n algorithm_params: Annotated[\n Optional[str],\n Query(description=\"Algorithm parameter\"),\n ] = None,\n) -> Optional[BaseAlgorithm]:\n \"\"\"Data Post-Processing options.\"\"\"\n kwargs = json.loads(algorithm_params) if algorithm_params else {}\n if algorithm:\n try:\n return algorithms.get(algorithm)(**kwargs)\n\n except ValidationError as e:\n raise HTTPException(status_code=400, detail=str(e)) from e\n\n return None\n"},{"location":"advanced/dependencies/#titilerxarray","title":"titiler.xarray","text":""},{"location":"advanced/dependencies/#xarrayioparams","title":"XarrayIOParams","text":"Define Xarray's open_args to xarray.open_dataset.
@dataclass\nclass XarrayIOParams(DefaultDependency):\n \"\"\"Dataset IO Options.\"\"\"\n\n group: Annotated[\n Optional[str],\n Query(\n description=\"Select a specific zarr group from a zarr hierarchy. Could be associated with a zoom level or dataset.\"\n ),\n ] = None\n\n decode_times: Annotated[\n Optional[bool],\n Query(\n title=\"decode_times\",\n description=\"Whether to decode times\",\n ),\n ] = None\n"},{"location":"advanced/dependencies/#xarraydsparams","title":"XarrayDsParams","text":"Define options to select a variable within a Xarray Dataset.
Name Type Required Default variable Query (str) Yes None sel Query (list of str) No None@dataclass\nclass XarrayDsParams(DefaultDependency):\n \"\"\"Xarray Dataset Options.\"\"\"\n\n variable: Annotated[str, Query(description=\"Xarray Variable name.\")]\n\n sel: Annotated[\n Optional[List[SelDimStr]],\n Query(\n description=\"Xarray Indexing using dimension names `{dimension}={value}` or `{dimension}={method}::{value}`.\",\n ),\n ] = None\n"},{"location":"advanced/dependencies/#xarrayparams","title":"XarrayParams","text":"Combination of XarrayIOParams and XarrayDsParams
@dataclass\nclass XarrayParams(XarrayIOParams, XarrayDsParams):\n \"\"\"Xarray Reader dependency.\"\"\"\n\n pass\n"},{"location":"advanced/dependencies/#compatxarrayparams","title":"CompatXarrayParams","text":"same as XarrayParams but with optional variable option.
@dataclass\nclass XarrayParams(XarrayIOParams, XarrayDsParams):\n \"\"\"Xarray Reader dependency.\"\"\"\n\n pass\n"},{"location":"advanced/dependencies/#datasetparams_1","title":"DatasetParams","text":"Same as titiler.core.dependencies.DatasetParams but with only nodata and reproject
@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 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"},{"location":"advanced/dependencies/#partfeatureparams_1","title":"PartFeatureParams","text":"Same as titiler.core.dependencies.PartFeatureParams but with resampling option
@dataclass\nclass PartFeatureParams(DefaultDependency):\n \"\"\"Common parameters for bbox and feature.\"\"\"\n\n # NOTE: the part sizes dependency can either be a Query or a Path Parameter\n max_size: Annotated[\n Optional[int], Field(description=\"Maximum image size to read onto.\")\n ] = None\n height: Annotated[\n Optional[int], Field(description=\"Force output image height.\")\n ] = None\n width: Annotated[Optional[int], Field(description=\"Force output image width.\")] = (\n None\n )\n resampling_method: Annotated[\n Optional[RIOResampling],\n Query(\n alias=\"resampling\",\n description=\"RasterIO resampling algorithm. Defaults to `nearest`.\",\n ),\n ] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width or self.height:\n self.max_size = 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/#titilercore","title":"titiler.core","text":""},{"location":"advanced/endpoints_factories/#basefactory","title":"BaseFactory","text":"class: titiler.core.factory.BaseFactory
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/#attributes","title":"Attributes","text":"fastapi.APIRouter.\"\".[].[].None.operationId prefix. Defined by self.name or self.router_prefix.replace(\"/\", \".\").class: titiler.core.factory.TilerFactory
Factory meant to create endpoints for single dataset using rio-tiler's Reader.
titiler.core.dependencies.DefaultDependencytitiler.core.dependencies.DatasetPathParams.titiler.core.dependencies.BidxExprParams.nodata value, apply rescaling and change the I/O or Warp resamplings. Defaults to titiler.core.dependencies.DatasetParams.buffer and padding to apply at tile creation. Defaults to titiler.core.dependencies.TileParams./statistics endpoints. Defaults to titiler.core.dependencies.StatisticsParams./statistics endpoints. Defaults to titiler.core.dependencies.HistogramParams./preview and /statistics endpoints. Defaults to titiler.core.dependencies.PreviewParams./bbox and /feature endpoints. Defaults to titiler.core.dependencies.PartFeatureParams.algorithm to apply to the data. Defaults to titiler.core.algorithm.algorithms.dependency.titiler.core.dependencies.ColorMapParamstitiler.core.dependencies.ImageRenderingParamslambda: {}.morecantile.tms.titiler.core.factory.DEFAULT_TEMPLATES.titiler.core.utils.render_image./preview endpoint to the router. Defaults to True./bbox and /feature endpoints to the router. Defaults to True./{TileMatrixSetId}/map.html endpoints to the router. Defaults to True./map endoint (OGC Maps API) to the router. Defaults to False.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 add_ogc_maps=True,\n)\n\n# add router endpoint to the main application\napp.include_router(cog.router)\n Method URL Output Description 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 JSON List of OGC Tilesets available GET /tiles/{tileMatrixSetId} JSON OGC Tileset metadata GET /tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}] image/bin create a web map tile image from a dataset GET /{tileMatrixSetId}/map.html HTML return a simple map viewer Optional GET /{tileMatrixSetId}/tilejson.json JSON (TileJSON) return a Mapbox TileJSON document GET /point/{lon},{lat} JSON (Point) return pixel values from a dataset GET /bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format} image/bin create an image from part of a dataset Optional POST /feature[/{width}x{height}][.{format}] image/bin create an image from a GeoJSON feature Optional GET /preview[/{width}x{height}][.{format}] image/bin create a preview image from a dataset Optional GET /maps image/bin create maps from a dataset 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).
rio_tiler.io.base.MultiBaseReader Dataset Reader required.titiler.core.dependencies.AssetsExprParams.titiler.core.dependencies.AssetsParams.from fastapi import FastAPI\n\nfrom rio_tiler.io import STACReader # STACReader is a MultiBaseReader\n\nfrom titiler.core.factory import MultiBaseTilerFactory\n\napp = FastAPI()\nstac = MultiBaseTilerFactory(\n reader=STACReader,\n add_preview=True,\n add_part=True,\n add_viewer=True,\n add_ogc_maps=True,\n)\napp.include_router(stac.router)\n Method URL Output Description 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 JSON List of OGC Tilesets available GET /tiles/{tileMatrixSetId} JSON OGC Tileset metadata GET /tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}] image/bin create a web map tile image from assets GET /{tileMatrixSetId}/map.html HTML return a simple map viewer Optional GET /{tileMatrixSetId}/tilejson.json JSON (TileJSON) return a Mapbox TileJSON document GET /point/{lon},{lat} JSON (Point) return pixel values from assets 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 /preview[/{width}x{height}][.{format}] image/bin create a preview image from assets Optional GET /map image/bin create maps from a dataset Optional"},{"location":"advanced/endpoints_factories/#tmsfactory","title":"TMSFactory","text":"class: titiler.core.factory.TMSFactory
Endpoints factory for OGC TileMatrixSets.
morecantile.tms.from fastapi import FastAPI\n\nfrom titiler.core.factory import TMSFactory\n\napp = FastAPI()\ntms = TMSFactory()\napp.include_router(tms.router)\n"},{"location":"advanced/endpoints_factories/#endpoints_2","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_4","title":"Attributes","text":"Algorithm. Defaults to titiler.core.algorithm.algorithms.from fastapi import FastAPI\n\nfrom titiler.core.factory import AlgorithmFactory\n\napp = FastAPI()\nalgo = AlgorithmFactory()\napp.include_router(algo.router)\n"},{"location":"advanced/endpoints_factories/#endpoints_3","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_5","title":"Attributes","text":"ColorMaps. Defaults to rio_tiler.colormap.cmap.from fastapi import FastAPI\n\nfrom titiler.core.factory import ColorMapFactory\n\napp = FastAPI()\ncolormap = ColorMapFactory()\napp.include_router(colormap.router)\n"},{"location":"advanced/endpoints_factories/#endpoints_4","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/endpoints_factories/#titilermosaic","title":"titiler.mosaic","text":""},{"location":"advanced/endpoints_factories/#mosaictilerfactory","title":"MosaicTilerFactory","text":"class: titiler.mosaic.factory.MosaicTilerFactory
Endpoints factory for mosaics.
"},{"location":"advanced/endpoints_factories/#attributes_6","title":"Attributes","text":"rio_tiler.mosaic.backends.BaseBackend Mosaic backend.titiler.core.dependencies.DefaultDependencyrio_tiler.io.Readertitiler.core.dependencies.DefaultDependencytitiler.mosaic.factory.DatasetPathParams.get_assets method. Defaults to titiler.core.dependencies.DefaultDependency.titiler.core.dependencies.BidxExprParams.nodata value, apply rescaling and change the I/O or Warp resamplings. Defaults to titiler.core.dependencies.DatasetParams.buffer and padding to apply at tile creation. Defaults to titiler.core.dependencies.TileParams.algorithm to apply to the data. Defaults to titiler.core.algorithm.algorithms.dependency./statistics endpoints. Defaults to titiler.core.dependencies.StatisticsParams./statistics endpoints. Defaults to titiler.core.dependencies.HistogramParams.titiler.core.dependencies.ColorMapParamstitiler.core.dependencies.ImageRenderingParamspixel_selection method. Defaults to titiler.mosaic.factory.PixelSelectionParams.lambda: {}.morecantile.tms.titiler.core.utils.render_image.[].titiler.core.factory.DEFAULT_TEMPLATES./{TileMatrixSetId}/map.html endpoints to the router. Defaults to True.POST - /statistics endpoints to the router. Defaults to False./bbox and /feature endpoints to the router. Defaults to False./map endpoints to the router. Default to False.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 JSON List of OGC Tilesets available GET /tiles/{tileMatrixSetId} JSON OGC Tileset metadata GET /tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}] image/bin create a web map tile image from a MosaicJSON GET /tiles/{tileMatrixSetId}/{z}/{x}/{y}/assets JSON return list of assets intersecting a XYZ tile GET /{tileMatrixSetId}/map.html HTML return a simple map viewer Optional GET /{tileMatrixSetId}/tilejson.json JSON (TileJSON) return a Mapbox TileJSON document GET /point/{lon},{lat} JSON (Point) return pixel value from a MosaicJSON dataset GET /point/{lon},{lat}/assets JSON return list of assets intersecting a point GET /bbox/{minx},{miny},{maxx},{maxy}/assets JSON return list of assets intersecting a bounding box POST /statistics GeoJSON (Statistics) return info and statistics for 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 /map image/bin create maps from a dataset Optional from fastapi import FastAPI\n\nfrom titiler.mosaic.factory import MosaicTilerFactory\nfrom cogeo_mosaic.backends import MosaicBackend as MosaicJSONBackend\n\n# Create FastAPI application\napp = FastAPI()\n\n# Create router and register set of endpoints\nmosaic = TilerFactory(\n backend=MosaicJSONBackend,\n add_part=True, # default to False\n add_statistics=True, # default to False\n add_ogc_maps=True, # default to False\n)\n\n# add router endpoint to the main application\napp.include_router(mosaic.router)\n"},{"location":"advanced/endpoints_factories/#titilerxarray","title":"titiler.xarray","text":""},{"location":"advanced/endpoints_factories/#tilerfactory_1","title":"TilerFactory","text":"class: titiler.xarray.factory.TilerFactory
titiler.core.dependencies.DatasetPathParams.titiler.xarray.dependencies.XarrayParamstitiler.core.dependencies.BidxParams.nodata value and change the Warp resamplings. Defaults to titiler.xarray.dependencies.DatasetParams.titiler.core.dependencies.DefaultDependency./statistics endpoints. Defaults to titiler.core.dependencies.StatisticsParams./statistics endpoints. Defaults to titiler.core.dependencies.HistogramParams./bbox and /feature endpoints. Defaults to titiler.xarray.dependencies.PartFeatureParams.algorithm to apply to the data. Defaults to titiler.core.algorithm.algorithms.dependency.titiler.core.dependencies.ColorMapParamstitiler.core.dependencies.ImageRenderingParamslambda: {}.morecantile.tms.titiler.core.factory.DEFAULT_TEMPLATES./bbox and /feature endpoints to the router. Defaults to True./{TileMatrixSetId}/map.html endpoints to the router. Defaults to True./map endpoints to the router. Default to False./preview endpoints to the router. Default to False.from fastapi import FastAPI\n\nfrom titiler.xarray.factory import TilerFactory\n\n# Create FastAPI application\napp = FastAPI()\n\n# Create router and register set of endpoints\nmd = TilerFactory(\n add_part=True, # default to True\n add_viewer=True, # default to True\n add_preview=True, # default to False\n)\n\n# add router endpoint to the main application\napp.include_router(md.router)\n"},{"location":"advanced/endpoints_factories/#endpoints_6","title":"Endpoints","text":"Method URL Output Description GET /info JSON (Info) return dataset's basic info GET /info.geojson GeoJSON (InfoGeoJSON) return dataset's basic info as a GeoJSON feature POST /statistics GeoJSON (Statistics) return dataset's statistics for a GeoJSON GET /tiles JSON List of OGC Tilesets available GET /tiles/{tileMatrixSetId} JSON OGC Tileset metadata GET /tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}] image/bin create a web map tile image from a dataset GET /{tileMatrixSetId}/map.html HTML return a simple map viewer Optional GET /{tileMatrixSetId}/tilejson.json JSON (TileJSON) return a Mapbox TileJSON document GET /point/{lon},{lat} JSON (Point) return pixel values from a dataset GET /bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format} image/bin create an image from part of a dataset Optional POST /feature[/{width}x{height}][.{format}] image/bin create an image from a GeoJSON feature Optional GET /preview[/{width}x{height}][.{format}] image/bin create a preview image from a dataset Optional"},{"location":"advanced/performance_tuning/","title":"Performance Tuning","text":""},{"location":"advanced/performance_tuning/#overview","title":"Overview","text":"TiTiler makes use of several great underlying libraries, including GDAL and Python bindings to GDAL. An effective deployment of titiler generally requires tweaking GDAL configuration settings. This document provides an overview of relevant settings. Full documentation from GDAL is available here.
"},{"location":"advanced/performance_tuning/#gdal-configuration","title":"GDAL Configuration","text":""},{"location":"advanced/performance_tuning/#setting-a-config-variable","title":"Setting a config variable","text":"GDAL configuration is modified using environment variables. Thus in order to change a setting, you'll need to set environment variables through your deployment mechanism. For example, in order to test locally you'd set an environment variable in bash:
export GDAL_HTTP_MULTIPLEX=YES\n"},{"location":"advanced/performance_tuning/#available-configuration-settings","title":"Available configuration settings","text":""},{"location":"advanced/performance_tuning/#gdal_http_merge_consecutive_ranges","title":"GDAL_HTTP_MERGE_CONSECUTIVE_RANGES","text":"When set to YES, this tells GDAL to merge adjacent range requests. Instead of making two requests for byte ranges 1-5 and 6-10, it would make a single request for 1-10. This should always be set to YES.
GDAL_DISABLE_READDIR_ON_OPEN","text":"This is a very important setting to control the number of requests GDAL makes.
This setting has two options: FALSE and EMPTY_DIR. FALSE (the default) causes GDAL to try to establish a list of all the available files in the directory. EMPTY_DIR tells GDAL to imagine that the directory is empty except for the requested file.
When reading datasets with necessary external sidecar files, it's imperative to set FALSE. For example, the landsat-pds bucket on AWS S3 contains GeoTIFF images where overviews are in external .ovr files. If set to EMPTY_DIR, GDAL won't find the .ovr files.
However, in all other cases, it's much better to set EMPTY_DIR because this prevents GDAL from making a LIST request.
This setting also has cost implications for reading data from requester-pays buckets. When set to FALSE, GDAL makes a LIST request every time it opens a file. Since LIST requests are much more expensive than GET requests, this can bring unexpected costs.
CPL_VSIL_CURL_ALLOWED_EXTENSIONS","text":"A list of file extensions that GDAL is allowed to open. For example if set to .tif, then GDAL would only open files with a .tif extension. For example, it would fail on JPEG2000 files with a .jp2 extension, but also wouldn't open GeoTIFFs exposed through an API endpoint that don't have a .tif suffix.
Note that you also need to include extensions of external overview files. For example, the landsat-pds bucket on AWS S3 has external overviews in .ovr files, so if you wished to read this data, you'd want
GDAL_INGESTED_BYTES_AT_OPEN","text":"Defines the number of initial bytes GDAL should read when opening a file and inspecting its metadata.
TiTiler works best with Cloud-Optimized GeoTIFFs (COGs) because they have a tiled internal structure that supports efficient random reads. These files have an initial metadata section that describes the location (byte range) within the file of each internal tile. The more internal tiles the COG has, the more data the header needs to contain.
GDAL needs to read the entire header before it can read any other portion of the file. By default GDAL reads the first 16KB of the file, then if that doesn't contain the entire metadata, it makes one more request for the rest of the metadata.
In environments where latency is relatively high (at least compared to bandwidth), such as AWS S3, it may be beneficial to increase this value depending on the data you expect to read.
There isn't currently a way to get the number of header bytes using GDAL, but alternative GeoTIFF readers such as aiocogeo can. Using its cli you can find the image's header size:
export AWS_REQUEST_PAYER=\"requester\"\naiocogeo info s3://usgs-landsat/collection02/level-2/standard/oli-tirs/2020/072/076/LC08_L2SR_072076_20201203_20210313_02_T2/LC08_L2SR_072076_20201203_20210313_02_T2_SR_B1.TIF\n\n PROFILE\n ...\n Header size: 32770\n It's wise to inspect the header sizes of your data sources, and set GDAL_INGESTED_BYTES_AT_OPEN appropriately. Beware, however, that the given number of bytes will be read for every image, so you don't want to make the value too large.
GDAL_CACHEMAX","text":"Default GDAL block cache. The value can be either in Mb, bytes or percent of the physical RAM
Recommended: 200 (200Mb)
"},{"location":"advanced/performance_tuning/#cpl_vsil_curl_cache_size","title":"CPL_VSIL_CURL_CACHE_SIZE","text":"A global least-recently-used cache shared among all downloaded content and may be reused after a file handle has been closed and reopen
Recommended: 200000000 (200Mb)
"},{"location":"advanced/performance_tuning/#vsi_cache","title":"VSI_CACHE","text":"Setting this to TRUE enables GDAL to use an internal caching mechanism. It's
Recommended (Strongly): TRUE.
"},{"location":"advanced/performance_tuning/#vsi_cache_size","title":"VSI_CACHE_SIZE","text":"The size of the above VSI cache in bytes per-file handle. If you open a VRT with 10 files and your VSI_CACHE_SIZE is 10 bytes, the total cache memory usage would be 100 bytes. The cache is RAM based and the content of the cache is discarded when the file handle is closed.
Recommended: 5000000 (5Mb per file handle)
"},{"location":"advanced/performance_tuning/#gdal_band_block_cache","title":"GDAL_BAND_BLOCK_CACHE","text":"GDAL Block Cache type: ARRAY or HASHSET. See gdal.org/development/rfc/rfc26_blockcache.html
PROJ_NETWORK","text":"Introduced with GDAL 3 and PROJ>7, the PROJ library can fetch more precise transformation grids hosted on the cloud.
Values: ON/OFF
Ref: proj.org/usage/network.html
"},{"location":"advanced/performance_tuning/#gdal_http_multiplex","title":"GDAL_HTTP_MULTIPLEX","text":"When set to YES, this attempts to download multiple range requests in parallel, reusing the same TCP connection. Note this is only possible when the server supports HTTP2, which many servers don't yet support. There's no downside to setting YES here.
GDAL_DATA","text":"The GDAL_DATA variable tells rasterio/GDAL where the GDAL C libraries have been installed. When using rasterio wheels, GDAL_DATA must be unset.
PROJ_LIB","text":"The PROJ_LIB variable tells rasterio/GDAL where the PROJ C libraries have been installed. When using rasterio wheels, PROJ_LIB must be unset.
AWS_REQUEST_PAYER","text":""},{"location":"advanced/performance_tuning/#recommended-configuration-for-dynamic-tiling","title":"Recommended Configuration for dynamic tiling","text":"CPL_VSIL_CURL_ALLOWED_EXTENSIONS=\".tif,.TIF,.tiff\"In addition to GDAL_DISABLE_READDIR_ON_OPEN, we set the allowed extensions to .tif to only enable tif files. (OPTIONAL)
GDAL_CACHEMAX=\"200\"200 Mb Cache.
CPL_VSIL_CURL_CACHE_SIZE=\"200000000200 Mb VSI Cache.
GDAL_BAND_BLOCK_CACHE=\"HASHSET\"
GDAL_DISABLE_READDIR_ON_OPEN=\"EMPTY_DIR\"
Maybe the most important variable. Setting it to EMPTY_DIR reduce the number of GET/LIST requests.
GDAL_HTTP_MERGE_CONSECUTIVE_RANGES=\"YES\"Tells GDAL to merge consecutive range GET requests.
GDAL_HTTP_MULTIPLEX=\"YES\"GDAL_HTTP_VERSION=\"2\"Both Multiplex and HTTP_VERSION will only have impact if the files are stored in an environment which support HTTP 2 (e.g cloudfront).
VSI_CACHE=\"TRUE\"VSI_CACHE_SIZE=\"5000000\"5Mb cache per file handle.
"},{"location":"advanced/telemetry/","title":"Observability with OpenTelemetry","text":""},{"location":"advanced/telemetry/#observability-with-opentelemetry","title":"Observability with OpenTelemetry","text":"TiTiler provides built-in observability through OpenTelemetry, automatically creating traces for all API endpoints. These traces include detailed spans for key internal operations like data access and image processing, enabling fine-grained performance analysis and debugging.
This instrumentation works seamlessly with other OpenTelemetry libraries, such as FastAPIInstrumentor, to provide a complete, end-to-end view of your application's performance, from incoming request to final response.
"},{"location":"advanced/telemetry/#installation","title":"Installation","text":"To enable telemetry, you must install titiler.core with the [telemetry] extra. This ensures all necessary OpenTelemetry packages are installed.
python -m pip install -U pip\n\n# From Pypi\npython -m pip install titiler.core[telemetry]\n\n# Or from sources\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler && python -m pip install -e src/titiler/core[telemetry]\n"},{"location":"advanced/telemetry/#configuration","title":"Configuration","text":"To export traces, you need to configure your application to send them to an observability platform (like Jaeger or Datadog) using an OTLP Exporter.
The following example demonstrates how to set up a tracer provider that exports data via the OTLP protocol over HTTP. This setup is typically done once when your application starts.
# In your main application file, e.g., main.py\n\nimport os\nfrom fastapi import FastAPI\nfrom opentelemetry import trace\nfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter\nfrom opentelemetry.instrumentation.fastapi import FastAPIInstrumentor\nfrom opentelemetry.instrumentation.logging import LoggingInstrumentor\nfrom opentelemetry.sdk.resources import SERVICE_NAME, SERVICE_VERSION, Resource\nfrom opentelemetry.sdk.trace import TracerProvider\nfrom opentelemetry.sdk.trace.export import BatchSpanProcessor\n\nfrom titiler.core.factory import TilerFactory\n\n# --- OpenTelemetry Configuration ---\n\n# Define a \"Resource\" for your application.\n# This adds metadata to your traces, like the service name and version.\nresource = Resource.create(\n {\n SERVICE_NAME: os.getenv(\"OTEL_SERVICE_NAME\", \"titiler\"),\n SERVICE_VERSION: \"0.1\",\n }\n)\n\n# Create a \"TracerProvider\" with the defined resource.\n# The provider manages the creation of tracers.\nprovider = TracerProvider(resource=resource)\n\n# Configure an \"Exporter\" to send telemetry data.\n# The OTLPSpanExporter sends data to an OTLP-compatible endpoint.\n# By default, it reads the endpoint from the OTEL_EXPORTER_OTLP_ENDPOINT\n# environment variable. The default for HTTP is http://localhost:4318.\nexporter = OTLPSpanExporter()\n\n# Use a \"BatchSpanProcessor\" to send spans in the background.\n# This is the recommended processor for production.\nprocessor = BatchSpanProcessor(exporter)\nprovider.add_span_processor(processor)\n\n# Set the configured provider as the global tracer provider.\ntrace.set_tracer_provider(provider)\n\n# --- FastAPI Application Setup ---\napp = FastAPI(title=\"My TiTiler App\")\n\n# Instrument the FastAPI application.\n# This adds middleware to trace requests, responses, and exceptions,\n# complementing TiTiler's internal endpoint tracing.\nFastAPIInstrumentor.instrument_app(app)\n\n# Add trace/span info to logging messages for trace correlation\nLoggingInstrumentor().instrument(set_logging_format=True)\n\n# Add your TiTiler endpoints with the enable_telemetry flag set to True\ncog = TilerFactory(enable_telemetry=True)\napp.include_router(cog.router)\n"},{"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.AssetsExprParams","title":"AssetsExprParamsdataclass","text":" Bases: ExpressionParams, AssetsParams
Assets and Expression parameters.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass AssetsExprParams(ExpressionParams, AssetsParams):\n \"\"\"Assets and Expression parameters.\"\"\"\n\n asset_as_band: Annotated[\n bool | None,\n Query(\n title=\"Consider asset as a 1 band dataset\",\n description=\"Asset as Band\",\n ),\n ] = None\n"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.AssetsParams","title":"AssetsParams dataclass","text":" Bases: DefaultDependency
Assets parameters.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass AssetsParams(DefaultDependency):\n \"\"\"Assets parameters.\"\"\"\n\n assets: Annotated[\n list[str],\n Query(\n title=\"Asset names\",\n description=\"Asset's names.\",\n openapi_examples={\n \"user-provided\": {\"value\": None},\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 ]\n"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.BidxExprParams","title":"BidxExprParams dataclass","text":" Bases: ExpressionParams, BidxParams
Band Indexes and Expression parameters.
Source code insrc/titiler/core/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 insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass BidxParams(DefaultDependency):\n \"\"\"Band Indexes parameters.\"\"\"\n\n indexes: Annotated[\n list[int] | None,\n Query(\n title=\"Band indexes\",\n alias=\"bidx\",\n description=\"Dataset band indexes\",\n openapi_examples={\n \"user-provided\": {\"value\": None},\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 insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass DatasetParams(DefaultDependency):\n \"\"\"Low level WarpedVRT Optional parameters.\"\"\"\n\n nodata: Annotated[\n Literal[\"nan\", \"inf\", \"-inf\"] | int | float | None,\n Query(\n title=\"Nodata value\",\n description=\"Overwrite internal Nodata value; nan or valid float values only.\",\n ),\n ] = None\n unscale: Annotated[\n bool | None,\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 RIOResampling | None,\n Query(\n alias=\"resampling\",\n description=\"RasterIO resampling algorithm. Defaults to `nearest`.\",\n ),\n ] = None\n reproject_method: Annotated[\n WarpResampling | None,\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 insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass DefaultDependency:\n \"\"\"Dataclass with dict unpacking\"\"\"\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.as_dict","title":"as_dict","text":"as_dict(exclude_none: bool = True) -> dict\n Transform dataclass to dict.
Source code insrc/titiler/core/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.ExpressionParams","title":"ExpressionParams dataclass","text":" Bases: DefaultDependency
Expression parameters.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass ExpressionParams(DefaultDependency):\n \"\"\"Expression parameters.\"\"\"\n\n expression: Annotated[\n str | None,\n Query(\n title=\"Band Math expression\",\n description=\"rio-tiler's band math expression\",\n openapi_examples={\n \"user-provided\": {\"value\": None},\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 insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass HistogramParams(DefaultDependency):\n \"\"\"Numpy Histogram options.\"\"\"\n\n bins: Annotated[\n str | None,\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 \"user-provided\": {\"value\": None},\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 pattern=r\"^\\d+(,\\d+)*$\",\n ),\n ] = None\n\n range: Annotated[\n str | None,\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 openapi_examples={\n \"user-provided\": {\"value\": None},\n \"array\": {\n \"description\": \"Defines custom histogram range (comma `,` delimited values)\",\n \"value\": \"0,1000\",\n },\n },\n pattern=separated_parseable_floats_regex(count=2),\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: RenderingParams
Image Rendering options.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass ImageRenderingParams(RenderingParams):\n \"\"\"Image Rendering options.\"\"\"\n\n add_mask: Annotated[\n bool | None,\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.OGCMapsParams","title":"OGCMapsParams dataclass","text":" Bases: DefaultDependency
OGC Maps options.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass OGCMapsParams(DefaultDependency):\n \"\"\"OGC Maps options.\"\"\"\n\n request: Request\n\n bbox: Annotated[\n str | None,\n BeforeValidator(validate_bbox),\n Query(\n description=\"Bounding box of the rendered map. The bounding box is provided as four or six coordinates.\",\n ),\n ] = None\n\n crs: Annotated[\n str | None,\n BeforeValidator(validate_crs),\n Query(\n description=\"Reproject the output to the given crs.\",\n ),\n ] = None\n\n bbox_crs: Annotated[\n str | None,\n BeforeValidator(validate_crs),\n Query(\n description=\"crs for the specified bbox.\",\n alias=\"bbox-crs\",\n ),\n ] = None\n\n height: Annotated[\n int | None,\n Query(\n description=\"Height of the map in pixels. If omitted and `width` is specified, defaults to the `height` maintaining a 1:1 aspect ratio. If both `width` and `height` are omitted, the server will select default dimensions.\",\n gt=0,\n ),\n ] = None\n\n width: Annotated[\n int | None,\n Query(\n description=\"Width of the map in pixels. If omitted and `height` is specified, defaults to the `width` maintaining a 1:1 aspect ratio. If both `width` and `height` are omitted, the server will select default dimensions.\",\n gt=0,\n ),\n ] = None\n\n f: Annotated[\n ImageType | None,\n Query(description=\"The format of the map response (e.g. png).\"),\n ] = None\n\n max_size: int | None = field(init=False, default=None)\n\n format: ImageType | None = field(init=False, default=ImageType.png)\n\n def __post_init__(self): # noqa: C901\n \"\"\"Parse and validate.\"\"\"\n if self.crs:\n self.crs = CRS.from_user_input(self.crs) # type: ignore\n\n if self.bbox_crs:\n self.bbox_crs = CRS.from_user_input(self.bbox_crs) # type: ignore\n\n if not self.height and not self.width:\n self.max_size = 1024\n\n if self.bbox:\n bounds = list(map(float, self.bbox.split(\",\")))\n if len(bounds) == 6:\n bounds = [bounds[0], bounds[1], bounds[3], bounds[4]]\n\n self.bbox = bounds # type: ignore\n\n if self.f:\n self.format = ImageType[self.f]\n\n else:\n if media := accept_media_type(\n self.request.headers.get(\"accept\", \"\"),\n [MediaType[e] for e in ImageType],\n ):\n self.format = ImageType[media.name]\n"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.PartFeatureParams","title":"PartFeatureParams dataclass","text":" Bases: DefaultDependency
Common parameters for bbox and feature.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass PartFeatureParams(DefaultDependency):\n \"\"\"Common parameters for bbox and feature.\"\"\"\n\n # NOTE: the part sizes dependency can either be a Query or a Path Parameter\n max_size: Annotated[\n int | None, Field(description=\"Maximum image size to read onto.\")\n ] = None\n height: Annotated[int | None, Field(description=\"Force output image height.\")] = (\n None\n )\n width: Annotated[int | None, Field(description=\"Force output image width.\")] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width or 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 insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass PreviewParams(DefaultDependency):\n \"\"\"Common Preview parameters.\"\"\"\n\n # NOTE: sizes dependency can either be a Query or a Path Parameter\n max_size: Annotated[int, Field(description=\"Maximum image size to read onto.\")] = (\n 1024\n )\n height: Annotated[int | None, Field(description=\"Force output image height.\")] = (\n None\n )\n width: Annotated[int | None, Field(description=\"Force output image width.\")] = None\n\n def __post_init__(self):\n \"\"\"Post Init.\"\"\"\n if self.width or self.height:\n self.max_size = None\n"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.RenderingParams","title":"RenderingParams dataclass","text":" Bases: DefaultDependency
Image Rendering options.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass RenderingParams(DefaultDependency):\n \"\"\"Image Rendering options.\"\"\"\n\n rescale: Annotated[\n list[str] | None,\n BeforeValidator(validate_rescale),\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\n color_formula: Annotated[\n str | None,\n BeforeValidator(validate_color_formula),\n Query(\n title=\"Color Formula\",\n description=\"rio-color formula (info: https://github.com/mapbox/rio-color)\",\n ),\n ] = None\n\n def __post_init__(self) -> None:\n \"\"\"Post Init.\"\"\"\n if self.rescale:\n rescale_array = []\n for r in self.rescale:\n parsed = tuple(\n map(\n float,\n r.split(\",\"),\n )\n )\n assert (\n len(parsed) == 2\n ), f\"Invalid rescale values: {self.rescale}, should be of form ['min,max', 'min,max'] or [[min,max], [min, max]]\"\n rescale_array.append(parsed)\n\n self.rescale: RescaleType = rescale_array # type: ignore\n"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.StatisticsParams","title":"StatisticsParams dataclass","text":" Bases: DefaultDependency
Statistics options.
Source code insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass StatisticsParams(DefaultDependency):\n \"\"\"Statistics options.\"\"\"\n\n categorical: Annotated[\n bool | None,\n Query(\n description=\"Return statistics for categorical dataset. Defaults to `False`\"\n ),\n ] = None\n categories: Annotated[\n list[float | int] | None,\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 list[int] | None,\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 insrc/titiler/core/titiler/core/dependencies.py @dataclass\nclass TileParams(DefaultDependency):\n \"\"\"Tile options.\"\"\"\n\n buffer: Annotated[\n float | None,\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 int | None,\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 float | None,\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) -> float | None\n Tile buffer Parameter.
Source code insrc/titiler/core/titiler/core/dependencies.py def BufferParams(\n buffer: Annotated[\n float | None,\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) -> float | None:\n \"\"\"Tile buffer Parameter.\"\"\"\n return buffer\n"},{"location":"api/titiler/core/dependencies/#titiler.core.dependencies.CRSParams","title":"CRSParams","text":"CRSParams(\n crs: Annotated[\n str | None, BeforeValidator(validate_crs), Query(description=\"Coordinate Reference System.\")\n ] = None,\n) -> CRS | None\n Coordinate Reference System Coordinates Param.
Source code insrc/titiler/core/titiler/core/dependencies.py def CRSParams(\n crs: Annotated[\n str | None,\n BeforeValidator(validate_crs),\n Query(\n description=\"Coordinate Reference System.\",\n ),\n ] = None,\n) -> CRS | None:\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.CoordCRSParams","title":"CoordCRSParams","text":"CoordCRSParams(\n crs: Annotated[\n str | None,\n BeforeValidator(validate_crs),\n Query(\n alias=coord_crs,\n description=\"Coordinate Reference System of the input coords. Default to `epsg:4326`.\",\n ),\n ] = None,\n) -> CRS | None\n Coordinate Reference System Coordinates Param.
Source code insrc/titiler/core/titiler/core/dependencies.py def CoordCRSParams(\n crs: Annotated[\n str | None,\n BeforeValidator(validate_crs),\n Query(\n alias=\"coord_crs\",\n description=\"Coordinate Reference System of the input coords. Default to `epsg:4326`.\",\n ),\n ] = None,\n) -> CRS | None:\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 insrc/titiler/core/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 str | None,\n BeforeValidator(validate_crs),\n Query(alias=dst_crs, description=\"Output Coordinate Reference System.\"),\n ] = None,\n) -> CRS | None\n Coordinate Reference System Coordinates Param.
Source code insrc/titiler/core/titiler/core/dependencies.py def DstCRSParams(\n crs: Annotated[\n str | None,\n BeforeValidator(validate_crs),\n Query(\n alias=\"dst_crs\",\n description=\"Output Coordinate Reference System.\",\n ),\n ] = None,\n) -> CRS | None:\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.create_colormap_dependency","title":"create_colormap_dependency","text":"create_colormap_dependency(cmap: ColorMaps) -> Callable\n Create Colormap Dependency.
Source code insrc/titiler/core/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 str | None, 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/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","text":" Bases: BaseFactory
Algorithm endpoints Factory.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.AlgorithmFactory._get_algo_metadata","title":"_get_algo_metadata","text":"_get_algo_metadata(algorithm: type[BaseAlgorithm]) -> AlgorithmMetadata\n Algorithm Metadata
"},{"location":"api/titiler/core/factory/#titiler.core.factory.AlgorithmFactory.register_routes","title":"register_routes","text":"register_routes()\n Register Algorithm routes.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseFactory","title":"BaseFactory","text":"Base Factory.
Abstract Base Class which defines most inputs used by dynamic tiler.
Attributes:
router (APIRouter) \u2013 Application router to register endpoints to.
router_prefix (str) \u2013 prefix where the router will be mounted in the application.
route_dependencies (list) \u2013 Additional routes dependencies to add after routes creations.
__attrs_post_init__()\n Post Init: register route and configure specific options.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseFactory.add_route_dependencies","title":"add_route_dependencies","text":"add_route_dependencies(*, scopes: list[EndpointScope], dependencies=list[Depends])\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.BaseFactory.add_telemetry","title":"add_telemetry","text":"add_telemetry()\n Applies the factory_trace decorator to all registered API routes.
This method iterates through the router's routes and wraps the endpoint of each APIRoute to ensure consistent OpenTelemetry tracing.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseFactory.register_routes","title":"register_routesabstractmethod","text":"register_routes()\n Register Routes.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.BaseFactory.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","text":" Bases: BaseFactory
Colormap endpoints Factory.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.ColorMapFactory._image_from_colormap","title":"_image_from_colormap","text":"_image_from_colormap(\n cmap,\n orientation: Literal[\"vertical\", \"horizontal\"] | None = None,\n width: int | None = None,\n height: int | None = None,\n) -> ImageData\n Create an image from a colormap.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.ColorMapFactory.register_routes","title":"register_routes","text":"register_routes()\n Register ColorMap routes.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.FactoryExtension","title":"FactoryExtension","text":"Factory Extension.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.FactoryExtension.register","title":"registerabstractmethod","text":"register(factory: BaseFactory)\n Register extension to the factory.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBaseTilerFactory","title":"MultiBaseTilerFactory","text":" Bases: TilerFactory
Custom Tiler Factory for MultiBaseReader classes.
NoteTo be able to use the rio_tiler.io.MultiBaseReader we need to be able to pass a assets argument to most of its methods. By using the AssetsBidxExprParams for the layer_dependency, the .tile(), .point(), .preview() and the .part() methods will receive assets, expression or indexes arguments.
The rio_tiler.io.MultiBaseReader .info() and .metadata() have assets as a requirement arguments (github.com/cogeotiff/rio-tiler/blob/main/rio_tiler/io/base.py#L365). This means we have to update the /info and /metadata endpoints in order to add the assets dependency.
info()\n Register /info endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.MultiBaseTilerFactory.statistics","title":"statistics","text":"statistics()\n Register /statistics endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TMSFactory","title":"TMSFactory","text":" Bases: BaseFactory
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.TilerFactory","title":"TilerFactory","text":" Bases: BaseFactory
Tiler Factory.
Attributes:
reader (BaseReader) \u2013 A rio-tiler reader. Defaults to rio_tiler.io.Reader.
reader_dependency (DefaultDependency) \u2013 Endpoint dependency defining BaseReader options.
path_dependency (Callable) \u2013 Endpoint dependency defining path to pass to the reader init.
layer_dependency (DefaultDependency) \u2013 Endpoint dependency defining dataset indexes/bands/assets options.
dataset_dependency (DefaultDependency) \u2013 Endpoint dependency defining dataset overwriting options (e.g nodata).
tile_dependency (DefaultDependency) \u2013 Endpoint dependency defining tile options (e.g buffer, padding).
stats_dependency (DefaultDependency) \u2013 Endpoint dependency defining options for rio-tiler's statistics method.
histogram_dependency (DefaultDependency) \u2013 Endpoint dependency defining options for numpy's histogram method.
img_preview_dependency (DefaultDependency) \u2013 Endpoint dependency defining options for rio-tiler's preview method.
img_part_dependency (DefaultDependency) \u2013 Endpoint dependency defining options for rio-tiler's part/feature methods.
process_dependency (DefaultDependency) \u2013 Endpoint dependency defining image post-processing options (e.g rescaling, color-formula).
rescale_dependency (Callable[..., Optional[RescaleType]]) \u2013 color_formula_dependency (Callable[..., Optional[str]]) \u2013 colormap_dependency (Callable) \u2013 Endpoint dependency defining ColorMap options (e.g colormap_name).
render_dependency (DefaultDependency) \u2013 Endpoint dependency defining image rendering options (e.g add_mask).
environment_dependency (Callable) \u2013 Endpoint dependency to define GDAL environment at runtime.
supported_tms (TileMatrixSets) \u2013 TileMatrixSets object holding the supported TileMatrixSets.
templates (Jinja2Templates) \u2013 Jinja2 templates.
add_preview (bool) \u2013 add /preview endpoints. Defaults to True.
add_part (bool) \u2013 add /bbox and /feature endpoints. Defaults to True.
add_viewer (bool) \u2013 add /map.html endpoints. Defaults to True.
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.html endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.ogc_maps","title":"ogc_maps","text":"ogc_maps()\n Register OGC Maps /map` endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.part","title":"part","text":"part()\n Register /bbox and /feature endpoints.
point()\n Register /point endpoints.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.preview","title":"preview","text":"preview()\n Register /preview endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.register_routes","title":"register_routes","text":"register_routes()\n This Method register routes to the router.
Because we wrap the endpoints in a class we cannot define the routes as methods (because of the self argument). The HACK is to define routes inside the class method and register them after the class initialization.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.statistics","title":"statistics","text":"statistics()\n add statistics endpoints.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.tile","title":"tile","text":"tile()\n Register /tiles endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.tilejson","title":"tilejson","text":"tilejson()\n Register /tilejson.json endpoint.
"},{"location":"api/titiler/core/factory/#titiler.core.factory.TilerFactory.tilesets","title":"tilesets","text":"tilesets()\n Register OGC tilesets endpoints.
"},{"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":"CacheControlMiddlewaredataclass","text":"MiddleWare to add CacheControl in response headers.
Parameters:
app (ASGIApp) \u2013 starlette/FastAPI application.
cachecontrol (str, default: None ) \u2013 Cache-Control string to add to the response.
exclude_path (set, default: set() ) \u2013 Set of regex expression to use to filter the path.
async","text":"__call__(scope: Scope, receive: Receive, send: Send)\n Handle call.
"},{"location":"api/titiler/core/middleware/#titiler.core.middleware.LoggerMiddleware","title":"LoggerMiddlewaredataclass","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.LowerCaseQueryStringMiddleware","title":"LowerCaseQueryStringMiddlewaredataclass","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.TotalTimeMiddleware","title":"TotalTimeMiddlewaredataclass","text":"MiddleWare to add Total process time in response headers.
Parameters:
app (ASGIApp) \u2013 starlette/FastAPI application.
async","text":"__call__(scope: Scope, receive: Receive, send: Send)\n Handle call.
"},{"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[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/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.BoundingBox","title":"BoundingBox","text":" Bases: BaseModel
BoundingBox model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/2DBoundingBox.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.Conformance","title":"Conformance","text":" Bases: BaseModel
Conformance model.
Ref: schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/confClasses.yaml
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.GeospatialData","title":"GeospatialData","text":" Bases: BaseModel
Geospatial model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/geospatialData.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.Landing","title":"Landing","text":" Bases: BaseModel
Landing page model.
Ref: schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/landingPage.yaml
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.Properties","title":"Properties","text":" Bases: BaseModel
Properties model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/propertiesSchema.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.PropertiesSchema","title":"PropertiesSchema","text":" Bases: BaseModel
PropertiesSchema model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/propertiesSchema.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.Style","title":"Style","text":" Bases: BaseModel
Style model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/style.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.TileMatrixLimits","title":"TileMatrixLimits","text":" Bases: BaseModel
The limits for an individual tile matrix of a TileSet's TileMatrixSet, as defined in the OGC 2D TileMatrixSet and TileSet Metadata Standard
Based on github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/tileMatrixLimits.yaml
"},{"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/OGC/#titiler.core.models.OGC.TilePoint","title":"TilePoint","text":" Bases: BaseModel
TilePoint model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/tilePoint.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.TileSet","title":"TileSet","text":" Bases: BaseModel
TileSet model.
Based on github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/tms/tileSet.yaml
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.TileSetList","title":"TileSetList","text":" Bases: BaseModel
TileSetList model.
Based on docs.ogc.org/is/20-057/20-057.html#toc34
"},{"location":"api/titiler/core/models/OGC/#titiler.core.models.OGC.TimeStamp","title":"TimeStamp","text":" Bases: RootModel
TimeStamp model.
Ref: github.com/opengeospatial/ogcapi-tiles/blob/master/openapi/schemas/common-geodata/timeStamp.yaml
Code generated using koxudaxi/datamodel-code-generator
"},{"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.LayerJSON","title":"LayerJSON","text":" Bases: BaseModel
github.com/mapbox/tilejson-spec/tree/master/3.0.0#33-vector_layers
"},{"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/3.0.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.ColorMapList","title":"ColorMapList","text":" Bases: BaseModel
Model for colormap list.
"},{"location":"api/titiler/core/models/responses/#titiler.core.models.responses.ColorMapRef","title":"ColorMapRef","text":" Bases: BaseModel
ColorMapRef model.
"},{"location":"api/titiler/core/models/responses/#titiler.core.models.responses.Point","title":"Point","text":" Bases: BaseModel
Point model.
response model for /point endpoints
Bases: BaseModel
Statistics model in geojson response.
"},{"location":"api/titiler/core/resources/enums/","title":"enums","text":""},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums","title":"titiler.core.resources.enums","text":"Titiler.core Enums.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageDriver","title":"ImageDriver","text":" Bases: str, Enum
Supported output GDAL drivers.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType","title":"ImageType","text":" Bases: str, Enum
Available Output image type.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType.driver","title":"driver","text":"driver()\n Return rio-tiler image default profile.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType.mediatype","title":"mediatype","text":"mediatype()\n Return image media type.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.ImageType.profile","title":"profile","text":"profile()\n Return rio-tiler image default profile.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.MediaType","title":"MediaType","text":" Bases: str, Enum
Responses Media types formerly known as MIME types.
"},{"location":"api/titiler/core/resources/enums/#titiler.core.resources.enums.OptionalHeader","title":"OptionalHeader","text":" Bases: str, Enum
Optional Header to add in responses.
"},{"location":"api/titiler/core/resources/responses/","title":"responses","text":""},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses","title":"titiler.core.resources.responses","text":"Common response models.
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.GeoJSONResponse","title":"GeoJSONResponse","text":" Bases: JSONResponse
GeoJSON Response
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.JSONResponse","title":"JSONResponse","text":" Bases: JSONResponse
Custom JSON Response.
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.JSONResponse.render","title":"render","text":"render(content: Any) -> bytes\n Render JSON.
Same defaults as starlette.responses.JSONResponse.render but allow NaN to be replaced by null using simplejson
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.NumpyEncoder","title":"NumpyEncoder","text":" Bases: JSONEncoder
Custom JSON Encoder.
"},{"location":"api/titiler/core/resources/responses/#titiler.core.resources.responses.NumpyEncoder.default","title":"default","text":"default(obj)\n Catch numpy types and convert them.
"},{"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","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: TilerFactory)\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","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: TilerFactory)\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","text":" Bases: FactoryExtension
Add /viewer endpoint to the TilerFactory.
"},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer.cogViewerExtension.register","title":"register","text":"register(factory: TilerFactory)\n Register endpoint to the tiler factory.
"},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer.stacViewerExtension","title":"stacViewerExtension","text":" Bases: FactoryExtension
Add /viewer endpoint to the TilerFactory.
"},{"location":"api/titiler/extensions/viewer/#titiler.extensions.viewer.stacViewerExtension.register","title":"register","text":"register(factory: TilerFactory)\n Register endpoint to the tiler factory.
"},{"location":"api/titiler/extensions/wms/","title":"wms","text":""},{"location":"api/titiler/extensions/wms/#titiler.extensions.wms","title":"titiler.extensions.wms","text":"wms Extension.
"},{"location":"api/titiler/extensions/wms/#titiler.extensions.wms.OverlayMethod","title":"OverlayMethoddataclass","text":" Bases: MosaicMethodBase
Overlay data on top.
"},{"location":"api/titiler/extensions/wms/#titiler.extensions.wms.OverlayMethod.feed","title":"feed","text":"feed(array: MaskedArray)\n Add data to the mosaic array.
"},{"location":"api/titiler/extensions/wms/#titiler.extensions.wms.WMSMediaType","title":"WMSMediaType","text":" Bases: str, Enum
Responses Media types for WMS
"},{"location":"api/titiler/extensions/wms/#titiler.extensions.wms.wmsExtension","title":"wmsExtension","text":" Bases: FactoryExtension
Add /wms endpoint to a TilerFactory.
"},{"location":"api/titiler/extensions/wms/#titiler.extensions.wms.wmsExtension.register","title":"register","text":"register(factory: TilerFactory)\n Register endpoint to the tiler factory.
"},{"location":"api/titiler/extensions/wmts/","title":"wmts","text":""},{"location":"api/titiler/extensions/wmts/#titiler.extensions.wmts","title":"titiler.extensions.wmts","text":"TiTiler WMTS Extension.
"},{"location":"api/titiler/extensions/wmts/#titiler.extensions.wmts.wmtsExtension","title":"wmtsExtension","text":" Bases: FactoryExtension
RESTful WMTS service Extension for TilerFactory.
"},{"location":"api/titiler/extensions/wmts/#titiler.extensions.wmts.wmtsExtension.register","title":"register","text":"register(factory: TilerFactory)\n Register extension's endpoints.
"},{"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","text":" Bases: BaseFactory
MosaicTiler Factory.
"},{"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.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.html endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.ogc_maps","title":"ogc_maps","text":"ogc_maps()\n Register OGC Maps /map` endpoint.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.part","title":"part","text":"part()\n Register /bbox and /feature 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.register_routes","title":"register_routes","text":"register_routes()\n This Method register routes to the router.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.MosaicTilerFactory.statistics","title":"statistics","text":"statistics()\n Register /statistics endpoint.
"},{"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.tilesets","title":"tilesets","text":"tilesets()\n Register OGC tilesets endpoints.
"},{"location":"api/titiler/mosaic/factory/#titiler.mosaic.factory.DatasetPathParams","title":"DatasetPathParams","text":"DatasetPathParams(url: Annotated[str, Query(description='Mosaic URL')]) -> str\n Create dataset path from args
"},{"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/mosaicjson/","title":"mosaicjson","text":""},{"location":"api/titiler/mosaic/mosaicjson/#titiler.mosaic.extensions.mosaicjson","title":"titiler.mosaic.extensions.mosaicjson","text":"titiler.mosaic extensions.
"},{"location":"api/titiler/mosaic/mosaicjson/#titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension","title":"MosaicJSONExtension","text":" Bases: FactoryExtension
Add MosaicJSON specific endpoints
"},{"location":"api/titiler/mosaic/mosaicjson/#titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension.register","title":"register","text":"register(factory: MosaicTilerFactory)\n Register endpoint to the tiler factory.
"},{"location":"api/titiler/mosaic/wmts/","title":"wmts","text":""},{"location":"api/titiler/mosaic/wmts/#titiler.mosaic.extensions.wmts","title":"titiler.mosaic.extensions.wmts","text":"titiler.mosaic wmts extensions.
"},{"location":"api/titiler/mosaic/wmts/#titiler.mosaic.extensions.wmts.wmtsExtension","title":"wmtsExtension","text":" Bases: FactoryExtension
RESTful WMTS service Extension for MosaicTilerFactory.
"},{"location":"api/titiler/mosaic/wmts/#titiler.mosaic.extensions.wmts.wmtsExtension.register","title":"register","text":"register(factory: MosaicTilerFactory)\n Register endpoint to the tiler factory.
"},{"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.AssetPoint","title":"AssetPoint","text":" Bases: BaseModel
Model for Point value per asset
"},{"location":"api/titiler/mosaic/models/responses/#titiler.mosaic.models.responses.Point","title":"Point","text":" Bases: BaseModel
Point model.
response model for /point endpoints
titiler.xarray dependencies.
"},{"location":"api/titiler/xarray/dependencies/#titiler.xarray.dependencies.CompatXarrayParams","title":"CompatXarrayParamsdataclass","text":" Bases: XarrayIOParams
Custom XarrayParams endpoints.
This Dependency aims to be used in a tiler where both GDAL/Xarray dataset would be supported. By default variable won't be required but when using an Xarray dataset, it would fail without the variable query-parameter set.
dataclass","text":" Bases: DefaultDependency
Low level WarpedVRT Optional parameters.
"},{"location":"api/titiler/xarray/dependencies/#titiler.xarray.dependencies.PartFeatureParams","title":"PartFeatureParamsdataclass","text":" Bases: DefaultDependency
Common parameters for bbox and feature.
"},{"location":"api/titiler/xarray/dependencies/#titiler.xarray.dependencies.PreviewParams","title":"PreviewParamsdataclass","text":" Bases: DefaultDependency
Common Preview parameters.
"},{"location":"api/titiler/xarray/dependencies/#titiler.xarray.dependencies.XarrayDsParams","title":"XarrayDsParamsdataclass","text":" Bases: DefaultDependency
Xarray Dataset Options.
"},{"location":"api/titiler/xarray/dependencies/#titiler.xarray.dependencies.XarrayIOParams","title":"XarrayIOParamsdataclass","text":" Bases: DefaultDependency
Dataset IO Options.
"},{"location":"api/titiler/xarray/dependencies/#titiler.xarray.dependencies.XarrayParams","title":"XarrayParamsdataclass","text":" Bases: XarrayIOParams, XarrayDsParams
Xarray Reader dependency.
"},{"location":"api/titiler/xarray/extensions/","title":"extensions","text":""},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions","title":"titiler.xarray.extensions","text":"titiler.xarray Extensions.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.DatasetMetadataExtension","title":"DatasetMetadataExtension","text":" Bases: FactoryExtension
Add dataset metadata endpoints to a Xarray TilerFactory.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.DatasetMetadataExtension.register","title":"register","text":"register(factory: TilerFactory)\n Register endpoint to the tiler factory.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.ValidateExtension","title":"ValidateExtension","text":" Bases: FactoryExtension
Add /validate endpoints to a Xarray TilerFactory.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.ValidateExtension.register","title":"register","text":"register(factory: TilerFactory)\n Register endpoint to the tiler factory.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.ValidationInfo","title":"ValidationInfo","text":" Bases: TypedDict
Variable Validation model.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.VariablesExtension","title":"VariablesExtension","text":" Bases: FactoryExtension
Add /variables endpoint to a Xarray TilerFactory.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.VariablesExtension.__attrs_post_init__","title":"__attrs_post_init__","text":"__attrs_post_init__()\n raise deprecation warning.
"},{"location":"api/titiler/xarray/extensions/#titiler.xarray.extensions.VariablesExtension.register","title":"register","text":"register(factory: TilerFactory)\n Register endpoint to the tiler factory.
"},{"location":"api/titiler/xarray/factory/","title":"factory","text":""},{"location":"api/titiler/xarray/factory/#titiler.xarray.factory","title":"titiler.xarray.factory","text":"TiTiler.xarray factory.
"},{"location":"api/titiler/xarray/factory/#titiler.xarray.factory.TilerFactory","title":"TilerFactory","text":" Bases: TilerFactory
Xarray Tiler Factory.
"},{"location":"api/titiler/xarray/factory/#titiler.xarray.factory.TilerFactory.__attrs_post_init__","title":"__attrs_post_init__","text":"__attrs_post_init__()\n Raise warning if preview is enabled.
"},{"location":"api/titiler/xarray/factory/#titiler.xarray.factory.TilerFactory.info","title":"info","text":"info()\n Register /info endpoint.
"},{"location":"api/titiler/xarray/factory/#titiler.xarray.factory.TilerFactory.statistics","title":"statistics","text":"statistics()\n add statistics endpoints.
"},{"location":"api/titiler/xarray/io/","title":"io","text":""},{"location":"api/titiler/xarray/io/#titiler.xarray.io","title":"titiler.xarray.io","text":"titiler.xarray.io
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io.FsReader","title":"FsReader","text":" Bases: Reader
Reader with fs_open_dataset opener
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io.Reader","title":"Reader","text":" Bases: XarrayReader
Reader: Open Zarr file and access DataArray.
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io.Reader.__attrs_post_init__","title":"__attrs_post_init__","text":"__attrs_post_init__()\n Set bounds and CRS.
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io.Reader.__exit__","title":"__exit__","text":"__exit__(exc_type, exc_value, traceback)\n Support using with Context Managers.
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io.Reader.close","title":"close","text":"close()\n Close xarray dataset.
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io.selector","title":"selector","text":" Bases: TypedDict
STAC Item.
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io._arrange_dims","title":"_arrange_dims","text":"_arrange_dims(da: DataArray) -> DataArray\n Arrange coordinates and time dimensions.
An rioxarray.exceptions.InvalidDimensionOrder error is raised if the coordinates are not in the correct order time, y, and x. See: corteva/rioxarray?674
We conform to using x and y as the spatial dimension names..
"},{"location":"api/titiler/xarray/io/#titiler.xarray.io._parse_dsl","title":"_parse_dsl","text":"_parse_dsl(sel: list[str] | None) -> list[selector]\n Parse sel DSL into dictionary.
Parameters:
sel (list of str) \u2013 List of Xarray Indexes.
Returns:
list ( list[selector] ) \u2013 list of dimension/values/method.
fs_open_dataset(\n src_path: str,\n group: str | None = None,\n decode_times: bool = True,\n decode_coords: str = \"all\",\n **kwargs\n) -> Dataset\n Open Xarray dataset with fsspec.
Parameters:
src_path (str) \u2013 dataset path.
group ((Optional, str), default: None ) \u2013 path to the netCDF/Zarr group in the given file to open given as a str.
decode_times (bool, default: True ) \u2013 If True, decode times encoded in the standard NetCDF datetime format into datetime objects. Otherwise, leave them encoded as numbers.
Returns:
Dataset \u2013 xarray.Dataset
get_variable(ds: Dataset, variable: str, sel: list[str] | None = None) -> DataArray\n Get Xarray variable as DataArray.
Parameters:
ds (Dataset) \u2013 Xarray Dataset.
variable (str) \u2013 Variable to extract from the Dataset.
sel (list of str, default: None ) \u2013 List of Xarray Indexes.
Returns:
DataArray \u2013 xarray.DataArray: 2D or 3D DataArray.
cached","text":"open_zarr(\n src_path: str,\n group: str | None = None,\n decode_times: bool = True,\n decode_coords: str = \"all\",\n infer_region: bool = True,\n **kwargs: Any\n) -> Dataset\n Open Xarray dataset with fsspec.
Parameters:
src_path (str) \u2013 dataset path.
group ((Optional, str), default: None ) \u2013 path to the netCDF/Zarr group in the given file to open given as a str.
decode_times (bool, default: True ) \u2013 If True, decode times encoded in the standard NetCDF datetime format into datetime objects. Otherwise, leave them encoded as numbers.
Returns:
Dataset \u2013 xarray.Dataset
titiler.xarray application.
"},{"location":"api/titiler/xarray/main/#titiler.xarray.main.ApiSettings","title":"ApiSettings","text":" Bases: BaseSettings
FASTAPI application settings.
"},{"location":"api/titiler/xarray/main/#titiler.xarray.main.ApiSettings.parse_cors_allow_methods","title":"parse_cors_allow_methods","text":"parse_cors_allow_methods(v)\n Parse CORS allowed methods.
"},{"location":"api/titiler/xarray/main/#titiler.xarray.main.ApiSettings.parse_cors_origin","title":"parse_cors_origin","text":"parse_cors_origin(v)\n Parse CORS origins.
"},{"location":"api/titiler/xarray/main/#titiler.xarray.main.application_health_check","title":"application_health_check","text":"application_health_check()\n Health check.
"},{"location":"api/titiler/xarray/main/#titiler.xarray.main.conformance","title":"conformance","text":"conformance(\n request: Request,\n f: Annotated[\n Literal[\"html\", \"json\"] | None,\n Query(\n description=\"Response MediaType. Defaults to endpoint's default or value defined in `accept` header.\"\n ),\n ] = None,\n)\n Conformance classes.
Called with GET /conformance.
Returns:
Conformance classes which the server conforms to.
landing(\n request: Request,\n f: Annotated[\n Literal[\"html\", \"json\"] | None,\n Query(\n description=\"Response MediaType. Defaults to endpoint's default or value defined in `accept` header.\"\n ),\n ] = None,\n)\n TiTiler landing page.
"},{"location":"api/titiler/xarray/main/#titiler.xarray.main.validate_access_token","title":"validate_access_token","text":"validate_access_token(access_token: str = Security(api_key_query))\n Validates API key access token, set as the api_settings.global_access_token value. Returns True if no access token is required, or if the access token is valid. Raises an HTTPException (401) if the access token is required but invalid/missing.
TiTiler is built on top of FastAPI, a modern, fast, Python web framework for building APIs. We can make our FastAPI application work as an Azure Function by wrapping it within the Azure Function Python worker.
If you are not familiar with Azure functions, we recommend checking docs.microsoft.com/en-us/azure/azure-functions/ first.
Minimal TiTiler Azure function code:
import azure.functions as func\nfrom titiler.application.main import cog, mosaic, stac, tms\nfrom fastapi import FastAPI\n\n\napp = FastAPI()\napp.include_router(cog.router, prefix=\"/cog\", tags=[\"Cloud Optimized GeoTIFF\"])\napp.include_router(\n stac.router, prefix=\"/stac\", tags=[\"SpatioTemporal Asset Catalog\"]\n)\napp.include_router(mosaic.router, prefix=\"/mosaicjson\", tags=[\"MosaicJSON\"])\napp.include_router(tms.router, tags=[\"TileMatrixSets\"])\n\n\nasync def main(\n req: func.HttpRequest, context: func.Context,\n) -> func.HttpResponse:\n return await func.AsgiMiddleware(app).handle_async(req, context)\n"},{"location":"deployment/azure/#requirements","title":"Requirements","text":"See: docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-python?tabs=azure-cli%2Cbash%2Cbrowser#create-supporting-azure-resources-for-your-function
$ git clone https://github.com/developmentseed/titiler.git\n$ cd titiler/deployment/azure\n\n$ az login\n$ az group create --name AzureFunctionsTiTiler-rg --location eastus\n$ az storage account create --name {your-new-storage-name} --sku Standard_LRS -g AzureFunctionsTiTiler-rg\n$ az functionapp create --consumption-plan-location eastus --runtime python --runtime-version 3.9 --functions-version 4 --name {your-new-function-name} --os-type linux -g AzureFunctionsTiTiler-rg -s {your-new-storage-name}\n$ func azure functionapp publish titiler --python\n or
use VScode: docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-python#publish-the-project-to-azure
"},{"location":"deployment/azure/#docs","title":"Docs","text":"Try locally:
minikube start\nkubectl config use-context minikube\nhelm init --wait\n\n# in the k8s directory\nhelm install -f titiler/Chart.yaml titiler\n For more info about K8S cluster and node configuration, please see: developmentseed/titiler#212
"},{"location":"deployment/aws/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 LAMBDA\n# The following settings only apply to AWS Lambda deployment\ntimeout: int = 10\nmemory: int = 1536\n# more about lambda config: https://www.sentiatechblog.com/aws-re-invent-2020-day-3-optimizing-lambda-cost-with-multi-threading\n\n# The maximum of concurrent executions you want to reserve for the function.\n# Default: - No specific limit - account limit.\nmax_concurrent: Optional[int]\n"},{"location":"deployment/aws/lambda/","title":"AWS Lambda","text":"TiTiler is built on top of FastAPI, a modern, fast, Python web framework for building APIs. It doesn't work natively with AWS Lambda and API Gateway because FastAPI understands HTTP requests, not API Gateway's event and context JSON objects. However, we can make our FastAPI application work on Lambda by wrapping it with the awesome mangum module, which translates API Gateway events into HTTP requests.
from mangum import Mangum\nfrom titiler.main import app\n\nhandler = Mangum(app, enable_lifespan=False)\n"},{"location":"deployment/aws/lambda/#deploy","title":"Deploy","text":"The Lambda stack is also deployed by the AWS CDK utility. Under the hood, CDK will create the deployment package required for AWS Lambda, upload it to AWS, and handle the creation of the Lambda and API Gateway resources.
Install CDK and connect to your AWS account. This step is only necessary once per AWS account.
# Download titiler repo\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler/deployment/aws\n\n# Install NodeJS dependencies\nnpm install -g aws-cdk@2.159.1\n\nuv run cdk -- bootstrap # Deploys the CDK toolkit stack into an AWS environment\n\n# or in specific region\nuv run cdk -- bootstrap aws://${AWS_ACCOUNT_ID}/eu-central-1\n Pre-Generate CFN template
uv run cdk -- synth # Synthesizes and prints the CloudFormation template for this stack\n Update settings (see intro.md)
export TITILER_STACK_NAME=\"mytiler\"\nexport TITILER_STACK_STAGE=\"dev\"\nexport TITILER_STACK_MEMORY=512\n Available settings for AWS Lambda:
timeout: int = 10\nmemory: int = 1536\n\n# The maximum of concurrent executions you want to reserve for the function.\n# Default: - No specific limit - account limit.\nmax_concurrent: Optional[int]\n Deploy
uv run cdk -- deploy mytiler-lambda-dev # Deploys the stack(s) titiler-lambda-dev in cdk/app.py\n\n# Deploy in specific region\nAWS_DEFAULT_REGION=eu-central-1 AWS_REGION=eu-central-1 uv run cdk -- deploy mytiler-lambda-dev\n An AWS SAM (Serverless Application Model) application is publicly available over serverlessrepo.aws.amazon.com/applications/us-east-1/552819999234/TiTiler
This enable almost a one click deployment solution
The SAM template is built on top of developmentseed/titiler-lambda-layer.
"},{"location":"endpoints/algorithms/","title":"/algorithms","text":"In addition to the /cog, /stac and /mosaicjson endpoints, the titiler.application package FastAPI application commes with additional metadata endpoints.
GET /algorithms JSON retrieve the list of available Algorithms GET /algorithms/{algorithmId} JSON retrieve the metadata of the specified algorithm."},{"location":"endpoints/algorithms/#description","title":"Description","text":""},{"location":"endpoints/algorithms/#list-algorithm","title":"List Algorithm","text":":endpoint:/algorithm - Get the list of supported TileMatrixSet
$ curl https://myendpoint/algorithms | jq\n\n{\n \"hillshade\": {\n \"title\": \"Hillshade\",\n \"description\": \"Create hillshade from DEM dataset.\",\n \"inputs\": {\n \"nbands\": 1\n },\n \"outputs\": {\n \"nbands\": 1,\n \"dtype\": \"uint8\",\n \"min\": null,\n \"max\": null\n },\n \"parameters\": {\n \"azimuth\": {\n \"default\": 90,\n \"maximum\": 360,\n \"minimum\": 0,\n \"title\": \"Azimuth\",\n \"type\": \"integer\"\n },\n \"angle_altitude\": {\n \"default\": 90.0,\n \"maximum\": 90.0,\n \"minimum\": -90.0,\n \"title\": \"Angle Altitude\",\n \"type\": \"number\"\n },\n \"buffer\": {\n \"default\": 3,\n \"maximum\": 99,\n \"minimum\": 0,\n \"title\": \"Buffer\",\n \"type\": \"integer\"\n }\n }\n },\n ...\n}\n"},{"location":"endpoints/algorithms/#get-algorithm-info","title":"Get Algorithm info","text":":endpoint:/algorithms/{algorithmId} - Get the algorithm metadata
$ curl http://127.0.0.1:8000/algorithms/contours | jq\n\n{\n \"title\": \"Contours\",\n \"description\": \"Create contours from DEM dataset.\",\n \"inputs\": {\n \"nbands\": 1\n },\n \"outputs\": {\n \"nbands\": 3,\n \"dtype\": \"uint8\",\n \"min\": null,\n \"max\": null\n },\n \"parameters\": {\n \"increment\": {\n \"default\": 35,\n \"maximum\": 999,\n \"minimum\": 0,\n \"title\": \"Increment\",\n \"type\": \"integer\"\n },\n \"thickness\": {\n \"default\": 1,\n \"maximum\": 10,\n \"minimum\": 0,\n \"title\": \"Thickness\",\n \"type\": \"integer\"\n },\n \"minz\": {\n \"default\": -12000,\n \"maximum\": 99999,\n \"minimum\": -99999,\n \"title\": \"Minz\",\n \"type\": \"integer\"\n },\n \"maxz\": {\n \"default\": 8000,\n \"maximum\": 99999,\n \"minimum\": -99999,\n \"title\": \"Maxz\",\n \"type\": \"integer\"\n }\n }\n}\n"},{"location":"endpoints/cog/","title":"/cog","text":"The titiler.application package comes with a full FastAPI application with COG, STAC and MosaicJSON supports.
The /cog routes are based on titiler.core.factory.TilerFactory but with cogValidateExtension and cogViewerExtension extensions.
GET /cog/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 JSON List of OGC Tilesets available GET /cog/tiles/{tileMatrixSetId} JSON OGC Tileset metadata GET /cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}] image/bin create a web map tile image from a dataset GET /cog/{tileMatrixSetId}/map.html HTML simple map viewer GET /cog/{tileMatrixSetId}/tilejson.json JSON return a Mapbox TileJSON document GET /cog/point/{lon},{lat} JSON return pixel values 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/preview[/{width}x{height}][.{format}] image/bin create a preview image from a dataset GET /cog/map image/bin create map image from a dataset 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) GET /cog/WMTSCapabilities.xml XML return OGC WMTS Get Capabilities (from titiler.extensions.wmts.wmtsExtension)"},{"location":"endpoints/cog/#description","title":"Description","text":""},{"location":"endpoints/cog/#tiles","title":"Tiles","text":":endpoint:/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}]
PathParams:
WebMercatorQuad)QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).0.5. Output tilesize will be expanded to tilesize + 2 * buffer (e.g 0.5 = 257x257, 1.0 = 258x258).0.hillshade).Example:
https://myendpoint/cog/tiles/WebMercatorQuad/1/2/3?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/tiles/WebMercatorQuad/1/2/3.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2https://myendpoint/cog/tiles/WorldCRS84Quad/1/2/3@2x.png?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/tiles/WorldCRS84Quad/1/2/3?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie:endpoint:/cog/preview
:endpoint:/cog/preview.{format}
:endpoint:/cog/preview/{width}x{height}.{format}
PathParams:
QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).max_size (int): Max image size, default is 1024.
nodata (str, int, float): Overwrite internal Nodata value.
nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).Important
if height or width is provided max_size will be ignored.
Example:
https://myendpoint/cog/preview?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/preview.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2https://myendpoint/cog/preview/100x100.jpg?url=https://somewhere.com/mycog.tif&bidx=3&bidx=1&bidx2https://myendpoint/cog/preview?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}.{format}
:endpoint:/cog/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}
PathParams:
QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).epsg:4326.coord_crs.nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).Important
if height or width is provided max_size will be ignored.
Example:
https://myendpoint/cog/bbox/0,0,10,10.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastiehttps://myendpoint/cog/bbox/0,0,10,10/100x100.png?url=https://somewhere.com/mycog.tif:endpoint:/cog/map
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).epsg:4326.:endpoint:/cog/feature - [POST]
:endpoint:/cog/feature.{format} - [POST]
:endpoint:/cog/feature/{width}x{height}.{format} - [POST]
Body:
PathParams:
QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).epsg:4326.coord_crs.nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).Important
if height or width is provided max_size will be ignored.
Example:
https://myendpoint/cog/feature?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/feature.png?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/feature/100x100.png?url=https://somewhere.com/mycog.tif&bidx=1&rescale=0,1000&colormap_name=cfastie:endpoint:/cog/point/{lon},{lat}
PathParams:
QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).epsg:4326.nearest.nearest.Example:
https://myendpoint/cog/point/0,0?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/point/0,0?url=https://somewhere.com/mycog.tif&bidx=1:endpoint:/cog/{tileMatrixSetId}/tilejson.json tileJSON document
PathParams:
WebMercatorQuad)QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).0.5. Output tilesize will be expanded to tilesize + 2 * buffer (e.g 0.5 = 257x257, 1.0 = 258x258).0.hillshade).Example:
https://myendpoint/cog/WebMercatorQuad/tilejson.json?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/WebMercatorQuad/tilejson.json?url=https://somewhere.com/mycog.tif&tile_format=pnghttps://myendpoint/cog/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/mycog.tif&tilesize=256&bidx=1,2,3:endpoint:/cog/{tileMatrixSetId}/map.html Simple viewer
PathParams:
WebMercatorQuad)QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).0.5. Output tilesize will be expanded to tilesize + 2 * buffer (e.g 0.5 = 257x257, 1.0 = 258x258).0.hillshade).Example:
https://myendpoint/cog/WebMercatorQuad/map.html?url=https://somewhere.com/mycog.tifhttps://myendpoint/cog/WebMercatorQuad/map.html?url=https://somewhere.com/mycog.tif&tile_format=pnghttps://myendpoint/cog/WorldCRS84Quad/map.html?url=https://somewhere.com/mycog.tif&tilesize=512&bidx=1,2,3:endpoint:/cog/info general raster info
Example:
https://myendpoint/cog/info?url=https://somewhere.com/mycog.tif:endpoint:/cog/info.geojson general raster info as a GeoJSON feature
epsg:4326.Example:
https://myendpoint/cog/info.geojson?url=https://somewhere.com/mycog.tifAdvanced raster statistics
:endpoint:/cog/statistics - [GET]
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).nearest.hillshade).Example:
https://myendpoint/cog/statistics?url=https://somewhere.com/mycog.tif&bidx=1,2,3&categorical=true&c=1&c=2&c=3&p=2&p98:endpoint:/cog/statistics - [POST]
Body:
QueryParams:
bidx=1, bidx=1&bidx=2&bidx=3).expression=b1/b2).epsg:4326.coord_crs.nearest.nearest.hillshade).Example:
https://myendpoint/cog/statistics?url=https://somewhere.com/mycog.tif&bidx=1,2,3&categorical=true&c=1&c=2&c=3&p=2&p98:endpoint:/cog/viewer - COG Viewer
Example:
https://myendpoint/cog/viewer?url=https://somewhere.com/mycog.tif:endpoint:/cog/validate - COG Viewer
Example:
https://myendpoint/cog/validate?url=https://somewhere.com/mycog.tif:endpoint:/cog/stac - Create STAC Item
projection extension and properties.raster extension and properties.eo extension and properties.Example:
https://myendpoint/cog/stac?url=https://somewhere.com/mycog.tifIn addition to the /cog, /stac and /mosaicjson endpoints, the titiler.application package FastAPI application commes with additional metadata endpoints.
GET /colorMaps JSON retrieve the list of available colorMaps GET /colorMaps/{colorMapId} JSON retrieve the metadata or image of the specified colorMap."},{"location":"endpoints/colormaps/#description","title":"Description","text":""},{"location":"endpoints/colormaps/#list-colormaps","title":"List colormaps","text":":endpoint:/colorMaps - Get the list of supported ColorMaps
$ curl https://myendpoint/colorMaps | jq\n\n{\n \"colorMaps\": [\n \"dense_r\",\n \"delta\",\n ...\n ],\n \"links\": [\n {\n \"href\": \"http://myendpoint/colorMaps\",\n \"rel\": \"self\",\n \"type\": \"application/json\",\n \"title\": \"List of available colormaps\"\n },\n {\n \"href\": \"http://myendpoint/colorMaps/{colorMapId}\",\n \"rel\": \"data\",\n \"type\": \"application/json\",\n \"templated\": true,\n \"title\": \"Retrieve colormap metadata\"\n },\n {\n \"href\": \"http://myendpoint/colorMaps/{colorMapId}?format=png\",\n \"rel\": \"data\",\n \"type\": \"image/png\",\n \"templated\": true,\n \"title\": \"Retrieve colormap as image\"\n }\n ]\n}\n"},{"location":"endpoints/colormaps/#get-colormap-metadata-or-as-image","title":"Get ColorMap metadata or as image","text":":endpoint:/colorMaps/{colorMapId} - Get the ColorMap metadata or image
PathParams:
QueryParams:
horizontal.$ curl http://myendpoint/colorMaps/viridis | jq\n\n{\n \"0\": [\n 68,\n 1,\n 84,\n 255\n ],\n ...\n \"255\": [\n 253,\n 231,\n 36,\n 255\n ]\n}\n curl http://myendpoint/colorMaps/viridis?format=png\n curl http://myendpoint/colorMaps/viridis?format=png&orientation=vertical\n curl http://myendpoint/colorMaps/viridis?format=png&orientation=vertical&width=100&height=1000\n"},{"location":"endpoints/mosaic/","title":"/mosaicjson","text":"The titiler.application package comes with a full FastAPI application with COG, STAC and MosaicJSON supports.
Read Mosaic Info/Metadata and create Web map Tiles from a multiple COG. The mosaic router is built on top of titiler.mosaic.factor.MosaicTilerFactory.
GET /mosaicjson/info JSON return mosaic's basic info GET /mosaicjson/info.geojson GeoJSON return mosaic's basic info as a GeoJSON feature GET /mosaicjson/tiles JSON List of OGC Tilesets available GET /mosaicjson/tiles/{tileMatrixSetId} JSON OGC Tileset metadata GET /mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}] image/bin create a web map tile image from mosaic assets GET /mosaicjson/{tileMatrixSetId}/map.html HTML simple map viewer GET /mosaicjson/{tileMatrixSetId}/tilejson.json JSON return a Mapbox TileJSON document GET /mosaicjson/point/{lon},{lat} JSON return pixel value from a mosaic assets GET /mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}/assets JSON return list of assets intersecting a XYZ tile GET /mosaicjson/point/{lon},{lat}/assets JSON return list of assets intersecting a point GET /mosaicjson/bbox/{minx},{miny},{maxx},{maxy}/assets JSON return list of assets intersecting a bounding box GET /mosaicjson/ JSON return a MosaicJSON document (from titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension) GET /mosaicjson/validate JSON validate a MosaicJSON document (from titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension) GET /mosaicjson/WMTSCapabilities.xml XML return OGC WMTS Get Capabilities (from titiler.mosaic.extensions.wmts.wmtsExtension)"},{"location":"endpoints/mosaic/#description","title":"Description","text":"[TODO]
"},{"location":"endpoints/stac/","title":"/stac","text":"The titiler.application package comes with a full FastAPI application with COG, STAC and MosaicJSON supports.
The /stac routes are based on titiler.core.factory.MultiBaseTilerFactory but with stacViewerExtension extension.
GET /stac/assets JSON return available assets within the STAC item GET /stac/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 JSON List of OGC Tilesets available GET /stac/tiles/{tileMatrixSetId} JSON OGC Tileset metadata GET /stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}] image/bin create a web map tile image from assets GET /stac/{tileMatrixSetId}/map.html HTML simple map viewer GET /stac/{tileMatrixSetId}/tilejson.json JSON return a Mapbox TileJSON document GET /stac/point/{lon},{lat} JSON return pixel value 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/preview[/{width}x{height}][.{format}] image/bin create a preview image from assets GET /stac/viewer HTML demo webpage (from titiler.extensions.stacViewerExtension) GET /stac/WMTSCapabilities.xml XML return OGC WMTS Get Capabilities (from titiler.extensions.wmts.wmtsExtension)"},{"location":"endpoints/stac/#description","title":"Description","text":""},{"location":"endpoints/stac/#tiles","title":"Tiles","text":":endpoint:/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[.{format}]
PathParams:
WebMercatorQuad)QueryParams:
b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).0.5. Output tilesize will be expanded to tilesize + 2 * buffer (e.g 0.5 = 257x257, 1.0 = 258x258).0.hillshade).Example:
https://myendpoint/stac/tiles/WebMercatorQuad/1/2/3?url=https://somewhere.com/item.json&assets=B01&assets=B00https://myendpoint/stac/tiles/WebMercatorQuad/1/2/3.jpg?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/tiles/WorldCRS84Quad/1/2/3?url=https://somewhere.com/item.json&assets=B01&assets=B02expression=b1/b2&rescale=0,1000&colormap_name=cfastie:endpoint:/stac/preview
:endpoint:/stac/preview/.{format}
:endpoint:/stac/preview/{width}x{height}.{format}
PathParams:
QueryParams:
b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).Important
Example:
https://myendpoint/stac/preview?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/preview.jpg?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/preview/100x100.jpg?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/preview?url=https://somewhere.com/item.json&assets=B01&assets=B02expression=b1/b2&rescale=0,1000&colormap_name=cfastie:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}.{format}
:endpoint:/stac/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}
PathParams:
QueryParams:
b1/b2).epsg:4326.coord_crs.nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).Important
Example:
https://myendpoint/stac/bbox/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/bbox/0,0,10,10/100x100.png?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/bbox/0,0,10,10.png?url=https://somewhere.com/item.json&assets=B01&assets=B02expression=b1/b2&rescale=0,1000&colormap_name=cfastie:endpoint:/stac/map
b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).epsg:4326.:endpoint:/stac/feature - [POST]
:endpoint:/stac/feature.{format} - [POST]
:endpoint:/stac/feature/{width}x{height}.{format} - [POST]
Body:
PathParams:
QueryParams:
b1/b2).epsg:4326.coord_crs.nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).hillshade).Important
Example:
https://myendpoint/stac/feature?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/feature.png?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/feature/100x100.png?url=https://somewhere.com/item.json&assets=B01&rescale=0,1000&colormap_name=cfastie:endpoint:/cog/point/{lon},{lat}
PathParams:
QueryParams:
b1/b2).Asset1|1,2,3).epsg:4326.nearest.Example:
https://myendpoint/stac/point/0,0?url=https://somewhere.com/item.json&assets=B01:endpoint:/stac/{tileMatrixSetId}/tilejson.json tileJSON document
PathParams:
WebMercatorQuad)QueryParams:
b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).0.5. Output tilesize will be expanded to tilesize + 2 * buffer (e.g 0.5 = 257x257, 1.0 = 258x258).0.hillshade).Example:
https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=pnghttps://myendpoint/stac/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/item.json&tilesize=256&assets=B01&assets=B02&expression=b1/b2:endpoint:/stac/{tileMatrixSetId}/map.html Simple viewer
PathParams:
WebMercatorQuad)QueryParams:
b1/b2).nearest.nearest.rescale=0,1000, rescale=0,1000&rescale=0,3000&rescale=0,2000).0.5. Output tilesize will be expanded to tilesize + 2 * buffer (e.g 0.5 = 257x257, 1.0 = 258x258).0.hillshade).Example:
https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01https://myendpoint/stac/WebMercatorQuad/tilejson.json?url=https://somewhere.com/item.json&assets=B01&tile_format=pnghttps://myendpoint/stac/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/item.json&tilesize=512&expression=B01/B02:endpoint:/stac/info - Return basic info on STAC item's COG.
Example:
https://myendpoint/stac/info?url=https://somewhere.com/item.json&assets=B01Note
Use assets=:all: to use all available assets
:endpoint:/stac/info.geojson - Return basic info on STAC item's COG as a GeoJSON feature
epsg:4326.Note
Use assets=:all: to use all available assets
:endpoint:/stac/assets - Return the list of available assets
Example:
https://myendpoint/stac/assets?url=https://somewhere.com/item.json:endpoint:/stac/asset_statistics - [GET]
nearest.Example:
https://myendpoint/stac/statistics?url=https://somewhere.com/item.json&assets=B01&categorical=true&c=1&c=2&c=3&p=2&p98Note
Use assets=:all: to use all available assets
:endpoint:/stac/statistics - [GET]
b1/b2).nearest.hillshade).Example:
https://myendpoint/stac/statistics?url=https://somewhere.com/item.json&assets=B01&categorical=true&c=1&c=2&c=3&p=2&p98Note
Use assets=:all: to use all available assets
:endpoint:/stac/statistics - [POST]
Body:
QueryParams:
b1/b2).epsg:4326.coord_crs.nearest.nearest.hillshade).Example:
https://myendpoint/stac/statistics?url=https://somewhere.com/item.json&assets=B01&categorical=true&c=1&c=2&c=3&p=2&p98Note
Use assets=:all: to use all available assets
:endpoint:/stac/viewer - STAC viewer
Example:
https://myendpoint/stac/viewer?url=https://somewhere.com/item.jsonIn addition to the /cog, /stac and /mosaicjson endpoints, the titiler.application package FastAPI application comes with additional metadata endpoints.
GET /tileMatrixSets JSON return the list of supported TileMatrixSet GET /tileMatrixSets/{tileMatrixSetId} JSON return the TileMatrixSet JSON document"},{"location":"endpoints/tms/#description","title":"Description","text":""},{"location":"endpoints/tms/#list-tms","title":"List TMS","text":":endpoint:/tileMatrixSets - Get the list of supported TileMatrixSet
$ curl https://myendpoint/tileMatrixSets | jq\n\n{\n \"tileMatrixSets\": [\n {\n \"id\": \"LINZAntarticaMapTilegrid\",\n \"title\": \"LINZ Antarctic Map Tile Grid (Ross Sea Region)\",\n \"links\": [\n {\n \"href\": \"https://myendpoint/tileMatrixSets/LINZAntarticaMapTilegrid\",\n \"rel\": \"item\",\n \"type\": \"application/json\"\n }\n ]\n },\n ...\n ]\n}\n"},{"location":"endpoints/tms/#get-tms-info","title":"Get TMS info","text":":endpoint:/tileMatrixSets/{tileMatrixSetId} - Get the TileMatrixSet JSON document
$ curl http://127.0.0.1:8000/tileMatrixSets/WebMercatorQuad | jq\n\n{\n \"type\": \"TileMatrixSetType\",\n \"title\": \"Google Maps Compatible for the World\",\n \"identifier\": \"WebMercatorQuad\",\n \"supportedCRS\": \"http://www.opengis.net/def/crs/EPSG/0/3857\",\n \"wellKnownScaleSet\": \"http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible\",\n \"boundingBox\": {\n \"type\": \"BoundingBoxType\",\n \"crs\": \"http://www.opengis.net/def/crs/EPSG/0/3857\",\n \"lowerCorner\": [\n -20037508.3427892,\n -20037508.3427892\n ],\n \"upperCorner\": [\n 20037508.3427892,\n 20037508.3427892\n ]\n },\n \"tileMatrix\": [\n {\n \"type\": \"TileMatrixType\",\n \"identifier\": \"0\",\n \"scaleDenominator\": 559082264.028717,\n \"topLeftCorner\": [\n -20037508.3427892,\n 20037508.3427892\n ],\n \"tileWidth\": 256,\n \"tileHeight\": 256,\n \"matrixWidth\": 1,\n \"matrixHeight\": 1\n },\n ...\n"},{"location":"examples/code/create_gdal_wmts_extension/","title":"GDAL WMTS Extension","text":"Goal: add a /wmts.xml endpoint to return a GDAL WMTS service description XML file
requirements: titiler.extension >=0.11
1 - Create an extension
# wmts.py\n\"\"\"gdal WMTS service Extension.\"\"\"\n\nimport xml.etree.ElementTree as ET\nfrom dataclasses import dataclass\nfrom typing import Literal\nfrom urllib.parse import urlencode\n\nfrom fastapi import Depends, Query\nfrom starlette.requests import Request\n\nfrom titiler.core.factory import BaseTilerFactory, FactoryExtension\nfrom titiler.core.resources.responses import XMLResponse\n\n\n@dataclass\nclass gdalwmtsExtension(FactoryExtension):\n \"\"\"Add /wmts.xml endpoint to a TilerFactory.\"\"\"\n\n def register(self, factory: BaseTilerFactory): # noqa: C901\n \"\"\"Register endpoint to the tiler factory.\"\"\"\n\n @factory.router.get(\n \"/{tileMatrixSetId}/wmts.xml\",\n response_class=XMLResponse,\n responses={\n 200: {\n \"description\": \"GDAL WMTS service description XML file\",\n \"content\": {\n \"application/xml\": {},\n },\n },\n },\n )\n def gdal_wmts(\n request: Request,\n tileMatrixSetId: Literal[tuple(factory.supported_tms.list())] = Path( # type: ignore\n description=\"TileMatrixSet Name\",\n ),\n url: str = Depends(factory.path_dependency), # noqa\n bandscount: int = Query(\n ..., description=\"Number of band returned by the tiler\"\n ),\n datatype: str = Query(..., description=\"Datatype returned by the tiler\"),\n maxconnections: int = Query(\n None,\n description=\"Maximum number of simultaneous connections (defaults to 2).\",\n ),\n timeout: int = Query(\n None, description=\"Connection timeout in seconds (defaults to 30).\"\n ),\n cache: bool = Query(None, description=\"Allow local cache.\"),\n ):\n \"\"\"Return a GDAL WMTS Service description.\"\"\"\n route_params = {\n \"tileMatrixSetId\": tileMatrixSetId,\n }\n wmts_url = factory.url_for(request, \"wmts\", **route_params)\n\n qs_key_to_remove = [\n \"tilematrixsetid\",\n \"bandscount\",\n \"datatype\",\n \"maxconnections\",\n \"timeout\",\n ]\n qs = [\n (key, value)\n for (key, value) in request.query_params._list\n if key.lower() not in qs_key_to_remove\n ]\n if qs:\n wmts_url += f\"?{urlencode(qs)}\"\n\n maxconnections = maxconnections or 2\n timeout = timeout or 30\n\n xml = ET.Element(\"GDAL_WMTS\")\n cap = ET.SubElement(xml, \"GetCapabilitiesUrl\")\n cap.text = wmts_url\n\n bandel = ET.SubElement(xml, \"BandsCount\")\n bandel.text = str(bandscount)\n datael = ET.SubElement(xml, \"DataType\")\n datael.text = datatype\n\n if cache:\n cacheel = ET.SubElement(xml, \"Cache\")\n\n connel = ET.SubElement(xml, \"MaxConnections\")\n connel.text = str(maxconnections)\n timeel = ET.SubElement(xml, \"Timeout\")\n timeel.text = str(timeout)\n codeel = ET.SubElement(xml, \"ZeroBlockHttpCodes\")\n codeel.text = \"404\"\n excepel = ET.SubElement(xml, \"ZeroBlockOnServerException\")\n excepel.text = \"true\"\n\n return XMLResponse(ET.tostring(xml))\n 2 - Create app and register our extension
\"\"\"app.\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\nfrom titiler.core.factory import TilerFactory\n\nfrom fastapi import FastAPI\n\nfrom .wmts import gdalwmtsExtension\n\napp = FastAPI(title=\"My simple app with custom TMS\")\n\n# Create a set of endpoints using TilerFactory and add our extension\ntiler = TilerFactory(extensions=[gdalwmtsExtension()])\n\napp.include_router(tiler.router)\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n 3 - Use it
from rio_tiler.io import Reader\n\nwith Reader(\"http://0.0.0.0/WebMercatorQuad/wmts.xml?url=file.tif&bidx=1&bandscount=1&datatype=float32&tile_format=tif\") as src:\n im = src.preview()\n Notes
The /wmts.xml endpoint has no idea about the data itself and do not care about the bidx or expression which is why we need to set bandscount and datatype parameters.
In the example above we use tile_format=tif so GDAL will fetch tif tiles and keep the datatype from the data (which we assume to be float32)
Goal: Create a simple Raster tiler
requirements: titiler.core
\"\"\"Minimal COG tiler.\"\"\"\n\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory()\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n"},{"location":"examples/code/mosaic_from_urls/","title":"Mosaic from COG urls","text":"Goal: Create a custom mosaic tiler which takes multiple URL as input
requirements: titiler.core | titiler.mosaic
1 - Create a custom Mosaic Backends
\"\"\"mosaic backends.\n\nThe goal is to build a minimalist Mosaic Backend which takes COG paths as input.\n\n>>> with MultiFilesBackend([\"cog1.tif\", \"cog2.tif\"]) as mosaic:\n img = mosaic.tile(1, 1, 1)\n\napp/backends.py\n\n\"\"\"\nfrom typing import Type, List, Tuple, Dict, Union\n\nimport attr\nfrom rio_tiler.io import BaseReader, Reader, MultiBaseReader\nfrom rio_tiler.constants import WEB_MERCATOR_TMS, WGS84_CRS\nfrom rio_tiler.mosaic.backend import BaseBackend\nfrom rasterio.crs import CRS\nfrom morecantile import TileMatrixSet\n\n\n@attr.s\nclass MultiFilesBackend(BaseBackend):\n\n input: list[str] = attr.ib()\n tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)\n\n reader: type[BaseReader] | type[MultiBaseReader] = (\n attr.ib(default=Reader)\n )\n reader_options: dict = attr.ib(factory=dict)\n\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 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 assets_for_bbox(\n self,\n left: float,\n bottom: float,\n right: float,\n top: float,\n coord_crs: CRS | None = None,\n **kwargs,\n ) -> list[str]:\n \"\"\"Retrieve assets for bbox.\"\"\"\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(backend=MultiFilesBackend, path_dependency=DatasetPathParams)\n 3 - Create app and register our custom endpoints
\"\"\"app.\n\napp/main.py\n\n\"\"\"\n\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\nfrom titiler.mosaic.errors import MOSAIC_STATUS_CODES\n\nfrom fastapi import FastAPI\n\nfrom .routers import mosaic\n\napp = FastAPI()\napp.include_router(mosaic.router)\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\nadd_exception_handlers(app, MOSAIC_STATUS_CODES)\n $ uvicorn app:app --reload\n\n$ curl http://127.0.0.1:8000/tilejson.json?url=cog1.tif,cog2.tif\n Gotcha
[-180, -90, 180, 90]Goal: 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, timezone\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.now(timezone.UTC)\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 Annotated, Dict, Optional, Literal\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%2Bxarray/","title":"STAC + Xarray","text":"Goal: Create a custom STAC Reader supporting both COG and NetCDF/Zarr dataset
requirements:
titiler.coretitiler.xarrayfsspeczarrh5netcdfaiohttp (optional)s3fs (optional)links:
First, we need to create a custom STACReader which will support both COG and NetCDF/Zarr dataset. The custom parts will be:
netcdf and zarr as valid asset media typesmd:// prefixed asset form, so users can pass assets=md://{netcdf asset name}?variable={variable name} as we do for the GDAL vrt string connection support.import attr\nfrom urllib.parse import urlparse, parse_qsl\nfrom rio_tiler.types import AssetInfo\nfrom rio_tiler.io import BaseReader, Reader\nfrom rio_tiler.io.stac import DEFAULT_VALID_TYPE, STAC_ALTERNATE_KEY\nfrom rio_tiler.io.stac import STACReader as BaseSTACReader\n\nfrom titiler.xarray.io import FsReader as XarrayReader\n\nvalid_types = {\n *DEFAULT_VALID_TYPE,\n \"application/x-netcdf\",\n \"application/vnd+zarr\",\n}\n\n\n@attr.s\nclass STACReader(BaseSTACReader):\n \"\"\"Custom STACReader which adds support for XarrayReader.\n\n Example:\n >>> with STACReader(\"https://raw.githubusercontent.com/cogeotiff/rio-tiler/refs/heads/main/tests/fixtures/stac_netcdf.json\") as src:\n print(src.assets)\n print(src._get_asset_info(\"netcdf|variable=dataset\"))\n\n ['geotiff', 'netcdf']\n {'url': 'https://raw.githubusercontent.com/cogeotiff/rio-tiler/refs/heads/main/tests/fixtures/dataset_2d.nc', 'name': 'netcdf', 'metadata': {}, 'reader_options': {'variable': 'dataset'}, 'method_options': {}, 'media_type': 'application/x-netcdf'}\n\n \"\"\"\n include_asset_types: set[str] = attr.ib(default=valid_types)\n\n def _get_reader(self, asset_info: AssetInfo) -> type[BaseReader]:\n \"\"\"Get Asset Reader.\"\"\"\n asset_type = asset_info.get(\"media_type\", None)\n if asset_type and asset_type in [\n \"application/x-netcdf\",\n \"application/vnd+zarr\",\n \"application/x-hdf5\",\n \"application/x-hdf\",\n ]:\n return XarrayReader\n\n return Reader\n\n def _get_asset_info(self, asset: str) -> AssetInfo:\n \"\"\"Validate asset names and return asset's info.\n\n Args:\n asset (str): STAC asset name.\n\n Returns:\n AssetInfo: STAC asset info.\n\n \"\"\"\n asset, vrt_options = self._parse_vrt_asset(asset)\n\n reader_options: dict[str, Any] = {}\n method_options: dict[str, Any] = {}\n # NOTE: asset can be in form of\n # \"{asset_name}|some_option=some_value&another_option=another_value\"\n if \"|\" in asset:\n asset, params = asset.split(\"|\", 1)\n # NOTE: Construct method options from params\n if params:\n for param in params.split(\"&\"):\n key, value = param.split(\"=\", 1)\n if key == \"indexes\":\n method_options[\"indexes\"] = list(map(int, value.split(\",\")))\n elif key == \"expression\":\n method_options[\"expression\"] = value\n # XarrayReader Reader-Options\n elif key == \"variable\":\n reader_options[\"variable\"] = value\n elif key == \"group\":\n reader_options[\"group\"] = value\n elif key == \"decode_times\":\n reader_options[\"decode_times\"] = value.lower() in [\"true\", \"yes\", \"1\"]\n elif key == \"datetime\":\n reader_options[\"datetime\"] = value\n elif key == \"drop_dim\":\n reader_options[\"drop_dim\"] = value\n\n if asset not in self.assets:\n raise InvalidAssetName(\n f\"'{asset}' is not valid, should be one of {self.assets}\"\n )\n\n asset_info = self.item.assets[asset]\n extras = asset_info.extra_fields\n\n info = AssetInfo(\n url=asset_info.get_absolute_href() or asset_info.href,\n name=asset,\n metadata=extras if not vrt_options else None,\n reader_options=reader_options,\n method_options=method_options,\n )\n\n if STAC_ALTERNATE_KEY and extras.get(\"alternate\"):\n if alternate := extras[\"alternate\"].get(STAC_ALTERNATE_KEY):\n info[\"url\"] = alternate[\"href\"]\n\n if asset_info.media_type:\n info[\"media_type\"] = asset_info.media_type\n\n # https://github.com/stac-extensions/file\n if head := extras.get(\"file:header_size\"):\n info[\"env\"] = {\"GDAL_INGESTED_BYTES_AT_OPEN\": head}\n\n # https://github.com/stac-extensions/raster\n if extras.get(\"raster:bands\") and not vrt_options:\n bands = extras.get(\"raster:bands\")\n stats = [\n (b[\"statistics\"][\"minimum\"], b[\"statistics\"][\"maximum\"])\n for b in bands\n if {\"minimum\", \"maximum\"}.issubset(b.get(\"statistics\", {}))\n ]\n # check that stats data are all double and make warning if not\n if (\n stats\n and all(isinstance(v, (int, float)) for stat in stats for v in stat)\n and len(stats) == len(bands)\n ):\n info[\"dataset_statistics\"] = stats\n else:\n warnings.warn(\n \"Some statistics data in STAC are invalid, they will be ignored.\"\n )\n\n if vrt_options:\n info[\"url\"] = f\"vrt://{info['url']}?{vrt_options}\"\n\n return info\n"},{"location":"examples/code/tiler_with_custom_stac%2Bxarray/#2-application","title":"2. Application","text":"main.py\"\"\"FastAPI application.\"\"\"\nfrom fastapi import FastAPI\nfrom titiler.core.factory import MultiBaseTilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom .stac import STACReader\n\n# STAC uses MultiBaseReader so we use MultiBaseTilerFactory to built the default endpoints\nstac = MultiBaseTilerFactory(\n reader=STACReader, \n add_preview=False, \n add_ogc_maps=False,\n)\n\n# Create FastAPI application\napp = FastAPI()\napp.include_router(stac.router)\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n uvicorn app:app --port 8080 --reload\n"},{"location":"examples/code/tiler_with_custom_stac%2Bxarray/#available-assets","title":"Available Assets","text":"curl http://127.0.0.1:8080/assets\\?url\\=https%3A%2F%2Fraw.githubusercontent.com%2Fcogeotiff%2Frio-tiler%2Frefs%2Fheads%2Fmain%2Ftests%2Ffixtures%2Fstac_netcdf.json | jq\n\n[\n \"geotiff\",\n \"netcdf\"\n]\n"},{"location":"examples/code/tiler_with_custom_stac%2Bxarray/#info","title":"Info","text":"curl http://127.0.0.1:8080/info?url=https://raw.githubusercontent.com/cogeotiff/rio-tiler/refs/heads/main/tests/fixtures/stac_netcdf.json&assets=netcdf|variable=dataset | jq\n{\n \"netcdf|variable=dataset\": {\n \"bounds\": [\n -170.085,\n -80.08,\n 169.914999999975,\n 79.91999999999659\n ],\n \"crs\": \"http://www.opengis.net/def/crs/EPSG/0/4326\",\n \"band_metadata\": [\n [\n \"b1\",\n {}\n ]\n ],\n \"band_descriptions\": [\n [\n \"b1\",\n \"dataset\"\n ]\n ],\n \"dtype\": \"float64\",\n \"nodata_type\": \"Nodata\",\n \"name\": \"dataset\",\n \"count\": 1,\n \"width\": 2000,\n \"height\": 1000,\n \"dimensions\": [\n \"y\",\n \"x\"\n ],\n \"attrs\": {\n \"valid_min\": 1.0,\n \"valid_max\": 1000.0,\n \"fill_value\": 0\n }\n }\n}\n"},{"location":"examples/code/tiler_with_custom_stac%2Bxarray/#maphtml","title":"Map.html","text":"http://127.0.0.1:8080/WebMercatorQuad/map.html?url=https://raw.githubusercontent.com/cogeotiff/rio-tiler/refs/heads/main/tests/fixtures/stac_netcdf.json&assets=netcdf|variable=dataset&rescale=0,1000&colormap_name=viridis\n"},{"location":"examples/code/tiler_with_custom_stac%2Bxarray/#tile-request","title":"Tile Request","text":"http://127.0.0.1:8080/tiles/WebMercatorQuad/1/0/0?url=https://raw.githubusercontent.com/cogeotiff/rio-tiler/refs/heads/main/tests/fixtures/stac_netcdf.json&assets=netcdf|variable=dataset&rescale=0,1000\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/tiler_with_layers/","title":"Custom Layers","text":"Goal: Create a Custom TiTiler with a limited set of supported layers
requirements: titiler.core
How:
The idea is to create a set of endpoints with a /layers/{layer_id} prefix and a set of configuration, e.g
config = {\n \"layer_1\": {\n \"url\": \"dataset_1 url\",\n \"indexes\": [1],\n \"render\": {\n \"rescale\": [(0, 1000)],\n \"colormap_name\": \"viridis\"\n }\n },\n ...\n}\n We then use custom set of endpoint dependencies to get the layer configuration and inject the parameters.
import json\nfrom dataclasses import dataclass, field\nfrom typing import Dict, Literal, Annotated, Optional, Sequence\n\nfrom fastapi import FastAPI, Path, HTTPException, Query\nfrom rio_tiler.colormap import ColorMaps\nfrom rio_tiler.colormap import cmap as default_cmap\nfrom rio_tiler.colormap import parse_color\nfrom starlette.requests import Request\n\nfrom titiler.core import dependencies\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\nfrom titiler.core.factory import TilerFactory\n\n\n# Layers Configuration\navailable_layers = {\n \"red\": {\n \"url\": \"https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B04.tif\",\n \"render\": {\n \"rescale\": [\n (0, 1000),\n ],\n },\n },\n \"green\": {\n \"url\": \"https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B03.tif\",\n \"render\": {\n \"rescale\": [\n (0, 1000),\n ],\n },\n },\n \"bleue\": {\n \"url\": \"https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B02.tif\",\n \"render\": {\n \"rescale\": [\n (0, 1000),\n ],\n },\n },\n}\n\n# VRT of bands B04, B03, B02, and B05 files\n# gdalbuildvrt vrt.vrt /vsicurl/https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B{04,03,02,08}.tif -separate\n# cat vrt.vrt | tr -d '\\n' | tr -d ' '\nvrt_rdbnir = '<VRTDataset rasterXSize=\"10980\" rasterYSize=\"10980\"> <SRS dataAxisToSRSAxisMapping=\"1,2\">PROJCS[\"WGS 84 / UTM zone 21S\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-57],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",10000000],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],AUTHORITY[\"EPSG\",\"32721\"]]</SRS> <GeoTransform> 6.0000000000000000e+05, 1.0000000000000000e+01, 0.0000000000000000e+00, 7.3000000000000000e+06, 0.0000000000000000e+00, -1.0000000000000000e+01</GeoTransform> <VRTRasterBand dataType=\"UInt16\" band=\"1\"> <NoDataValue>0</NoDataValue> <ComplexSource> <SourceFilename relativeToVRT=\"0\">/vsicurl/https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B04.tif</SourceFilename> <SourceBand>1</SourceBand> <SourceProperties RasterXSize=\"10980\" RasterYSize=\"10980\" DataType=\"UInt16\" BlockXSize=\"1024\" BlockYSize=\"1024\" /> <SrcRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <DstRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <NODATA>0</NODATA> </ComplexSource> </VRTRasterBand> <VRTRasterBand dataType=\"UInt16\" band=\"2\"> <NoDataValue>0</NoDataValue> <ComplexSource> <SourceFilename relativeToVRT=\"0\">/vsicurl/https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B03.tif</SourceFilename> <SourceBand>1</SourceBand> <SourceProperties RasterXSize=\"10980\" RasterYSize=\"10980\" DataType=\"UInt16\" BlockXSize=\"1024\" BlockYSize=\"1024\" /> <SrcRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <DstRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <NODATA>0</NODATA> </ComplexSource> </VRTRasterBand> <VRTRasterBand dataType=\"UInt16\" band=\"3\"> <NoDataValue>0</NoDataValue> <ComplexSource> <SourceFilename relativeToVRT=\"0\">/vsicurl/https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B02.tif</SourceFilename> <SourceBand>1</SourceBand> <SourceProperties RasterXSize=\"10980\" RasterYSize=\"10980\" DataType=\"UInt16\" BlockXSize=\"1024\" BlockYSize=\"1024\" /> <SrcRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <DstRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <NODATA>0</NODATA> </ComplexSource> </VRTRasterBand> <VRTRasterBand dataType=\"UInt16\" band=\"4\"> <NoDataValue>0</NoDataValue> <ComplexSource> <SourceFilename relativeToVRT=\"0\">/vsicurl/https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/J/XN/2021/2/S2B_21JXN_20210214_1_L2A/B08.tif</SourceFilename> <SourceBand>1</SourceBand> <SourceProperties RasterXSize=\"10980\" RasterYSize=\"10980\" DataType=\"UInt16\" BlockXSize=\"1024\" BlockYSize=\"1024\" /> <SrcRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <DstRect xOff=\"0\" yOff=\"0\" xSize=\"10980\" ySize=\"10980\" /> <NODATA>0</NODATA> </ComplexSource> </VRTRasterBand></VRTDataset>'\n\n# Mpre configs (using VRT)\navailable_layers.update(\n {\n \"true_color\": {\n \"url\": vrt_rdbnir,\n \"indexes\": [1, 2, 3],\n \"render\": {\n \"rescale\": [\n (0, 3000),\n (0, 3000),\n (0, 3000),\n ],\n },\n },\n \"false_color\": {\n \"url\": vrt_rdbnir,\n \"indexes\": [4, 1, 2],\n \"render\": {\n \"rescale\": [\n (0, 4000),\n (0, 3000),\n (0, 3000),\n ],\n },\n },\n \"ndvi\": {\n \"url\": vrt_rdbnir,\n \"expression\": \"(b4-b1)/(b4+b1)\",\n \"render\": {\n \"rescale\": [\n (-1, 1),\n ],\n \"colormap_name\": \"viridis\",\n },\n },\n }\n)\n\n# List of all Layers\nlayers_list = Literal[\"red\", \"green\", \"blue\", \"true_color\", \"false_color\", \"ndvi\"]\n\n\n# Custom `DatasetPathParams` which return the dataset URL for a `layer_id`\ndef DatasetPathParams(layer_id: layers_list = Path()) -> str:\n return available_layers[layer_id][\"url\"]\n\n\n@dataclass\nclass CustomAsDict:\n \"\"\"Custom `DefaultDependency` to ignore `requests`\"\"\"\n def as_dict(self, exclude_none: bool = True) -> Dict:\n \"\"\"Transform dataclass to dict.\"\"\"\n exclude_keys = {\"request\"}\n if exclude_none:\n return {\n k: v\n for k, v in self.__dict__.items()\n if v is not None and k not in exclude_keys\n }\n\n return {k: v for k, v in self.__dict__.items() if k not in exclude_keys}\n\n\n# Custom Layer Param\n@dataclass\nclass LayerParams(CustomAsDict, dependencies.BidxExprParams):\n\n request: Request = field(default=None)\n\n def __post_init__(self):\n if (layer := self.request.path_params.get(\"layer_id\")) and not any(\n [self.indexes, self.expression]\n ):\n layer_params = available_layers[layer]\n if indexes := layer_params.get(\"indexes\"):\n self.indexes = indexes\n elif expr := layer_params.get(\"expression\"):\n self.expression = expr\n\n# Custom Rendering Params\n@dataclass\nclass RenderingParams(CustomAsDict, dependencies.ImageRenderingParams):\n\n request: Request = field(default=None)\n\n def __post_init__(self):\n super().__post_init__()\n\n if layer := self.request.path_params.get(\"layer_id\"):\n layer_params = available_layers[layer].get(\"render\", {})\n\n if not self.rescale and (rescale := layer_params.get(\"rescale\")):\n self.rescale = rescale\n\n if not self.color_formula and (color_formula := layer_params.get(\"color_formula\")):\n self.color_formula = color_formula\n\n if self.add_mask is not None and (add_mask := layer_params.get(\"add_mask\")):\n self.add_mask = add_mask\n\n\n# Custom ColorMap Params\ndef ColorMapParams(\n request: Request,\n colormap_name: Annotated[ # type: ignore\n Literal[tuple(default_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 layer := request.path_params.get(\"layer_id\"):\n layer_params = available_layers[layer].get(\"render\", {})\n colormap_name = layer_params.get(\"colormap_name\", colormap_name)\n colormap = layer_params.get(\"colormap\", colormap)\n\n if colormap_name:\n return default_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\napp = FastAPI()\n\ncog = TilerFactory(\n path_dependency=DatasetPathParams,\n layer_dependency=LayerParams,\n render_dependency=RenderingParams,\n colormap_dependency=ColorMapParams,\n router_prefix=\"/layers/{layer_id}\",\n)\napp.include_router(cog.router, prefix=\"/layers/{layer_id}\")\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n# Run the application\nimport uvicorn\nuvicorn.run(app=app, host=\"127.0.0.1\", port=8080, log_level=\"info\")\n http://127.0.0.1:8080/docs
http://127.0.0.1:8080/layers/true_color/preview
ref: developmentseed/titiler?331
Goal: allow users to pass signed url or url containing query parameters (delimited with &)
requirements: titiler.core
"},{"location":"examples/code/working_with_signed_urls/#what-why-how","title":"What / Why / How","text":"Passing a signed URL or a complex URL for a dataset is not supported by default in TiTiler because the parameters (delimited with &) from the signed url conflict with the query parameters from the application itself. In order to allow signed url in the application there are two solutions:
The easiest way (from the application's point of view) to allow complex URLs is to allow an encoded url as an input parameter.
import base64\nurl = \"http://my.dataset.com/cog.tif?p=aaa&c&1234&t=4321\"\n\n# base64.b64encode(url.encode())\n>>> \"aHR0cDovL215LmRhdGFzZXQuY29tL2NvZy50aWY/cD1hYWEmYyYxMjM0JnQ9NDMyMQ==\"\n When base64 encoded, the url is just a regular string and thus will be valid in the application.
\"\"\"Minimal COG tiler with Signed URL support.\"\"\"\n\nimport base64\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\n# Custom Path dependency which can `decode` a base64 url\ndef DatasetPathParams(\n url: str = Query(..., description=\"Dataset URL\"),\n base64_encoded: bool = Query(None)\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n if base64_encoded:\n url = base64.b64decode(url).decode()\n return url\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory(path_dependency=DatasetPathParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n import base64\nfrom my_provider import signed_url\n\nmy_url = \"https://dataset.com/....\"\n\n# Get signed URL\nmy_signed_url = signed_url(my_url)\n\n# Encode the signed url using base64\nurl = base64.b64encode(my_signed_url.encode())\n\ninfo = request.get(f\"{titiler_endpoint}/info\", params={\"url\": url, signed_url: True})\n"},{"location":"examples/code/working_with_signed_urls/#12-encode-only-the-url-params","title":"1.2 Encode only the url params","text":"\"\"\"Minimal COG tiler with Signed URL support.\"\"\"\n\nimport base64\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\n# Another solution is to pass only the query parameters encoded in base64\ndef DatasetPathParams(\n url: str = Query(..., description=\"Dataset URL\"),\n url_params: str = Query(\n None, description=\"Base64 encoded Query parameters to add to the dataset URL.\"\n ),\n) -> str:\n \"\"\"DatasetPath Params.\"\"\"\n if url_params:\n url += f\"?{b64decode(url_params).decode()}\"\n return url\n\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory(path_dependency=DatasetPathParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n import base64\nfrom urllib.parse import urlparse\nfrom my_provider import signed_url\n\nmy_url = \"https://dataset.com/....\"\n\n# Get signed URL\nmy_signed_url = signed_url(my_url)\n\n# Extract the url parameters\nsigned_params = urlparse(my_signed_url).query\n\n# Encode the parameters using base64\nencoded_params = base64.b64encode(signed_params.encode())\n\ninfo = request.get(f\"{titiler_endpoint}/info\", params={\"url\": url, url_params: encoded_params})\n"},{"location":"examples/code/working_with_signed_urls/#2-signing-url-in-the-application","title":"2. Signing URL in the application","text":"Another solution is to sign the URL directly in TiTiler.
\"\"\"Minimal COG tiler with Signed URL support.\"\"\"\n\nimport my_provider # e.g AWS, Google, ...\n\nfrom titiler.core.factory import TilerFactory\nfrom titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers\n\nfrom fastapi import FastAPI\n\n\n# Custom Path dependency which will sign url\n# !!! You may want to add caching here to avoid to many call to the signing provider !!!\ndef DatasetPathParams(\n url: str = Query(..., description=\"Dataset URL\"),\n) -> str:\n \"\"\"Create dataset path from args\"\"\"\n # Use your provider library to sign the URL\n return my_provider.sign(url)\n\n\napp = FastAPI(title=\"My simple app\")\n\ncog = TilerFactory(path_dependency=DatasetPathParams)\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\nadd_exception_handlers(app, DEFAULT_STATUS_CODES)\n\n\n@app.get(\"/healthz\", description=\"Health Check\", tags=[\"Health Check\"])\ndef ping():\n \"\"\"Health check.\"\"\"\n return {\"ping\": \"pong!\"}\n import base64\nfrom my_provider import signed_url\n\nmy_url = \"https://dataset.com/....\"\n\ninfo = request.get(f\"{titiler_endpoint}/info\", params={\"url\": my_url})\n"},{"location":"examples/notebooks/Working_with_Algorithm/","title":"Working With Algorithms","text":"In\u00a0[\u00a0]: Copied! import json\nimport httpx\n\nfrom folium import Map, TileLayer\nimport json import httpx from folium import Map, TileLayer In\u00a0[\u00a0]: Copied!
titiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\ntitiler_endpoint = ( \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. ) In\u00a0[\u00a0]: Copied!
url = \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\"\nurl = \"https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif\" In\u00a0[\u00a0]: Copied!
# Fetch dataset Metadata\nr = httpx.get(\n f\"{titiler_endpoint}/cog/info\",\n params={\n \"url\": url,\n },\n).json()\n\nprint(r)\n # Fetch dataset Metadata r = httpx.get( f\"{titiler_endpoint}/cog/info\", params={ \"url\": url, }, ).json() print(r) In\u00a0[\u00a0]: Copied! r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params={\n \"url\": url,\n },\n).json()\n\nbounds = r[\"bounds\"]\nm = Map(\n location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2),\n zoom_start=r[\"minzoom\"],\n)\n\nTileLayer(\n tiles=r[\"tiles\"][0], opacity=1, 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], opacity=1, 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(tiles=r[\"tiles\"][0], opacity=1, attr=\"Yo!!\")\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(tiles=r[\"tiles\"][0], opacity=1, attr=\"Yo!!\").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(tiles=r[\"tiles\"][0], opacity=1, attr=\"Yo!!\").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
By default, the tiles will be rescaled from min/max from dataset statistics (1615.812 / 2015.09448)
"},{"location":"examples/notebooks/Working_with_Algorithm/#show-available-algorithms","title":"Show Available Algorithms\u00b6","text":""},{"location":"examples/notebooks/Working_with_Algorithm/#display-hillshade-tiles","title":"Display Hillshade Tiles\u00b6","text":""},{"location":"examples/notebooks/Working_with_Algorithm/#pass-parameters-to-the-algorithm","title":"Pass parameters to the algorithm\u00b6","text":""},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/","title":"Working With COG - At Scale","text":"In\u00a0[\u00a0]: Copied!# Uncomment this line if you need to install the dependencies\n# !pip install rasterio boto3 folium requests tqdm\n# Uncomment this line if you need to install the dependencies # !pip install rasterio boto3 folium requests tqdm In\u00a0[\u00a0]: Copied!
import os\nimport datetime\nimport json\nimport urllib.parse\nfrom io import BytesIO\nfrom functools import partial\nfrom concurrent import futures\n\nimport httpx\nimport numpy\nfrom boto3.session import Session as boto3_session\n\nfrom rasterio.plot import reshape_as_image\nfrom rasterio.features import bounds as featureBounds\n\nfrom tqdm.notebook import tqdm\n\nfrom folium import Map, TileLayer, GeoJson\n\nimport matplotlib.pyplot as plt\nimport matplotlib.dates as mdates\nimport os import datetime import json import urllib.parse from io import BytesIO from functools import partial from concurrent import futures import httpx import numpy from boto3.session import Session as boto3_session from rasterio.plot import reshape_as_image from rasterio.features import bounds as featureBounds from tqdm.notebook import tqdm from folium import Map, TileLayer, GeoJson import matplotlib.pyplot as plt import matplotlib.dates as mdates In\u00a0[\u00a0]: Copied!
titiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\ntitiler_endpoint = ( \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. ) In\u00a0[\u00a0]: Copied!
# use geojson.io\ngeojson = {\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [\n [\n [-74.1796875, 45.18978009667531],\n [-73.092041015625, 45.18978009667531],\n [-73.092041015625, 46.00459325574482],\n [-74.1796875, 46.00459325574482],\n [-74.1796875, 45.18978009667531],\n ]\n ],\n },\n }\n ],\n}\n\nbounds = featureBounds(geojson)\n # use geojson.io geojson = { \"type\": \"FeatureCollection\", \"features\": [ { \"type\": \"Feature\", \"properties\": {}, \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ [ [-74.1796875, 45.18978009667531], [-73.092041015625, 45.18978009667531], [-73.092041015625, 46.00459325574482], [-74.1796875, 46.00459325574482], [-74.1796875, 45.18978009667531], ] ], }, } ], } bounds = featureBounds(geojson) In\u00a0[\u00a0]: Copied! m = Map(\n tiles=\"OpenStreetMap\",\n location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2),\n zoom_start=6,\n)\n\nGeoJson(geojson).add_to(m)\nm\nm = Map( tiles=\"OpenStreetMap\", location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2), zoom_start=6, ) GeoJson(geojson).add_to(m) m In\u00a0[\u00a0]: Copied!
# To Be able to run this notebook you'll need to have AWS credential available in the environment\n\n# import os\n# os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\"\n# os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\"\n# To Be able to run this notebook you'll need to have AWS credential available in the environment # import os # os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\" # os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\" In\u00a0[\u00a0]: Copied!
session = boto3_session(region_name=\"us-west-2\")\nclient = session.client(\"s3\")\n\nbucket = \"omi-no2-nasa\" # https://registry.opendata.aws/omi-no2-nasa/\n\n\ndef list_objects(bucket, prefix):\n \"\"\"AWS s3 list objects.\"\"\"\n\n paginator = client.get_paginator(\"list_objects_v2\")\n\n files = []\n for subset in paginator.paginate(Bucket=bucket, Prefix=prefix):\n files.extend(subset.get(\"Contents\", []))\n\n return files\n\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.0 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.0 for r in list_files]) print(f\"Size of the archive: {size} Mo ({size / 1000} Go)\") In\u00a0[\u00a0]: Copied! print(files[0:10])\nprint(files[0:10])
file name structure is \"OMI-Aura_L3-OMNO2d_{YEAR}m{MONTH:02}{DAY:02}...\"
We can then easily filter e.g
In\u00a0[\u00a0]: Copied!files_2019 = list(filter(lambda x: x.split(\"_\")[2][0:4] == \"2019\", files))\nprint(len(files_2019))\nfiles_2019 = list(filter(lambda x: x.split(\"_\")[2][0:4] == \"2019\", files)) print(len(files_2019)) In\u00a0[\u00a0]: Copied!
files_Oct5 = list(\n filter(\n lambda x: (x.split(\"_\")[2][5:7] == \"10\") & (x.split(\"_\")[2][7:9] == \"05\"), files\n )\n)\nprint(len(files_Oct5))\nprint(files_Oct5)\nfiles_Oct5 = list( filter( lambda x: (x.split(\"_\")[2][5:7] == \"10\") & (x.split(\"_\")[2][7:9] == \"05\"), files ) ) print(len(files_Oct5)) print(files_Oct5) In\u00a0[\u00a0]: Copied!
def _url(src_path):\n return f\"s3://omi-no2-nasa/{src_path}\"\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\", params={\"url\": _url(files[0])}\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), zoom_start=6\n)\n\nTileLayer(tiles=r[\"tiles\"][0], opacity=1, attr=\"NASA\").add_to(m)\n\nGeoJson(geojson, style_function=lambda feature: {\"fill\": False, \"color\": \"red\"}).add_to(\n m\n)\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\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\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 = [executor.submit(fetch_bbox, file) for file in files_15]\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 = [v[1] for v in values]\n\nfig, ax1 = plt.subplots(dpi=300)\nfig.autofmt_xdate()\n\nax1.plot(dates, max_values, label=\"No2\")\nax1.xaxis.set_major_locator(mdates.YearLocator(1, 7))\n\nax1.set_xlabel(\"Dates\")\nax1.set_ylabel(\"No2\")\n\nax1.legend()\nwith futures.ThreadPoolExecutor(max_workers=10) as executor: future_work = [executor.submit(fetch_bbox, file) for file in files_15] for f in tqdm(futures.as_completed(future_work), total=len(future_work)): pass values, dates = zip(*list(_filter_futures(future_work))) max_values = [v[1] for v in values] fig, ax1 = plt.subplots(dpi=300) fig.autofmt_xdate() ax1.plot(dates, max_values, label=\"No2\") ax1.xaxis.set_major_locator(mdates.YearLocator(1, 7)) ax1.set_xlabel(\"Dates\") ax1.set_ylabel(\"No2\") ax1.legend() In\u00a0[\u00a0]: Copied!
with futures.ThreadPoolExecutor(max_workers=50) as executor:\n future_work = [executor.submit(fetch_bbox, file) for file in files]\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 = [v[1] for v in values]\n\nfig, ax1 = plt.subplots(dpi=150)\nfig.autofmt_xdate()\n\nax1.plot(dates, max_values, label=\"No2\")\nax1.xaxis.set_major_locator(mdates.YearLocator())\n\nax1.set_xlabel(\"Dates\")\nax1.set_ylabel(\"No2\")\n\nax1.legend()\nwith futures.ThreadPoolExecutor(max_workers=50) as executor: future_work = [executor.submit(fetch_bbox, file) for file in files] for f in tqdm(futures.as_completed(future_work), total=len(future_work)): pass values, dates = zip(*list(_filter_futures(future_work))) max_values = [v[1] for v in values] fig, ax1 = plt.subplots(dpi=150) fig.autofmt_xdate() ax1.plot(dates, max_values, label=\"No2\") ax1.xaxis.set_major_locator(mdates.YearLocator()) ax1.set_xlabel(\"Dates\") ax1.set_ylabel(\"No2\") ax1.legend() In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#working-with-cog-at-scale","title":"Working With COG - At Scale\u00b6","text":"
For this demo we will use the new Ozone Monitoring Instrument (OMI) / Aura NO2 Tropospheric Column Density dataset hosted on AWS PDS: https://registry.opendata.aws/omi-no2-nasa/
Requirement: AWS credentials
"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF/#requirements","title":"Requirements\u00b6","text":"!pip install rasterio boto3 folium httpx tqdm
{endpoint}/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}?url={cog}&{otherquery params}
{endpoint}/cog/bbox/{minx},{miny},{maxx},{maxy}.{format}?url={cog}&{otherquery params}
{endpoint}/cog/point/{minx},{miny}?url={cog}&{otherquery params}
# Uncomment this line if you need to install the dependencies\n# !pip install folium httpx\n# Uncomment this line if you need to install the dependencies # !pip install folium httpx In\u00a0[\u00a0]: Copied!
import json\n\nimport httpx\n\nfrom folium import Map, TileLayer\n\n%matplotlib inline\nimport json import httpx from folium import Map, TileLayer %matplotlib inline In\u00a0[\u00a0]: Copied!
titiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\ntitiler_endpoint = ( \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. ) url = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\" In\u00a0[\u00a0]: Copied!
# Fetch File Metadata to get min/max rescaling values (because the file is stored as float32)\nr = httpx.get(\n f\"{titiler_endpoint}/cog/info\",\n params={\n \"url\": url,\n },\n).json()\n\nbounds = r[\"bounds\"]\nprint(r)\n # Fetch File Metadata to get min/max rescaling values (because the file is stored as float32) r = httpx.get( f\"{titiler_endpoint}/cog/info\", params={ \"url\": url, }, ).json() bounds = r[\"bounds\"] print(r) In\u00a0[\u00a0]: Copied! # Fetch File Metadata to get min/max rescaling values (because the file is stored as float32)\nr = httpx.get(\n f\"{titiler_endpoint}/cog/statistics\",\n params={\n \"url\": url,\n },\n).json()\n\nprint(json.dumps(r, indent=4))\n # Fetch File Metadata to get min/max rescaling values (because the file is stored as float32) r = httpx.get( f\"{titiler_endpoint}/cog/statistics\", params={ \"url\": url, }, ).json() print(json.dumps(r, indent=4)) In\u00a0[\u00a0]: Copied! r = httpx.get(\n f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json\",\n params={\n \"url\": url,\n },\n).json()\n\nm = Map(\n location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2), zoom_start=13\n)\n\nTileLayer(tiles=r[\"tiles\"][0], opacity=1, attr=\"DigitalGlobe OpenData\").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 = (\n r[\"band_metadata\"][0][1][\"STATISTICS_MINIMUM\"],\n r[\"band_metadata\"][0][1][\"STATISTICS_MAXIMUM\"],\n)\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(tiles=r[\"tiles\"][0], opacity=1, attr=\"Swisstopo\").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={\"url\": url, \"rescale\": f\"{minv},{maxv}\", \"colormap_name\": \"terrain\"},\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(tiles=r[\"tiles\"][0], opacity=1, attr=\"Swisstopo\").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={\"url\": url, \"colormap\": cmap},\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(tiles=r[\"tiles\"][0], opacity=1, attr=\"Swisstopo\")\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"},{"location":"examples/notebooks/Working_with_CloudOptimizedGeoTIFF_simple/#working-with-cog","title":"Working With COG\u00b6","text":"For this demo we will use the new DigitalGlobe OpenData dataset https://www.digitalglobe.com/ecosystem/open-data
pip install folium httpx
Note: By default if the metadata has min/max statistics, titiler will use those to rescale the data
# Uncomment this line if you need to install the dependencies\n#!pip install rasterio folium tqdm httpx rio-tiler geojson_pydantic cogeo-mosaic\n# Uncomment this line if you need to install the dependencies #!pip install rasterio folium tqdm httpx rio-tiler geojson_pydantic cogeo-mosaic In\u00a0[\u00a0]: Copied!
import os\nimport json\nimport rasterio\nimport httpx\nfrom boto3.session import Session as boto3_session\n\nfrom concurrent import futures\nfrom rio_tiler.io import COGReader\nfrom rasterio.features import bounds as featureBounds\n\nfrom folium import Map, TileLayer, GeoJson\nimport os import json import rasterio import httpx from boto3.session import Session as boto3_session from concurrent import futures from rio_tiler.io import COGReader from rasterio.features import bounds as featureBounds from folium import Map, TileLayer, GeoJson In\u00a0[\u00a0]: Copied!
# To Be able to run this notebook you'll need to have AWS credential available in the environment\n\n# import os\n# os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\"\n# os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\"\n# To Be able to run this notebook you'll need to have AWS credential available in the environment # import os # os.environ[\"AWS_ACCESS_KEY_ID\"] = \"YOUR AWS ACCESS ID HERE\" # os.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"YOUR AWS ACCESS KEY HERE\" In\u00a0[\u00a0]: Copied!
session = boto3_session(region_name=\"us-west-2\")\nclient = session.client(\"s3\")\n\nbucket = \"noaa-eri-pds\" # https://registry.opendata.aws/omi-no2-nasa/\n\n\ndef list_objects(bucket, prefix):\n \"\"\"AWS s3 list objects.\"\"\"\n\n paginator = client.get_paginator(\"list_objects_v2\")\n\n files = []\n for subset in paginator.paginate(Bucket=bucket, Prefix=prefix):\n files.extend(subset.get(\"Contents\", []))\n\n return [r[\"Key\"] for r in files]\n\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)\nprint(files) In\u00a0[\u00a0]: Copied!
# We can derive the `bbox` from the filename\n# s3://noaa-eri-pds/2020_Nashville_Tornado/20200307a_RGB/20200307aC0870130w361200n.tif\n# -> 20200307aC0870130w361200n.tif\n# -> 20200307aC \"0870130w\" \"361200n\" .tif\n# -> 0870130w -> 87.025 (West)\n# -> 361200n -> 36.2 (Top)\n# We also know each files cover ~0.025x~0.025 degrees\n\nimport re\nfrom geojson_pydantic.features import Feature\nfrom geojson_pydantic.geometries import Polygon\n\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)) * (\n -1 if direction in [\"W\", \"S\"] else 1\n )\n\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(lon, lat - 0.025, lon + 0.025, lat),\n properties={\n \"path\": src_path,\n },\n )\n\n\nfeatures = [fname_to_feature(f).dict(exclude_none=True) for f in files]\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,\n \"dashArray\": \"1\",\n \"fillOpacity\": 0,\n \"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)\nfrom rio_tiler.io import COGReader from cogeo_mosaic.mosaic import MosaicJSON from cogeo_mosaic.backends import MosaicBackend with COGReader(files[0]) as cog: info = cog.info() print(info.minzoom) print(info.maxzoom) In\u00a0[\u00a0]: Copied!
# We are creating the mosaicJSON using the features we created earlier\n# by default MosaicJSON.from_feature will look in feature.properties.path to get the path of the dataset\nmosaicdata = MosaicJSON.from_features(\n features, minzoom=info.minzoom, maxzoom=info.maxzoom\n)\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 = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\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), 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,\n \"dashArray\": \"1\",\n \"fillOpacity\": 0,\n \"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
Read each file geo metadata
"},{"location":"examples/notebooks/Working_with_MosaicJSON/#5-create-mosaic","title":"5. Create Mosaic\u00b6","text":""},{"location":"examples/notebooks/Working_with_NumpyTile/","title":"NumpyTile","text":"In\u00a0[\u00a0]: Copied!# Uncomment this line if you need to install the dependencies\n# !pip install numpy mercantile\n# Uncomment this line if you need to install the dependencies # !pip install numpy mercantile In\u00a0[\u00a0]: Copied!
import httpx\nimport mercantile\nfrom io import BytesIO\nimport numpy\n\n%pylab inline\nimport httpx import mercantile from io import BytesIO import numpy %pylab inline In\u00a0[\u00a0]: Copied!
titiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\ntitiler_endpoint = ( \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. ) url = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\" In\u00a0[\u00a0]: Copied!
r = httpx.get(f\"{titiler_endpoint}/cog/WebMercatorQuad/tilejson.json?url={url}\").json()\nprint(r)\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(\n f\"{titiler_endpoint}/cog/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}.npy?url={url}\"\n)\n # Call TiTiler endpoint using the first tile tile = tiles[0] r = httpx.get( f\"{titiler_endpoint}/cog/tiles/WebMercatorQuad/{tile.z}/{tile.x}/{tile.y}.npy?url={url}\" ) In\u00a0[\u00a0]: Copied! # Load result using numpy.load\n\narr = numpy.load(BytesIO(r.content))\nprint(type(arr))\nprint(arr.shape)\n# Load result using numpy.load arr = numpy.load(BytesIO(r.content)) print(type(arr)) print(arr.shape) In\u00a0[\u00a0]: Copied!
# By default we put the data and the mask in the same array\ntile, mask = arr[0:-1], arr[-1]\n# By default we put the data and the mask in the same array tile, mask = arr[0:-1], arr[-1] In\u00a0[\u00a0]: Copied!
print(tile.shape)\nprint(tile.shape) In\u00a0[\u00a0]: Copied!
print(mask.shape)\nprint(mask.shape) In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_NumpyTile/#numpytile","title":"NumpyTile\u00b6","text":"
Specification: https://github.com/planetlabs/numpytiles-spec
"},{"location":"examples/notebooks/Working_with_NumpyTile/#requirements","title":"Requirements\u00b6","text":"!pip install numpy mercantile
# Uncomment this line if you need to install the dependencies\n# !pip install rasterio folium httpx tqdm\n# Uncomment this line if you need to install the dependencies # !pip install 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\n# from tqdm.notebook import tqdm\n\nfrom rasterio.plot import reshape_as_image\nfrom rasterio.features import bounds as featureBounds\n\nfrom folium import Map, TileLayer, GeoJson\n\n%pylab inline\nimport os import json import base64 import httpx import datetime import itertools import urllib.parse from io import BytesIO from functools import partial from concurrent import futures # from tqdm.notebook import tqdm from rasterio.plot import reshape_as_image from rasterio.features import bounds as featureBounds from folium import Map, TileLayer, GeoJson %pylab inline In\u00a0[\u00a0]: Copied!
# Endpoint variables\ntitiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\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 [30.810813903808594, 29.454247067148533],\n [30.88600158691406, 29.454247067148533],\n [30.88600158691406, 29.51879923863822],\n [30.810813903808594, 29.51879923863822],\n [30.810813903808594, 29.454247067148533],\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 start = datetime.datetime.strptime(\"2019-01-01\", \"%Y-%m-%d\").strftime(\n \"%Y-%m-%dT00:00:00Z\"\n)\nend = datetime.datetime.strptime(\"2019-12-11\", \"%Y-%m-%d\").strftime(\n \"%Y-%m-%dT23:59:59Z\"\n)\n\n# POST body\nquery = {\n \"collections\": [\"sentinel-s2-l2a-cogs\"],\n \"datetime\": f\"{start}/{end}\",\n \"query\": {\n \"eo:cloud_cover\": {\"lt\": 5},\n },\n \"intersects\": geojson[\"features\"][0][\"geometry\"],\n \"limit\": 100,\n \"fields\": {\n \"include\": [\n \"id\",\n \"properties.datetime\",\n \"properties.eo:cloud_cover\",\n ], # 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,\n \"dashArray\": \"1\",\n \"fillOpacity\": 0,\n \"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(\n dates,\n cloudcover,\n label=\"Cloud Cover\",\n color=\"tab:red\",\n linewidth=0.4,\n linestyle=\"-.\",\n)\n\nax.legend()\nfig = plt.figure(dpi=100) fig.autofmt_xdate() ax = fig.add_subplot(1, 1, 1) ax.plot( dates, cloudcover, label=\"Cloud Cover\", color=\"tab:red\", linewidth=0.4, linestyle=\"-.\", ) ax.legend() In\u00a0[\u00a0]: Copied!
url_template = \"https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/{id}\"\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 (\n \"assets\",\n \"B04\",\n ), # red, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\n \"assets\",\n \"B03\",\n ), # green, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\n \"assets\",\n \"B02\",\n ), # blue, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\n \"color_formula\",\n \"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\",\n ), # 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), 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 (\n \"assets\",\n \"B08\",\n ), # nir, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\n \"assets\",\n \"B04\",\n ), # red, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\n \"assets\",\n \"B03\",\n ), # green, in next STAC item version (see https://github.com/cogeotiff/rio-tiler-pds/issues/63)\n (\n \"color_formula\",\n \"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\",\n ), # 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), 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 (\"assets\", \"B08\"),\n (\"assets\", \"B04\"),\n (\n \"expression\",\n \"(b1-b2)/(b1+b2)\",\n ), # NDVI (nir-red)/(nir+red), in next STAC item version (see\n (\"rescale\", \"-1,1\"),\n (\"colormap_name\", \"viridis\"),\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), 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])), (\"assets\", \"B08\"), (\"assets\", \"B04\"), ( \"expression\", \"(b1-b2)/(b1+b2)\", ), # NDVI (nir-red)/(nir+red), in next STAC item version (see (\"rescale\", \"-1,1\"), (\"colormap_name\", \"viridis\"), (\"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! def fetch_bbox_array(sceneid, bbox, assets, 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\n for asset in assets:\n params += ((\"assets\", asset),)\n\n if expression:\n params += (\n (\"expression\", expression),\n (\"asset_as_band\", True),\n )\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\n\ndef _filter_futures(tasks):\n for future in tasks:\n try:\n yield future.result()\n except Exception:\n pass\n\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, 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)) for asset in assets: params += ((\"assets\", asset),) if expression: params += ( (\"expression\", expression), (\"asset_as_band\", True), ) 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(\n sceneid[0], bounds, assets=[\"B02\"], width=128, height=128\n)\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=[\n \"B04\",\n \"B03\",\n \"B02\",\n ], # (\"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 = [executor.submit(bbox_worker, scene) for scene in sceneid]\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 assets=[\n \"B08\",\n \"B04\",\n ],\n expression=\"(b1-b2)/(b1+b2)\", # (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 = [executor.submit(bbox_worker, scene) for scene in sceneid]\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_ndvi) + 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, assets=[ \"B08\", \"B04\", ], expression=\"(b1-b2)/(b1+b2)\", # (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_ndvi) + 1): fig.add_subplot(row, col, i) plt.imshow(results_ndvi[i - 1][1][0]) In\u00a0[\u00a0]: Copied!
stats = [_stats(data, mask) for _, data, mask in results_ndvi]\n\nfig, ax1 = plt.subplots(dpi=150)\nfig.autofmt_xdate()\n\nax1.plot(dates, [s[0] for s in stats], label=\"Min\")\nax1.plot(dates, [s[1] for s in stats], label=\"Max\")\nax1.plot(dates, [s[2] for s in stats], label=\"Mean\")\nax1.set_xlabel(\"Dates\")\nax1.set_ylabel(\"Normalized Difference Vegetation Index\")\n\nax1.legend()\nstats = [_stats(data, mask) for _, data, mask in results_ndvi] fig, ax1 = plt.subplots(dpi=150) fig.autofmt_xdate() ax1.plot(dates, [s[0] for s in stats], label=\"Min\") ax1.plot(dates, [s[1] for s in stats], label=\"Max\") ax1.plot(dates, [s[2] for s in stats], label=\"Mean\") ax1.set_xlabel(\"Dates\") ax1.set_ylabel(\"Normalized Difference Vegetation Index\") ax1.legend() In\u00a0[\u00a0]: Copied!
\n"},{"location":"examples/notebooks/Working_with_STAC/#working-with-stac-at-scale","title":"Working With STAC - At Scale\u00b6","text":""},{"location":"examples/notebooks/Working_with_STAC/#stac-spatiotemporal-asset-catalog","title":"STAC: SpatioTemporal Asset Catalog\u00b6","text":"
The SpatioTemporal Asset Catalog (STAC) specification aims to standardize the way geospatial assets are exposed online and queried. A 'spatiotemporal asset' is any file that represents information about the earth captured in a certain space and time. The initial focus is primarily remotely-sensed imagery (from satellites, but also planes, drones, balloons, etc), but the core is designed to be extensible to SAR, full motion video, point clouds, hyperspectral, LiDAR and derived data like NDVI, Digital Elevation Models, mosaics, etc.
Ref: https://github.com/radiantearth/stac-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/src/endpoints/stac.md
TiTiler was first designed to work with single COG by passing the file URL to the tiler. e.g : https://myendpoint/cog/tiles/1/2/3?url=https://somewhere.com/mycog.tif
With STAC is a bit different because we first have to read the STAC items and then know which assets to read.
Example of STAC Item
{\n \"type\": \"Feature\",\n \"id\": \"S2A_34SGA_20200318_0_L2A\",\n\n \"geometry\": {...},\n \"properties\": {\n \"datetime\": \"2020-03-18T09:11:33Z\",\n ...\n },\n \"collection\": \"sentinel-s2-l2a-cogs\",\n \"assets\": {\n \"thumbnail\": {\n \"title\": \"Thumbnail\",\n \"type\": \"image/png\",\n \"href\": \"https://myurl.com/preview.jpg\"\n },\n ...\n \"B03\": {\n \"title\": \"Band 3 (green)\",\n \"type\": \"image/tiff; application=geotiff; profile=cloud-optimized\",\n \"href\": \"https://myurl.com/B03.tif\",\n \"proj:shape\": [\n 10980,\n 10980\n ],\n \"proj:transform\": [\n 10,\n 0,\n 699960,\n 0,\n -10,\n 3600000,\n 0,-*\n 0,\n 1\n ]\n },\n ...\n },\n \"links\": [...]\n}\n To be able to create Web Map tile from the B03 asset you'll need to pass the STAC Item url and the asset name:
https://myendpoint/stac/tiles/1/2/3?url=https://somewhere.com/item.json&assets=B03
To be able to run this notebook you'll need the following requirements:
!pip install rasterio folium httpx tqdm
See https://github.com/radiantearth/stac-api-spec for more documentation about the stac API
You can use geojson.io to define your search AOI
"},{"location":"examples/notebooks/Working_with_STAC/#use-titiler-endpoint","title":"Use Titiler endpoint\u00b6","text":"https://titiler.xyz/api.html#/SpatioTemporal%20Asset%20Catalog
{endpoint}/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}?url={stac_item}&{otherquery params}
{endpoint}/stac/bbox/{minx},{miny},{maxx},{maxy}.{format}?url={stac_item}&{otherquery params}
{endpoint}/stac/point/{minx},{miny}?url={stac_item}&{otherquery params}
titiler doesn't return only png or jpeg but can also return Numpy array directly
"},{"location":"examples/notebooks/Working_with_STAC_simple/","title":"Working With STAC","text":"In\u00a0[\u00a0]: Copied!# Uncomment this line if you need to install the dependencies\n# !pip install folium requests rasterio\n# Uncomment this line if you need to install the dependencies # !pip install folium requests rasterio In\u00a0[\u00a0]: Copied!
import httpx\n\nfrom rasterio.features import bounds as featureBounds\n\nfrom folium import Map, TileLayer, GeoJson\n\n%pylab inline\nimport httpx from rasterio.features import bounds as featureBounds from folium import Map, TileLayer, GeoJson %pylab inline In\u00a0[\u00a0]: Copied!
titiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\nstac_item = \"https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a/items/S2A_30TVT_20221112_0_L2A\"\ntitiler_endpoint = ( \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. ) stac_item = \"https://earth-search.aws.element84.com/v1/collections/sentinel-2-l2a/items/S2A_30TVT_20221112_0_L2A\" In\u00a0[\u00a0]: Copied!
item = httpx.get(stac_item).json()\nprint(item)\nitem = httpx.get(stac_item).json() print(item) In\u00a0[\u00a0]: Copied!
for it, asset in item[\"assets\"].items():\n print(\"Name:\", it, \"| Format:\", asset[\"type\"])\nfor it, asset in item[\"assets\"].items(): print(\"Name:\", it, \"| Format:\", asset[\"type\"]) In\u00a0[\u00a0]: Copied!
bounds = featureBounds(item)\n\nm = Map(\n tiles=\"OpenStreetMap\",\n location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2),\n zoom_start=8,\n)\n\ngeo_json = GeoJson(data=item)\ngeo_json.add_to(m)\nm\nbounds = featureBounds(item) m = Map( tiles=\"OpenStreetMap\", location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2), zoom_start=8, ) geo_json = GeoJson(data=item) geo_json.add_to(m) m In\u00a0[\u00a0]: Copied!
# Get Tile URL\nr = httpx.get(\n f\"{titiler_endpoint}/stac/info\",\n params=(\n (\"url\", stac_item),\n # Get info for multiple assets\n (\"assets\", \"visual\"),\n (\"assets\", \"red\"),\n (\"assets\", \"blue\"),\n (\"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), 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|indexes=1,2,3\",\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), 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|indexes=1,2,3\", \"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), 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 (\"assets\", \"nir\"),\n (\"assets\", \"red\"),\n (\"expression\", \"(b1-b2)/(b1+b2)\"), # 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), 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), (\"assets\", \"nir\"), (\"assets\", \"red\"), (\"expression\", \"(b1-b2)/(b1+b2)\"), # 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-spec
Using STAC makes data indexation and discovery really easy. In addition to the Collection/Item/Asset (data) specifications, data providers are also encouraged to follow a STAC API specification: https://github.com/radiantearth/stac-api-spec
The API is compliant with the OGC API - Features standard (formerly known as OGC Web Feature Service 3), in that it defines many of the endpoints that STAC uses. A STAC API should be compatible and usable with any OGC API - Features clients. The STAC API can be thought of as a specialized Features API to search STAC Catalogs, where the features returned are STAC Items, that have common properties, links to their assets and geometries that represent the footprints of the geospatial assets.
"},{"location":"examples/notebooks/Working_with_STAC_simple/#requirements","title":"Requirements\u00b6","text":"To be able to run this notebook you'll need the following requirements:
!pip install folium httpx rasterio
# setup\nimport httpx\nimport json\n\ntitiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\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[\u00a0]: 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)) In\u00a0[\u00a0]: 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)) This endpoint is far more configurable than /info. You can specify which bands to analyse, how to generate the histogram, and pre-process the image.
For example, if you wanted to get the statistics of the VARI of the image you can use the expression parameter to conduct simple band math:
r = httpx.get(\n f\"{titiler_endpoint}/cog/statistics\",\n params={\n \"url\": cog_url,\n \"expression\": \"(b2-b1)/(b1+b2-b3)\", # expression for the VARI\n \"histogram_range\": \"-1,1\",\n },\n).json()\n\nprint(json.dumps(r))\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)) Alternatively, if you would like to get statistics for only a certain area, you can specify an area via a feature or a feature collection.
(Note: this endpoint is not available in the mosaicjson endpoint, only /cog and /stac)
mahebourg = \"\"\"\n{\n \"type\": \"FeatureCollection\",\n \"features\": [\n {\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"coordinates\": [\n [\n [\n 57.70358910197049,\n -20.384114558699935\n ],\n [\n 57.68564920588395,\n -20.384114558699935\n ],\n [\n 57.68209507552771,\n -20.39855066753664\n ],\n [\n 57.68666467170024,\n -20.421074640746554\n ],\n [\n 57.70341985766697,\n -20.434397129770545\n ],\n [\n 57.72999121319131,\n -20.42392955694521\n ],\n [\n 57.70358910197049,\n -20.384114558699935\n ]\n ]\n ],\n \"type\": \"Polygon\"\n }\n }\n ]\n}\n\"\"\"\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[\u00a0]: 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 \"max_size\": 1024,\n },\n timeout=20,\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, \"max_size\": 1024, }, timeout=20, ).json() print(json.dumps(r)) 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 - /info and /statistics.
(Note: these examples will be using the /cog endpoint, but everything is also available for /stac and /mosaicjson unless otherwise noted)
The /info endpoint returns general metadata about the image/asset.
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_Zarr/","title":"Working with Zarr","text":"In\u00a0[\u00a0]: Copied!# setup\nimport httpx\nimport json\nfrom IPython.display import Image\n\n# Developmentseed Demo endpoint. Please be kind. Ref: https://github.com/developmentseed/titiler/discussions/1223\n# titiler_endpoint = \"https://xarray.titiler.xyz\"\n\n# Or launch your own local instance with:\n# uv run --group server uvicorn titiler.xarray.main:app --host 127.0.0.1 --port 8080 --reload\ntitiler_endpoint = \"http://127.0.0.1:8080\"\n\nzarr_url = \"https://nasa-power.s3.us-west-2.amazonaws.com/syn1deg/temporal/power_syn1deg_monthly_temporal_lst.zarr\"\n# setup import httpx import json from IPython.display import Image # Developmentseed Demo endpoint. Please be kind. Ref: https://github.com/developmentseed/titiler/discussions/1223 # titiler_endpoint = \"https://xarray.titiler.xyz\" # Or launch your own local instance with: # uv run --group server uvicorn titiler.xarray.main:app --host 127.0.0.1 --port 8080 --reload titiler_endpoint = \"http://127.0.0.1:8080\" zarr_url = \"https://nasa-power.s3.us-west-2.amazonaws.com/syn1deg/temporal/power_syn1deg_monthly_temporal_lst.zarr\" In\u00a0[\u00a0]: Copied!
r = httpx.get(\n f\"{titiler_endpoint}/dataset/dict\",\n params={\n \"url\": zarr_url,\n },\n).json()\n\nprint(json.dumps(r, indent=2))\n r = httpx.get( f\"{titiler_endpoint}/dataset/dict\", params={ \"url\": zarr_url, }, ).json() print(json.dumps(r, indent=2)) In\u00a0[\u00a0]: Copied! r = httpx.get(\n f\"{titiler_endpoint}/dataset/keys\",\n params={\n \"url\": zarr_url,\n },\n).json()\n\nprint(json.dumps(r, indent=2))\n r = httpx.get( f\"{titiler_endpoint}/dataset/keys\", params={ \"url\": zarr_url, }, ).json() print(json.dumps(r, indent=2)) In\u00a0[\u00a0]: Copied! r = httpx.get(\n f\"{titiler_endpoint}/info\",\n params={\"url\": zarr_url, \"variable\": \"AIRMASS\"},\n).json()\n\nprint(json.dumps(r, indent=2))\n r = httpx.get( f\"{titiler_endpoint}/info\", params={\"url\": zarr_url, \"variable\": \"AIRMASS\"}, ).json() print(json.dumps(r, indent=2)) Or as a GeoJSON feature
In\u00a0[\u00a0]: Copied!r = httpx.get(\n f\"{titiler_endpoint}/info.geojson\",\n params={\"url\": zarr_url, \"variable\": \"AIRMASS\"},\n).json()\n\nprint(json.dumps(r, indent=2))\n r = httpx.get( f\"{titiler_endpoint}/info.geojson\", params={\"url\": zarr_url, \"variable\": \"AIRMASS\"}, ).json() print(json.dumps(r, indent=2)) In\u00a0[\u00a0]: Copied! r = httpx.get(\n f\"{titiler_endpoint}/bbox/-180,-90,180,90.png\",\n params=(\n (\"url\", zarr_url),\n (\"variable\", \"AIRMASS\"),\n # Select 1 specific band\n (\"bidx\", 50),\n (\"rescale\", \"1,20\"),\n (\"colormap_name\", \"viridis\"),\n ),\n timeout=10,\n)\n\nImage(r.content)\n r = httpx.get( f\"{titiler_endpoint}/bbox/-180,-90,180,90.png\", params=( (\"url\", zarr_url), (\"variable\", \"AIRMASS\"), # Select 1 specific band (\"bidx\", 50), (\"rescale\", \"1,20\"), (\"colormap_name\", \"viridis\"), ), timeout=10, ) Image(r.content) In\u00a0[\u00a0]: Copied! r = httpx.get(\n f\"{titiler_endpoint}/bbox/-180,-90,180,90.png\",\n params=(\n (\"url\", zarr_url),\n (\"variable\", \"AIRMASS\"),\n # Select 1 specific time slices\n (\"sel\", \"time=2003-06-30\"),\n (\"rescale\", \"1,20\"),\n (\"colormap_name\", \"viridis\"),\n ),\n timeout=10,\n)\n\nImage(r.content)\n r = httpx.get( f\"{titiler_endpoint}/bbox/-180,-90,180,90.png\", params=( (\"url\", zarr_url), (\"variable\", \"AIRMASS\"), # Select 1 specific time slices (\"sel\", \"time=2003-06-30\"), (\"rescale\", \"1,20\"), (\"colormap_name\", \"viridis\"), ), timeout=10, ) Image(r.content) In\u00a0[\u00a0]: Copied! r = httpx.get(\n f\"{titiler_endpoint}/bbox/-180,-90,180,90.png\",\n params=(\n (\"url\", zarr_url),\n (\"variable\", \"AIRMASS\"),\n # Select 3 specific time slices to create a 3 band image\n (\"sel\", \"time=2003-06-30\"),\n (\"sel\", \"time=2004-06-30\"),\n (\"sel\", \"time=2005-06-30\"),\n (\"rescale\", \"1,10\"),\n ),\n timeout=10,\n)\n\nImage(r.content)\n r = httpx.get( f\"{titiler_endpoint}/bbox/-180,-90,180,90.png\", params=( (\"url\", zarr_url), (\"variable\", \"AIRMASS\"), # Select 3 specific time slices to create a 3 band image (\"sel\", \"time=2003-06-30\"), (\"sel\", \"time=2004-06-30\"), (\"sel\", \"time=2005-06-30\"), (\"rescale\", \"1,10\"), ), timeout=10, ) Image(r.content)"},{"location":"examples/notebooks/Working_with_Zarr/#working-with-zarr","title":"Working with Zarr\u00b6","text":""},{"location":"examples/notebooks/Working_with_Zarr/#intro","title":"Intro\u00b6","text":"titiler.xarray is a submodule designed specifically for working with multidimensional dataset. With version 0.25.0, we've introduced a default application with only support for Zarr dataset.
The /dataset/dict endpoint returns general metadata about the Zarr Dataset
Endpoint: /dataset/dict
QueryParams:
Endpoint: /dataset/keys
QueryParams:
We can use /info endpoint to get more Geo information about a specific variable.
QueryParams:
AIRMASS, found in /dataset/keys response)Looking at the info response we can see that the AIRMASS variable has 348 (count) bands, each one corresponding to as specific TIME (day).
We can also see that the data is stored as float32 which mean that we will have to apply linear rescaling in order to get output image as PNG/JPEG.
The min/max values are also indicated with valid_max=31.73 and valid_min=1.0.
We cannot visualize all the bands at once, so we need to perform dimension reduction to go from array in shape (348, 360, 180) to a 1b (1, 360, 180) or 3b (3, 360, 180) image.
To do it, we have two methods whitin titiler.xarray:
bidx=: same as for COG we can select a band indexsel={dimension}=value: which will be using xarray .sel methodThis Notebook shows how to use and display tiles with non-webmercator TileMatrixSet
In\u00a0[\u00a0]: Copied!# Uncomment if you need to install those module within the notebook\n# !pip install ipyleaflet httpx\n# Uncomment if you need to install those module within the notebook # !pip install ipyleaflet httpx In\u00a0[\u00a0]: Copied!
import json\n\nimport httpx\n\nfrom ipyleaflet import (\n Map,\n basemaps,\n basemap_to_tiles,\n TileLayer,\n WMSLayer,\n GeoJSON,\n projections,\n)\nimport json import httpx from ipyleaflet import ( Map, basemaps, basemap_to_tiles, TileLayer, WMSLayer, GeoJSON, projections, ) In\u00a0[\u00a0]: Copied!
titiler_endpoint = (\n \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind.\n)\nurl = \"https://s3.amazonaws.com/opendata.remotepixel.ca/cogs/natural_earth/world.tif\" # Natural Earth WORLD tif\ntitiler_endpoint = ( \"https://titiler.xyz\" # Developmentseed Demo endpoint. Please be kind. ) url = \"https://s3.amazonaws.com/opendata.remotepixel.ca/cogs/natural_earth/world.tif\" # Natural Earth WORLD tif In\u00a0[\u00a0]: Copied!
r = httpx.get(f\"{titiler_endpoint}/tileMatrixSets\").json()\n\nprint(\"Supported TMS:\")\nfor tms in r[\"tileMatrixSets\"]:\n print(\"-\", tms[\"id\"])\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\": [8192.0, 4096.0, 2048.0, 1024.0, 512.0, 256.0],\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":"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
"},{"location":"migrations/v1_migration/","title":"Migration Guide: TiTiler 0.26 to 1.0","text":"This guide covers the breaking changes and new features when upgrading from TiTiler 0.26 to 1.0.
"},{"location":"migrations/v1_migration/#breaking-changes","title":"Breaking Changes","text":""},{"location":"migrations/v1_migration/#data-type-changes","title":"Data Type Changes","text":""},{"location":"migrations/v1_migration/#uint8-output-for-jpegpng","title":"UINT8 Output for JPEG/PNG","text":"Impact: High - Affects all automatic image format outputs
When no output format is explicitly specified, TiTiler now returns UINT8 datatype for JPEG and PNG formats.
# If your data needs specific datatypes, explicitly specify the format\n# Example: Request with explicit format control\n# In this case, if the input data is in uint16, the ouput png will be in UINT16\nresponse = requests.get(\"/tiles/1/2/3.png?url=data_in_uint16.tif\")\n Action Required: Review your endpoints that rely on automatic format detection. If you need specific data type handling, consider explicitly specifying the output format and rescaling parameters.
"},{"location":"migrations/v1_migration/#wmts-changes","title":"WMTS Changes","text":""},{"location":"migrations/v1_migration/#wmts-endpoint-restructuring","title":"WMTS Endpoint Restructuring","text":"Impact: High - Affects all WMTS usage
The /{tileMatrixSetId}/WMTSCapabilities.xml endpoints have been removed from the default factories. WMTS functionality is now provided through a dedicated extension.
# Before (0.26)\n# WMTS available at: /{tileMatrixSetId}/WMTSCapabilities.xml\n\n# Now (1.0)\nfrom titiler.extensions import wmtsExtension\n\n# Add extension to factory\nfactory = TilerFactory(\n router_prefix=\"/cog\",\n extensions=[wmtsExtension()]\n)\n\n# WMTS now available at: /WMTSCapabilities.xml\n Additionally, the WMTS response now supports all TileMatrixSets as separate layers.
Action Required: 1. Add wmtsExtension to your factory configurations 2. Update client applications to use the new /WMTSCapabilities.xml endpoint path 3. Update any code that expects a single layer to handle multiple TileMatrixSets
Impact: Medium - Affects /point endpoint consumers
The /point endpoint now includes a band_description attribute in its response model.
# Before (0.26)\nclass Point(BaseModel):\n coordinates: List[float]\n values: List[Optional[float]]\n # ... other fields\n\n# Now (1.0)\nclass Point(BaseModel):\n coordinates: List[float]\n values: List[Optional[float]]\n band_description: List[str] | None # New field\n # ... other fields\n Action Required: Update client code that parses /point responses to handle the new band_description field.
Impact: High - Affects mosaic /point endpoint consumers
The response model for the mosaic /point endpoint has been completely restructured for better clarity.
# Before (0.26)\nclass Point(BaseModel):\n coordinates: List[float]\n values: List[Tuple[str, List[Optional[float]], List[str]]]\n\n# Now (1.0)\nclass AssetPoint(BaseModel):\n name: str\n values: list[float | None]\n band_names: list[str]\n band_descriptions: list[str] | None = None\n\nclass Point(BaseModel):\n coordinates: list[float]\n assets: list[AssetPoint]\n Migration Example:
# Before (0.26)\nresponse = {\n \"coordinates\": [-122.5, 37.5],\n \"values\": [\n (\"asset1\", [100.0, 200.0], [\"B1\", \"B2\"]),\n (\"asset2\", [150.0, 250.0], [\"B1\", \"B2\"])\n ]\n}\n\n# Now (1.0)\nresponse = {\n \"coordinates\": [-122.5, 37.5],\n \"assets\": [\n {\n \"name\": \"asset1\",\n \"values\": [100.0, 200.0],\n \"band_names\": [\"B1\", \"B2\"],\n \"band_descriptions\": None\n },\n {\n \"name\": \"asset2\",\n \"values\": [150.0, 250.0],\n \"band_names\": [\"B1\", \"B2\"],\n \"band_descriptions\": None\n }\n ]\n}\n Action Required: Update all client code that parses mosaic /point responses to use the new structure.
Impact: Medium - Affects installations and imports
The cogeo-mosaic package is now an optional dependency.
# Before (0.26)\npip install titiler.mosaic\n\n# Now (1.0) - Install with MosaicJSON support\npip install \"titiler.mosaic[mosaicjson]\"\n Action Required: Update your installation commands if you use MosaicJSON functionality.
"},{"location":"migrations/v1_migration/#backend-attribute-no-longer-has-default","title":"Backend Attribute No Longer Has Default","text":"Impact: High - Affects custom mosaic implementations
The MosaicTilerFactory.backend attribute no longer has a default value and must be explicitly specified.
# Before (0.26)\n# Backend had a default value\nfactory = MosaicTilerFactory()\n\n# Now (1.0)\n# Must explicitly set backend\nfrom titiler.mosaic.backends import MosaicBackend\n\nfactory = MosaicTilerFactory(\n backend=MosaicBackend\n)\n Action Required: Explicitly set the backend attribute when creating MosaicTilerFactory instances.
Impact: Medium - Affects mosaic endpoint structure
The / and /validate endpoints are now provided by the MosaicJSONExtension instead of being included by default.
# Before (0.26)\n# Endpoints available by default\nfactory = MosaicTilerFactory()\n\n# Now (1.0)\nfrom titiler.mosaic.extensions import MosaicJSONExtension\n\nfactory = MosaicTilerFactory(\n extensions=[MosaicJSONExtension()]\n)\n Action Required: Add MosaicJSONExtension to your factory if you need the / and /validate endpoints.
Impact: Low - Dependency version change
The rio-cogeo requirement has been updated to 7.0,<8.0.
Action Required: Review the rio-cogeo changelog for any breaking changes that might affect your usage.
"},{"location":"migrations/v1_migration/#new-features","title":"New Features","text":""},{"location":"migrations/v1_migration/#titilermosaic","title":"titiler.mosaic","text":""},{"location":"migrations/v1_migration/#new-optional-endpoints","title":"New Optional Endpoints","text":"Three new optional endpoints are available for mosaic operations:
/feature - Feature-based queries/bbox - Bounding box queries/statistics - Statistical analysis# Enable in your factory\nfactory = MosaicTilerFactory(\n add_feature=True, # Enables /feature endpoint\n add_bbox=True, # Enables /bbox endpoint\n add_statistics=True # Enables /statistics endpoint\n)\n"},{"location":"migrations/v1_migration/#wmts-extension-for-mosaics","title":"WMTS Extension for Mosaics","text":"A dedicated WMTS extension is now available for mosaic factories:
from titiler.mosaic.extensions import wmtsExtension\n\nfactory = MosaicTilerFactory(\n extensions=[wmtsExtension()]\n)\n"},{"location":"migrations/v1_migration/#ogc-maps-api-support","title":"OGC Maps API Support","text":"Optional OGC Maps API /map endpoint is now available:
factory = MosaicTilerFactory(\n add_map=True # Enables /map endpoint\n)\n"},{"location":"migrations/v1_migration/#dependency-updates","title":"Dependency Updates","text":""},{"location":"migrations/v1_migration/#core-dependencies","title":"Core Dependencies","text":"rio-tiler: Updated to >=8.0,<9.0rio-cogeo: Updated to 7.0,<8.0Action Required: Test your application with the new versions and review their respective changelogs for any behavioral changes.
"},{"location":"migrations/v1_migration/#migration-checklist","title":"Migration Checklist","text":"Use this checklist to ensure a smooth migration:
wmtsExtension/point responses (both core and mosaic)backend attribute for MosaicTilerFactoryMosaicJSONExtension if using MosaicJSON endpoints[mosaicjson] extra if neededrio-tiler and rio-cogeo/feature, /bbox, /statistics)If you encounter issues during migration:
Note
This migration guide was generated with the help of Claude AI
This document describes the breaking changes and migration steps required when upgrading from TiTiler 1.1.1 to 2.0.0.
"},{"location":"migrations/v2_migration/#overview","title":"Overview","text":"TiTiler 2.0.0 introduces several breaking changes primarily focused on:
tile_scale with direct tilesize parameterbidx) option for multi-asset readersMultiBandTilerFactory and related classestile_scale and @{scale}x suffix","text":"The tile_scale parameter and @{scale}x URL suffix have been removed from all endpoints.
Before (1.x):
GET /tiles/WebMercatorQuad/10/512/384@2x.png?url=...\nGET /tilejson.json?url=...&tile_scale=2\nGET /map.html?url=...&tile_scale=2\nGET /WMTSCapabilities.xml?url=...&tile_scale=2\n After (2.0):
GET /tiles/WebMercatorQuad/10/512/384.png?url=...&tilesize=512\nGET /tilejson.json?url=...&tilesize=512\nGET /map.html?url=...&tilesize=256\nGET /WMTSCapabilities.xml?url=... # can't overwrite tilesize anymore\n"},{"location":"migrations/v2_migration/#new-tilesize-query-parameter","title":"New: tilesize query parameter","text":"A new tilesize query parameter is available for tile and tilejson endpoints:
/tiles/{tms}/{z}/{x}/{y} 256x256 (scale=1) TileMatrix's tileHeight x tileWidth /tilejson.json 256 512 /map.html 256 256 Migration Steps: 1. Replace @2x suffix with tilesize=512 query parameter 2. Replace @4x suffix with tilesize=1024 query parameter 3. Replace tile_scale=N with tilesize=N*256 query parameter 4. Update any client code that constructs tile URLs with scale suffix
Several dependency classes have been renamed to reflect their simplified functionality.
"},{"location":"migrations/v2_migration/#renamed-classes","title":"Renamed Classes","text":"Old Name (1.x) New Name (2.0)AssetsBidxExprParams AssetsExprParams Migration Steps:
# Before (1.x)\nfrom titiler.core.dependencies import AssetsBidxExprParams, AssetsBidxExprParamsOptional\n\n# After (2.0)\nfrom titiler.core.dependencies import AssetsExprParams\n"},{"location":"migrations/v2_migration/#removed-classes","title":"Removed Classes","text":"The following classes have been removed entirely:
titiler.core.dependencies.AssetsBidxParamstitiler.core.dependencies.BandsParamstitiler.core.dependencies.BandsExprParamsOptionaltitiler.core.dependencies.BandsExprParamsMigration Steps:
If you were using BandsParams or BandsExprParams with a custom MultiBandReader: - Consider migrating to a MultiBaseReader approach with assets - Or use expressions to select specific bands
The following functions have been removed:
titiler.core.dependencies.parse_asset_indexes()titiler.core.dependencies.parse_asset_expression()These were used internally for parsing asset_indexes and asset_expression query parameters.
MultiBandTilerFactory","text":"The MultiBandTilerFactory class has been completely removed because MultiBandReader is no longer available in rio-tiler 9.0.
Before (1.x):
from titiler.core.factory import MultiBandTilerFactory\nfrom rio_tiler.io import MultiBandReader\n\nclass MyBandReader(MultiBandReader):\n ...\n\ntiler = MultiBandTilerFactory(reader=MyBandReader)\n Migration Steps:
MultiBandReader to a MultiBaseReader that exposes bands as assetsMultiBaseTilerFactory instead# After (2.0)\nfrom titiler.core.factory import MultiBaseTilerFactory\nfrom rio_tiler.io import MultiBaseReader\n\nclass MyAssetReader(MultiBaseReader):\n # Expose bands as assets\n ...\n\ntiler = MultiBaseTilerFactory(reader=MyAssetReader)\n"},{"location":"migrations/v2_migration/#5-assets-parameter-is-mandatory-for-multibasereader","title":"5. assets parameter is mandatory for MultiBaseReader","text":""},{"location":"migrations/v2_migration/#info-and-statistics","title":"Info and statistics","text":"/info and /statistics endpoints now require assets= parameter to be set. Users can use special notation assets=:all: to get info/statistics for all assets.
Before (1.x):
GET /stac/info?url=...\n After (2.0):
GET /stac/info?url=...&assets=:all:\n"},{"location":"migrations/v2_migration/#expression","title":"Expression","text":"The expression parameter cannot be used to define assets and thus assets parameter must be provided.
Before (1.x):
GET /stac/tiles/WebMercatorQuad/10/512/384.png?url=...&expression=B02_b1+B03_b1\n After (2.0):
GET /stac/tiles/WebMercatorQuad/10/512/384.png?url=...&assets=B02&assets=B03&expression=b1+b2\n"},{"location":"migrations/v2_migration/#6-bidx-parameter-ignored-for-multibasereader","title":"6. bidx Parameter Ignored for MultiBaseReader","text":"The bidx (band index) parameter is now ignored by MultiBaseTilerFactory endpoints. Previously, you could use bidx to select bands across all assets.
Before (1.x):
GET /stac/tiles/WebMercatorQuad/10/512/384.png?url=...&assets=B02&assets=B03&bidx=1\n After (2.0):
Use new asset notation: assets={AssetName}|indexes=1,2,3
GET /stac/tiles/WebMercatorQuad/10/512/384.png?url=...&assets=B02|indexes=1&assets=B03|indexes=1\n"},{"location":"migrations/v2_migration/#7-removed-asset_indexes-and-asset_expression-options","title":"7. Removed: asset_indexes and asset_expression Options","text":"The asset_indexes and asset_expression query parameters have been removed from dependencies.py.
Before (1.x):
GET /stac/preview.png?url=...&asset_indexes=data|1,2,3\nGET /stac/preview.png?url=...&asset_expression=data|b1*b2\n After (2.0):
Use new asset notation: assets={AssetName}|indexes=1,2,3 or assets={AssetName}|expression=b1*2
GET /stac/preview.png?url=...&assets=B01|indexes=1,2,3\nGET /stac/preview.png?url=...&assets=B01|expression=b1*2\n"},{"location":"migrations/v2_migration/#8-extension-changes","title":"8. Extension Changes","text":""},{"location":"migrations/v2_migration/#cog-and-stac-viewers","title":"COG and STAC Viewers","text":"The cogViewerExtension and stacViewerExtension now force tilesize=256 in their tile requests.
The tile_scale parameter has been removed from /WMTSCapabilities.xml endpoints in both titiler.extensions and titiler.mosaic.
/tiles/{tms}/{z}/{x}/{y}@{scale}x Removed - Use tilesize query param /tiles/{tms}/{z}/{x}/{y}@{scale}x.{format} Removed - Use tilesize query param /tilejson.json?tile_scale=N Removed - Use tilesize=N*256 /map.html?tile_scale=N Removed - Use tilesize=N*256 /WMTSCapabilities.xml?tile_scale=N Removed"},{"location":"migrations/v2_migration/#code-migration-checklist","title":"Code Migration Checklist","text":"@{scale}x tile URL suffixes with tilesize parametertile_scale query parameter with tilesizeAssetsBidxExprParams \u2192 AssetsExprParamsAssetsBidxParams, BandsParams, BandsExprParams, BandsExprParamsOptionalMultiBandTilerFactory usage to MultiBaseTilerFactorybidx parameter from multi-asset requests (use expression instead)asset_indexes and asset_expression parametersassets=:all: for /info and /statistics endpoints for MultiBaseTilerFactoryTiTiler 2.0 requires: - rio-tiler >= 9.0
Make sure to update your pyproject.toml accordingly.
If you encounter issues during migration: - Check the CHANGES.md for detailed release notes - Open an issue at https://github.com/developmentseed/titiler/issues
"},{"location":"packages/application/","title":"titiler.application","text":""},{"location":"packages/application/#titilerapplication","title":"titiler.application","text":"Titiler's demo application.
"},{"location":"packages/application/#installation","title":"Installation","text":"python -m pip install -U pip\n\n# From Pypi\npython -m pip install titiler.application\n\n# Or from sources\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions -e src/titiler/mosaic -e src/titiler/xarray -e src/titiler/application\n Launch Application
$ python -m pip install uvicorn\n$ uvicorn titiler.application.main:app --reload\n"},{"location":"packages/application/#package-structure","title":"Package structure","text":"titiler/\n \u2514\u2500\u2500 application/\n \u251c\u2500\u2500 tests/ - Tests suite\n \u2514\u2500\u2500 titiler/application/ - `application` namespace package\n \u251c\u2500\u2500 templates/\n | \u2514\u2500\u2500 index.html - Landing page\n \u251c\u2500\u2500 main.py - Main FastAPI application\n \u2514\u2500\u2500 settings.py - demo settings (cache, cors...)\n"},{"location":"packages/core/","title":"titiler.core","text":""},{"location":"packages/core/#titilercore","title":"titiler.core","text":"Core of Titiler's application. Contains blocks to create dynamic tile servers.
"},{"location":"packages/core/#installation","title":"Installation","text":"python -m pip install -U pip\n\n# From Pypi\npython -m pip install titiler.core\n\n# Or from sources\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler && python -m pip install -e src/titiler/core\n"},{"location":"packages/core/#how-to","title":"How To","text":"from fastapi import FastAPI\nfrom titiler.core.factory import TilerFactory\n\n# Create a FastAPI application\napp = FastAPI(\n description=\"A lightweight Cloud Optimized GeoTIFF tile server\",\n)\n\n# Create a set of COG endpoints\ncog = TilerFactory()\n\n# Register the COG endpoints to the application\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n"},{"location":"packages/core/#package-structure","title":"Package structure","text":"titiler/\n \u2514\u2500\u2500 core/\n \u251c\u2500\u2500 tests/ - Tests suite\n \u2514\u2500\u2500 titiler/core/ - `core` namespace package\n \u251c\u2500\u2500 algorithm/\n | \u251c\u2500\u2500 base.py - ABC Base Class for custom algorithms\n | \u251c\u2500\u2500 dem.py - Elevation data related algorithms\n | \u2514\u2500\u2500 index.py - Simple band index algorithms\n \u251c\u2500\u2500 models/\n | \u251c\u2500\u2500 response.py - Titiler's response models\n | \u251c\u2500\u2500 mapbox.py - Mapbox TileJSON pydantic model\n | \u2514\u2500\u2500 OGC.py - Open GeoSpatial Consortium pydantic models (TileMatrixSets...)\n \u251c\u2500\u2500 resources/\n | \u251c\u2500\u2500 enums.py - Titiler's enumerations (e.g MediaType)\n | \u2514\u2500\u2500 responses.py - Custom Starlette's responses\n \u251c\u2500\u2500 templates/\n | \u251c\u2500\u2500 map.html - Simple Map viewer (built with leaflet)\n | \u2514\u2500\u2500 wmts.xml - OGC WMTS document template\n \u251c\u2500\u2500 dependencies.py - Titiler FastAPI's dependencies\n \u251c\u2500\u2500 errors.py - Errors handler factory\n \u251c\u2500\u2500 middleware.py - Starlette middlewares\n \u251c\u2500\u2500 factory.py - Dynamic tiler endpoints factories\n \u251c\u2500\u2500 routing.py - Custom APIRoute class\n \u251c\u2500\u2500 telemetry.py - OpenTelemetry tracing functions\n \u2514\u2500\u2500 utils.py - Titiler utility functions\n"},{"location":"packages/extensions/","title":"titiler.extensions","text":""},{"location":"packages/extensions/#titilerextensions","title":"titiler.extensions","text":"Extent TiTiler Tiler Factories
"},{"location":"packages/extensions/#installation","title":"Installation","text":"python -m pip install -U pip\n\n# From Pypi\npython -m pip install titiler.extensions\n\n# Or from sources\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions\n"},{"location":"packages/extensions/#available-extensions","title":"Available extensions","text":""},{"location":"packages/extensions/#cogvalidateextension","title":"cogValidateExtension","text":"/validate endpoint which return the content of rio-cogeo info methodtitiler.extensions[\"cogeo\"]/viewer endpoint which return an HTML viewer for simple COGs/viewer endpoint which return an HTML viewer for STAC item/wms endpoint to support OGC Web Map Service (GetTile and GetCapabilities) specificationExtensions 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"},{"location":"packages/extensions/#create-your-own","title":"Create your own","text":"from dataclasses import dataclass, field\nfrom typing import Tuple, List, Optional\nimport rasterio\nfrom starlette.responses import Response\nfrom fastapi import Depends, FastAPI, Query\nfrom titiler.core.factory import TilerFactory, FactoryExtension\nfrom titiler.core.dependencies import ImageRenderingParams\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 TilerFactory object as input\n def register(self, factory: TilerFactory):\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 reader_params=Depends(factory.reader_dependency),\n layer_params=Depends(factory.layer_dependency),\n dataset_params=Depends(factory.dataset_dependency),\n post_process=Depends(factory.process_dependency),\n colormap=Depends(factory.colormap_dependency),\n render_params=Depends(factory.render_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 format = ImageType.jpeg if image.mask.all() else ImageType.png\n\n if post_process:\n image = post_process(image)\n\n content, media_type = factory.render_func(\n image,\n colormap=colormap,\n **render_params.as_dict(),\n )\n\n return Response(content, media_type=media_type)\n\n# Use it\napp = FastAPI()\ntiler = TilerFactory(\n extensions=[\n thumbnailExtension(max_size=64)\n ]\n)\napp.include_router(tiler.router)\n"},{"location":"packages/mosaic/","title":"titiler.mosaic","text":""},{"location":"packages/mosaic/#titilermosaic","title":"titiler.mosaic","text":"Adds support for Mosaic in Titiler. Mosaic's backend needs to be built on top of rio-tiler's Mosaic Backend cogeotiff.github.io/rio-tiler/advanced/mosaic_backend/
python -m pip install -U pip\n\n# From Pypi\npython -m pip install titiler.mosaic\n\n# Or from sources\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler && python -m pip install -e src/titiler/core -e src/titiler/mosaic\n\n# install cogeo-mosaic for MosaicJSON support\npython -m pip install cogeo-mosaic\n"},{"location":"packages/mosaic/#how-to","title":"How To","text":"from fastapi import FastAPI\nfrom titiler.mosaic.factory import MosaicTilerFactory\n\nfrom cogeo_mosaic.backends import MosaicBackend\n\n# Create a FastAPI application\napp = FastAPI(\n description=\"A Mosaic tile server\",\n)\n\n# Create a set of Mosaic endpoints using MosaicJSON backend from cogeo-mosaic project\nmosaic = MosaicTilerFactory(backend=MosaicBackend)\n\n# Register the Mosaic endpoints to the application\napp.include_router(mosaic.router, tags=[\"MosaicJSON\"])\n"},{"location":"packages/mosaic/#package-structure","title":"Package structure","text":"titiler/\n \u2514\u2500\u2500 mosaic/\n \u251c\u2500\u2500 tests/ - Tests suite\n \u2514\u2500\u2500 titiler/mosaic/ - `mosaic` namespace package\n \u251c\u2500\u2500 models/\n | \u2514\u2500\u2500 responses.py - mosaic response models\n \u251c\u2500\u2500 errors.py - mosaic known errors\n \u251c\u2500\u2500 extensions.py - extensions\n \u2514\u2500\u2500 factory.py - Mosaic endpoints factory\n"},{"location":"packages/xarray/","title":"titiler.xarray","text":""},{"location":"packages/xarray/#titilerxarray","title":"titiler.xarray","text":"Adds support for Xarray Dataset (NetCDF/Zarr) in Titiler.
"},{"location":"packages/xarray/#installation","title":"Installation","text":"python -m pip install -U pip\n\n# From Pypi\npython -m pip install \"titiler.xarray[full]\"\n\n# Or from sources\ngit clone https://github.com/developmentseed/titiler.git\ncd titiler && python -m pip install -e src/titiler/core -e \"src/titiler/xarray\"\n"},{"location":"packages/xarray/#installation-options","title":"Installation options","text":"Default installation for titiler.xarray DOES NOT include fsspec or any storage's specific dependencies (e.g s3fs) nor engine dependencies (zarr, h5netcdf). This is to ease the customization and deployment of user's applications. If you want to use the default's dataset reader you will need to at least use the [minimal] dependencies (e.g python -m pip install \"titiler.xarray[minimal]\").
Here is the list of available options:
h5netcdf, fsspec, s3fs, aiohttp, gcsfsTitiler.xarray follows SPEC 0, similar to xarray.
"},{"location":"packages/xarray/#how-to","title":"How To","text":"from fastapi import FastAPI\n\nfrom titiler.xarray.extensions import VariablesExtension\nfrom titiler.xarray.factory import TilerFactory\n\napp = FastAPI(\n openapi_url=\"/api\",\n docs_url=\"/api.html\",\n description=\"\"\"Xarray based tiles server for MultiDimensional dataset (Zarr/NetCDF).\n\n---\n\n**Documentation**: <a href=\"https://developmentseed.org/titiler/\" target=\"_blank\">https://developmentseed.org/titiler/</a>\n\n**Source Code**: <a href=\"https://github.com/developmentseed/titiler\" target=\"_blank\">https://github.com/developmentseed/titiler</a>\n\n---\n \"\"\",\n)\n\nmd = TilerFactory(\n router_prefix=\"/md\",\n extensions=[\n VariablesExtension(),\n ],\n)\napp.include_router(md.router, prefix=\"/md\", tags=[\"Multi Dimensional\"])\n"},{"location":"packages/xarray/#package-structure","title":"Package structure","text":"titiler/\n \u2514\u2500\u2500 xarray/\n \u251c\u2500\u2500 tests/ - Tests suite\n \u2514\u2500\u2500 titiler/xarray/ - `xarray` namespace package\n \u251c\u2500\u2500 dependencies.py - titiler-xarray dependencies\n \u251c\u2500\u2500 extensions.py - titiler-xarray extensions\n \u251c\u2500\u2500 main.py - main fastapi application\n \u251c\u2500\u2500 io.py - titiler-xarray Readers\n \u2514\u2500\u2500 factory.py - endpoints factory\n"},{"location":"packages/xarray/#custom-dataset-opener","title":"Custom Dataset Opener","text":"A default Dataset IO is provided within titiler.xarray.io.Reader class with only support for Zarr dataset (via xarray+zarr-python).
For other dataset (e.g NetCDF), you can use titiler.xarray.io.FsReader which use the optional dependencies (fsspec, netcdf5).
python -m pip install \"titiler.xarray[fs]\"\n Example of application with fsspec reader:
from fastapi import FastAPI\nfrom titiler.xarray.extensions import VariablesExtension\nfrom titiler.xarray.factory import TilerFactory\nfrom titiler.xarray.io import FsReader\n\n# Create FastAPI application\napp = FastAPI(openapi_url=\"/api\", docs_url=\"/api.html\")\n\n# Create custom endpoints with the FsReader\nmd = TilerFactory(\n reader=FsReader,\n router_prefix=\"/md\",\n extensions=[\n # we also want to use the simple opener for the Extension\n VariablesExtension(dataset_opener=xarray.open_dataset),\n ],\n)\n\napp.include_router(md.router, prefix=\"/md\", tags=[\"Multi Dimensional\"])\n"},{"location":"user_guide/algorithms/","title":"Algorithm","text":"Starting with titiler>=0.8, we added the possibility to apply custom algorithms on Image outputs from tile, crop or preview endpoints.
The algorithms are meant to overcome the limitation of expression (using numexpr) by allowing more complex operations.
We added a set of custom algorithms:
hillshade: Create hillshade from elevation dataset (parameters: azimuth (45), angle_altitude(45))contours: Create contours lines (raster) from elevation dataset (parameters: increment (35), thickness (1))slope: Create degrees of slope from elevation datasetterrarium: Mapzen's format to encode elevation value in RGB values elevation = (red * 256 + green + blue / 256) - 32768terrainrgb: Mapbox/Maptiler's format to encode elevation value in RGB values elevation = -10000 + ((red * 256 * 256 + green * 256 + blue) * 0.1)normalizedIndex: Normalized Difference Index (e.g NDVI)cast: Cast data to integerfloor: Round data to the smallest integerceil: Round data to the largest integer
min: Return Min values along the bands axis.
max: Return Max values along the bands axis.median: Return Median values along the bands axis.mean: Return Mean values along the bands axis.std: Return the Standard Deviation along the bands axis.var: Return Variance along the bands axis.sum: Return Sum along the bands axis.grayscale: Return a grayscale version of an image using ITU-R 601-2 luma transformation.bitonal: All values larger than 127 are set to 255 (white), all other values to 0 (black).# 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":"user_guide/algorithms/#create-your-own-algorithm","title":"Create your own Algorithm","text":"A titiler'w Algorithm must be defined using titiler.core.algorithm.BaseAlgorithm base class.
class BaseAlgorithm(BaseModel, metaclass=abc.ABCMeta):\n \"\"\"Algorithm baseclass.\n\n Note: attribute starting with `input_` or `output_` are considered as metadata\n\n \"\"\"\n\n # metadata\n input_nbands: int\n output_nbands: int\n output_dtype: str\n output_min: Optional[Sequence]\n output_max: Optional[Sequence]\n\n @abc.abstractmethod\n def __call__(self, img: ImageData) -> ImageData:\n \"\"\"Apply algorithm\"\"\"\n ...\n\n class Config:\n \"\"\"Config for model.\"\"\"\n\n extra = \"allow\"\n This base class defines that algorithm:
HAVE TO implement an __call__ method which takes an ImageData as input and return an ImageData. Using __call__ let us use the object as a callable (e.g Algorithm(**kwargs)(image)).
can have input/output metadata (informative)
can haveparameters (enabled by extra = \"allow\" pydantic config)
Here is a simple example of a custom algorithm:
from titiler.core.algorithm import BaseAlgorithm\nfrom rio_tiler.models import ImageData\n\nclass Multiply(BaseAlgorithm):\n\n # Parameters\n factor: int # There is no default, which means calls to this algorithm without any parameter will fail\n\n # We don't set any metadata for this Algorithm\n\n def __call__(self, img: ImageData) -> ImageData:\n # Multiply image data bcy factor\n data = img.data * self.factor\n\n # Create output ImageData\n return ImageData(\n data,\n img.mask,\n assets=img.assets,\n crs=img.crs,\n bounds=img.bounds,\n )\n"},{"location":"user_guide/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":"user_guide/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 which algorithms 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":"user_guide/algorithms/#order-of-operation","title":"Order of operation","text":"When creating a map tile (or other images), we will first 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":"user_guide/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 Mapbox GL-JS) will apply the 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 a 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, ... ). The map library will almost only accept Uint8 RGB(A) tile encoded as PNG, JPEG or Webp.
Static tiling is referring to static tiles (file on storage) that are pre-rendered from input dataset. Here are the steps needed to create those tiles:
When the tiles are available on a storage, you can either put a web server (e.g. tilecache, mapserver) or maybe directly put a CDN, which will allow map client to fetch the tiles.
gdal2tiles.py)The goal of the Dynamic Tiling process is to get rid of all the pre-processing steps, by creating a tile server which can access the raw data (COG) and apply operations (rescaling, reprojection, image encoding) to create the visual tiles on the fly.
With Static tile generation, you are often limited because you are visualizing data that is fixed and stored somewhere on a disk. With Dynamic tiling, users have the possibility to apply their own choice of processing (e.g rescaling, masking) before creating the image.
Static tiling will always be faster to load than dynamic tiling, but a cache layer can be set up in front of the dynamic tiler. Using a dynamic tiler often means that the same tile won't be served twice (because users can set multiple options).
"},{"location":"user_guide/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":"user_guide/getting_started/","title":"Getting Started","text":"TiTiler is a modern map tile server that helps developers quickly serve geospatial data on the web. Think of it as a specialized tool that takes large geographic files (like satellite imagery) and slices them into small, web-friendly map tiles that load efficiently in browser-based maps.
Built on FastAPI, TiTiler makes working with Cloud-Optimized GeoTIFFs, Spatio Temporal Asset Catalog and other spatial data formats straightforward, even if you're not a GIS expert. It handles all the complex work of processing geographic data and serving it through simple API endpoints that any web developer can use.
In the past, putting maps on websites was a real pain. Developers had to use bulky tools like GeoServer that were hard to set up, or spend hours making thousands of static tiny map images with tools like gdal2tiles that couldn't be changed later. TiTiler makes this so much easier. It creates dynamic map pieces right when you need them, instead of making them all beforehand. It works great with modern cloud data and doesn't need complicated setup. This means less headache and more time to focus on building cool map features that users will love.
"},{"location":"user_guide/getting_started/#dynamic-vs-static-tiles-whats-the-difference","title":"Dynamic vs. Static Tiles: What's the Difference?","text":"Static tiles are like pre-printed map pieces stored in folders. Once created, they're locked\u2014changing anything means starting over. They use lots of storage, but load quickly.
TiTiler's dynamic tiles work like a chef cooking to order. When someone views your map, TiTiler grabs just the data needed and creates tiles on the spot. This lets you instantly change colors, adjust contrast, or highlight different features. Your map becomes flexible and responsive, adapting to what users need right now, rather than being stuck with choices made earlier.
More on Dynamic Tiling
"},{"location":"user_guide/getting_started/#lets-get-titiler-up-and-running","title":"Let's Get TiTiler Up and Running!","text":"Now that we understand the advantage of TiTiler's dynamic approach, let's get it running on your local machine. Follow these steps:
"},{"location":"user_guide/getting_started/#1-create-your-project-workspace","title":"1. Create Your Project Workspace","text":"First, let's create a dedicated space for our TiTiler project. Open your terminal (Command Prompt or PowerShell on Windows, Terminal on macOS/Linux) and run:
# Works on all operating systems\nmkdir Titiler\ncd Titiler\n \ud83d\udca1 Pro Tip: Keeping your TiTiler project in its own folder makes it easier to manage and prevents conflicts with other Python projects.
"},{"location":"user_guide/getting_started/#2-set-up-a-python-virtual-environment","title":"2. Set Up a Python Virtual Environment","text":"a. Create the virtual environment:
python -m venv titiler\n b. Activate the virtual environment: - For Linux/macOS: source titiler/bin/activate\n - For Windows: titiler\\Scripts\\activate\n"},{"location":"user_guide/getting_started/#3-install-titiler-and-its-dependencies","title":"3. Install TiTiler and Its Dependencies","text":"With your environment activated, install TiTiler and the web server it needs:
pip install titiler.core uvicorn\n \u26a0\ufe0f Warning: Previously, TiTiler was available via a titiler metapackage, but that's no longer the case. In late 2025, we dropped support for this metapackage.
This command installs the core TiTiler package and Uvicorn, a lightning-fast ASGI server.
\ud83d\udca1 What's happening: TiTiler.core contains the essential functionality for serving map tiles. Uvicorn is the engine that will run our FastAPI application.
"},{"location":"user_guide/getting_started/#4-create-your-titiler-application","title":"4. Create Your TiTiler Application","text":"Now for the fun part! Create a file named main.py with the following code:
from fastapi import FastAPI\nfrom titiler.core.factory import TilerFactory\n\nfrom starlette.middleware.cors import CORSMiddleware\n\napp = FastAPI()\n\n# Add CORS middleware\napp.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"], # Allows all origins (for development - be more specific in production)\n allow_credentials=True,\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n)\n\n# Create a TilerFactory for Cloud-Optimized GeoTIFFs\ncog = TilerFactory()\n\n# Register all the COG endpoints automatically\napp.include_router(cog.router, tags=[\"Cloud Optimized GeoTIFF\"])\n\n\n# Optional: Add a welcome message for the root endpoint\n@app.get(\"/\")\ndef read_index():\n return {\"message\": \"Welcome to TiTiler\"}\n \ud83d\udca1 Code Breakdown:
TilerFactory() creates all the endpoints needed for serving COG tilesapp.include_router()Run the following command to start the server:
uvicorn main:app --reload\n You should see an output similar to this: \ud83d\udca1 The --reload flag automatically restarts the server whenever you change your code - perfect for development!
Open your browser and go to:
http://127.0.0.1:8000/ - See your welcome message
http://127.0.0.1:8000/docs - Explore the interactive API documentation. The /docs page is your mission control center. It shows all the endpoints TiTiler created for you and lets you test them directly in your browser:
Now that your server is running, let's see what it can do with real data!
"},{"location":"user_guide/getting_started/#quick-preview-of-your-raster","title":"Quick Preview of Your Raster","text":"To get a quick preview of any Cloud-Optimized GeoTIFF, use:
http://127.0.0.1:8000/preview?url=file:///path_to_your_raster.tif\n \u26a0\ufe0f Note: Replace the path with the actual path to your COG file. Remember to use the full path for local files.
"},{"location":"user_guide/getting_started/#visualizing-a-specific-tile-z-x-y","title":"Visualizing a Specific Tile (Z, X, Y)","text":"When working with web maps, understanding tile coordinates is essential. Let's break down what Z, X, Y values mean:
At zoom level 0, there's just 1 tile for the whole world. Each zoom level increase splits each tile into 4 more detailed tiles.
"},{"location":"user_guide/getting_started/#why-visualize-specific-tiles","title":"Why Visualize Specific Tiles?","text":"The rio_tiler and morecantile library makes this straightforward:
from rio_tiler.io import Reader\nimport morecantile\n\n# Web Mercator is the default tiling scheme for most web map clients\nWEB_MERCATOR_TMS = morecantile.tms.get(\"WebMercatorQuad\")\n\nwith Reader('/path/to/your/raster.tif', tms=WEB_MERCATOR_TMS) as src:\n bbox = src.get_geographic_bounds(\"epsg:4326\")\n zoom = 15\n # Find all tiles covering the bounding box\n tiles = list(src.tms.tiles(bbox[0], bbox[1], bbox[2], bbox[3], zoom))\n for t in tiles:\n print(\"Tile coordinate (x, y, z):\", t.x, t.y, t.z)\n"},{"location":"user_guide/getting_started/#viewing-a-specific-tile-in-titiler","title":"Viewing a Specific Tile in TiTiler","text":"For example, if your tile has coordinates x=5412, y=12463, z=15, you would access the specific tile with:
http://127.0.0.1:8000/tiles/WebMercatorQuad/15/5412/12463.png?url=raster.tif\n URL components explained:
WebMercatorQuad/: The tiling scheme (this should match your raster's CRS - TiTiler will reproject on-the-fly if needed, but using the correct scheme improves performance and accuracy){z}/{x}/{y}: Your tile coordinates.png: Output format (alternatives: .jpg, .webp, .tif)?url=raster.tif: Source raster fileMore on Tiling Schemes
"},{"location":"user_guide/getting_started/#creating-a-web-map-with-leaflet","title":"Creating a Web Map with Leaflet","text":"Leaflet is a lightweight, open-source JavaScript library for interactive maps. It lets you combine base maps (like OpenStreetMap) with overlays from custom tile servers such as TiTiler.
The following code (in map.html) loads a base map, adds your TiTiler raster overlay, and automatically sets the map's view to the raster's bounds:
map.html Code<!DOCTYPE html>\n<html>\n<head>\n <title>Leaflet Basemap + TiTiler Raster Overlay</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"initial-scale=1.0\">\n <link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\" />\n <script src=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js\"></script>\n</head>\n<body>\n <div id=\"map\" style=\"width: 100%; height: 600px;\"></div>\n <script>\n // Initialize the map with OpenStreetMap as the basemap\n var map = L.map('map').setView([0, 0], 2);\n L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n maxZoom: 19,\n attribution: '© OpenStreetMap contributors'\n }).addTo(map);\n\n /// Define the local raster path and TiTiler endpoint\n // Replace with your own full GeoTIFF path - use the appropriate format for your OS.\n var rasterPath = 'file:///path_to_your_raster.tif';\n\n // Fetch the raster's bounding box from TiTiler and adjust the map view accordingly\n var tileJSONUrl = 'http://127.0.0.1:8000/WebMercatorQuad/tilejson.json?url=' + encodeURIComponent(rasterPath) + \"&tilesize=256\";\n console.log(tileJSONUrl)\n fetch(tileJSONUrl)\n .then(response => response.json())\n .then(data => {\n console.log(\"Bounds data:\", data.bounds);\n if (data && data.bounds) {\n // data.bounds is [minX, minY, maxX, maxY]\n var b = data.bounds;\n // Convert to Leaflet bounds: [[southWest_lat, southWest_lng], [northEast_lat, northEast_lng]]\n var leafletBounds = [[b[1], b[0]], [b[3], b[2]]];\n map.fitBounds(leafletBounds);\n\n // Add the TiTiler raster overlay with some transparency\n L.tileLayer(data.tiles[0], {\n tileSize: 256,\n opacity: 0.7,\n maxZoom: data.maxzoom\n }).addTo(map);\n\n } else {\n console.error(\"No bounds returned from TiTiler.\");\n }\n })\n .catch(error => console.error(\"Error fetching bounds:\", error));\n\n </script>\n</body>\n</html>\n"},{"location":"user_guide/getting_started/#troubleshooting-common-issues","title":"Troubleshooting Common Issues","text":""},{"location":"user_guide/getting_started/#cors-issues","title":"CORS Issues","text":"If you encounter \"Access to fetch at X has been blocked by CORS policy\" errors in your browser console, make sure you:
main.py as shown aboveWhen using file:/// URLs: - Make sure to use the absolute path to your file with the correct format for your operating system:
file:///C:/Users/username/data/image.tiffile:///Users/username/data/image.tiffile:///home/username/data/image.tifIf your map loads but your tiles don't appear:
rio cogeo validate from the rio-cogeo package)Created by Dimple Jain
"},{"location":"user_guide/getting_started/#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 the default endpoints documentation pages:
/cog - Cloud Optimized GeoTIFF/mosaicjson - MosaicJSON/stac - Spatio Temporal Asset Catalog/tileMatrixSets - Tiling Schemes/algorithms - Algorithms/colorMaps - ColorMapsThe default application can be customized using environment variables defined in titiler.application.settings.ApiSettings class. Each variable needs to be prefixed with TITILER_API_.
NAME (str): name of the application. Defaults to titiler.CORS_ORIGINS (str, , delimited origins): allowed CORS origin. Defaults to *.CORS_ALLOW_METHODS (str, , delimited methods): allowed CORS methods. Defaults to GET.CACHECONTROL (str): Cache control header to add to responses. Defaults to \"public, max-age=3600\".ROOT_PATH (str): path behind proxy.DEBUG (str): adds LoggerMiddleware and TotalTimeMiddleware in the middleware stack.DISABLE_COG (bool): disable /cog endpoints.DISABLE_STAC (bool): disable /stac endpoints.DISABLE_MOSAIC (bool): disable /mosaic endpoints.LOWER_CASE_QUERY_PARAMETERS (bool): transform all query-parameters to lower case (see developmentseed/titiler!321).GLOBAL_ACCESS_TOKEN (str | None): a string which is required in the ?access_token= query param with every request.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":"user_guide/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 is required to create a valid web map tile.
TiTiler also has support for more complex output data formats, such as JPEG2000 or GeoTIFF. While it might not be useful for FrontEnd display (most browsers can't decode GeoTIFF natively), some users could want to transmit the data as raw values to some applications (non-web display).
Default output types/extensions are:
.tif: image/tiff; application=geotiff.jp2: image/jp2.png: image/png.pngraw: image/png.jpeg: image/jpeg.jpg: image/jpg.webp: image/webp.npy: application/x-binaryWhile .tif could be interesting, decoding the GeoTIFF format requires non-native/default libraries. Recently, in collaboration with Planet, we started exploring the use of a Numpy-native format to encode the data array.
planetlabs/numpytiles-spec
This specification attempts to create a standard for representing uncompressed, full bit-depth, raster imagery that can be easily communicated between a server and a client.
Example:
import numpy\nimport requests\nfrom io import BytesIO\n\nurl = \"https://opendata.digitalglobe.com/events/mauritius-oil-spill/post-event/2020-08-12/105001001F1B5B00/105001001F1B5B00.tif\"\n\nr = requests.get(\"http://127.0.0.1:8000/cog/tiles/14/10818/9146.npy\",\n params = {\n \"url\": url,\n }\n)\ndata = numpy.load(BytesIO(r.content))\nprint(data.shape)\n>>> (4, 256, 256)\n\n# By default titiler will return a concatenated data,mask array.\ndata, mask = data[0:-1], data[-1]\n Notebook: Working_with_NumpyTile
"},{"location":"user_guide/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).
When using Titiler to visualize imagery, there are some helper options that change how the data appears on the screen. You can:
Color maps are arrays of colors, used to map pixel values to specific colors. For example, it is possible to map a single band DEM, where the 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":"user_guide/rendering/#default-colormaps","title":"Default Colormaps","text":"Default colormaps pre-made, each with a given name. These maps come from the rio-tiler library, which has taken colormaps packaged with Matplotlib and has added others that are commonly used with raster data.
A list of available color maps can be found in Titiler's Swagger docs, or in the rio-tiler documentation.
To use a default colormap, simply use the parameter colormap_name:
import httpx\n\nresp = httpx.get(\n \"https://titiler.xyz/cog/preview\",\n params={\n \"url\": \"<YOUR DATASET URL HERE>\",\n \"colormap_name\": \"<YOUR COLORMAP NAME HERE>\" # e.g. autumn_r\n }\n)\n You can take any of the colormaps listed on rio-tiler, and add _r to reverse it.
If you'd like to specify your own colormap, you can specify your own using an encoded JSON:
import httpx\n\nresponse = httpx.get(\n \"https://titiler.xyz/cog/preview\",\n params={\n \"url\": \"<YOUR DATASET URL HERE>\",\n \"bidx\": \"1\",\n \"colormap\": json.dumps({\n \"0\": \"#e5f5f9\",\n \"10\": \"#99d8c9\",\n \"255\": \"#2ca25f\",\n })\n }\n)\n TiTiler supports colormaps that are both discrete (where pixels will be one of the colors that you specify) and linear (where pixel colors will blend between the given colors).
For more information, please check out rio-tiler's docs.
It is also possible to add a colormap dependency to automatically apply a default colormap.
"},{"location":"user_guide/rendering/#color-formula","title":"Color Formula","text":"Color formulae are simple commands that apply color corrections to images. This is useful for reducing artefacts like atmospheric haze, dark shadows, or muted colors.
Titiler supports color formulae as defined in Mapbox's rio-color plugin. These include the operations (taken from the rio-color docs):
Gamma adjustment: adjusts RGB values according to a power law, effectively brightening or darkening the midtones. It can be very effective in satellite imagery for reducing atmospheric haze in the blue and green bands.
Sigmoidal contrast adjustment: can alter the contrast and brightness of an image in a way that matches human's non-linear visual perception. It works well to increase contrast without blowing out the very dark shadows or already-bright parts of the image.
Saturation: can be thought of as the \"colorfulness\" of a pixel. Highly saturated colors are intense and almost cartoon-like, low saturation is more muted, closer to black and white. You can adjust saturation independently of brightness and hue, but the data must be transformed into a different color space.
In TiTiler, color_formulae are applied through the color_formula parameter as a string. An example of this option in action:
import httpx\n\nresponse = httpx.get(\n \"https://titiler.xyz/cog/preview\",\n params={\n \"url\": \"<YOUR DATASET URL HERE>\",\n \"color_formula\": \"gamma rg 1.3, sigmoidal rgb 22 0.1, saturation 1.5\"\n }\n)\n"},{"location":"user_guide/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":"user_guide/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":"user_guide/titiler_with_stac/","title":"Titiler with STAC","text":"In our previous post, we set up TiTiler for serving Cloud-Optimized GeoTIFFs. Now let's explore how TiTiler works with STAC (SpatioTemporal Asset Catalog) - a standardized way to describe and organize geospatial data.
Prerequisites: For Python environment and TiTiler setup, check out the Getting Started with TiTiler post.
"},{"location":"user_guide/titiler_with_stac/#what-is-stac","title":"What is STAC?","text":"STAC is like a library catalog for satellite imagery. Instead of searching through folders, you get structured JSON files that tell you:
Throughout this tutorial, we'll use openly available Maxar satellite imagery from the Bay of Bengal Cyclone Mocha event:
https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json\n This STAC item contains:
Asset Description Bandsvisual True color RGB Red, Green, Blue ms_analytic Multispectral Coastal, Blue, Green, Yellow, Red, RedEdge, NIR1, NIR2 pan_analytic Panchromatic Single band (high resolution) data-mask Valid data mask Single band Tip: Explore other open datasets at STAC Index to practice with different imagery
"},{"location":"user_guide/titiler_with_stac/#setting-up-titiler-for-stac","title":"Setting Up TiTiler for STAC","text":"Make sure your main.py includes the STAC router:
from fastapi import FastAPI\nfrom titiler.core.factory import TilerFactory, MultiBaseTilerFactory\nfrom starlette.middleware.cors import CORSMiddleware\nfrom rio_tiler.io import STACReader\n\napp = FastAPI(title=\"TiTiler\")\n\napp.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"],\n allow_credentials=True,\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n)\n\n# COG endpoints\ncog = TilerFactory()\napp.include_router(cog.router, prefix=\"/cog\", tags=[\"COG\"])\n\n# STAC endpoints\nstac = MultiBaseTilerFactory(reader=STACReader)\napp.include_router(stac.router, prefix=\"/stac\", tags=[\"STAC\"])\n\n@app.get(\"/\")\ndef read_index():\n return {\"message\": \"Welcome to TiTiler!\"}\n What's new for STAC: We import STACReader from rio_tiler.io and use MultiBaseTilerFactory (instead of TilerFactory) to create STAC endpoints - this factory understands how to read multiple assets from a single STAC item.
Start the server:
uvicorn main:app --reload\n Open your browser and go to: http://127.0.0.1:8000/docs - Explore the interactive API documentation.
/stac/info:","text":"Before visualizing, let's understand what's in our STAC item. The /stac/info endpoint returns metadata about available assets and their properties.
http://127.0.0.1:8000/stac/assets?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json\n"},{"location":"user_guide/titiler_with_stac/#basic-info-request","title":"Basic Info Request","text":"http://127.0.0.1:8000/stac/info?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=:all:\n Note
assets=:all: special notation tells titiler to fetch info for all available asset
To get detailed information about a specific asset:
http://127.0.0.1:8000/stac/info?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=visual\n "},{"location":"user_guide/titiler_with_stac/#2-quick-preview-stacpreview","title":"2. Quick Preview: /stac/preview","text":"The /stac/preview endpoint generates a downsampled image of your data - perfect for quick visualization.
http://127.0.0.1:8000/stac/preview?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=visual\n True Color visual asset:
"},{"location":"user_guide/titiler_with_stac/#preview-with-output-format","title":"Preview with Output Format","text":"You can specify the output format:
# JPEG output (smaller file size)\nhttp://127.0.0.1:8000/stac/preview.jpg?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=visual\n\n# WebP output (modern format, good compression)\nhttp://127.0.0.1:8000/stac/preview.webp?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=visual\n"},{"location":"user_guide/titiler_with_stac/#3-map-tiles-stactilestilematrixsetidzxy","title":"3. Map Tiles: /stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}","text":"For web maps, you need tiles - small image pieces that load progressively as users pan and zoom. This is the most powerful endpoint for building interactive maps.
"},{"location":"user_guide/titiler_with_stac/#finding-tile-coordinates-z-x-y","title":"Finding Tile Coordinates (Z, X, Y)","text":"To request a specific tile, you need to know its coordinates. You can calculate these from the STAC item's bounding box using the morecantile library.
Important: STAC items have two types of bounding boxes:
bbox - Always in WGS84 (lon/lat) - use this for tile calculationsproj:bbox - In the asset's projection (e.g., UTM meters) - don't use this directlyimport httpx\nimport morecantile\n\n# Fetch the STAC item\nstac_url = \"https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json\"\nitem = httpx.get(stac_url).json()\n\n# Get the WGS84 bbox (not proj:bbox!)\nbbox = item[\"bbox\"] # [92.724, 20.481, 92.761, 20.530]\n\n# Calculate center point\nlon = (bbox[0] + bbox[2]) / 2 # 92.743\nlat = (bbox[1] + bbox[3]) / 2 # 20.506\nzoom = 15\n\n# Load the WebMercatorQuad TileMatrixSet\ntms = morecantile.tms.get(\"WebMercatorQuad\")\n\n# Get the tile containing this point\ntile = tms.tile(lon, lat, zoom)\nprint(f\"z={tile.z}, x={tile.x}, y={tile.y}\")\n# Output: z=15, x=24825, y=14476\n Tip: Install morecantile with python -m pip install morecantile
http://127.0.0.1:8000/stac/tiles/WebMercatorQuad/15/24825/14476.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=visual\n URL breakdown:
WebMercatorQuad: Standard web map projection (EPSG:3857)15: Zoom level24825: X tile coordinate14476: Y tile coordinate.png: Output formatassets Parameter","text":"The assets parameter specifies which asset(s) to render:
# Single asset (visual - RGB)\n&assets=visual\n\n# Multiple assets (for band combinations from different assets)\n&assets=ms_analytic&assets=pan_analytic\n"},{"location":"user_guide/titiler_with_stac/#passing-per-asset-options-using-nameoptions","title":"Passing per Asset options using {name}|{options}={...}","text":"When working with multi-band asset, you can extend the asset option to select specific bands or apply expression:
Note
Support of options in asset's name may depends on implementation. rio-tiler's STACReader supports both indexes= and expression=:
assets=ms_analytic|indexes=1,2,3assets=ms_analytic|expression=b1+b2# Select band 3 (Green) from ms_analytic\nhttp://127.0.0.1:8000/stac/tiles/WebMercatorQuad/15/24825/14476.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=ms_analytic|indexes=3\n Band indices for ms_analytic:
{asset}|indexes={}","text":"Create false-color composites by specifying 3 bands:
# Natural Color (Red, Green, Blue - bands 5,3,2)\n&assets=ms_analytic|indexes=5,3,2\n\n# False Color Infrared (NIR, Red, Green - bands 7,5,3)\n&assets=ms_analytic|indexes=7,5,3\n\n# Agriculture (NIR, Green, Blue - bands 7,3,2)\n&assets=ms_analytic|indexes=7,3,2\n"},{"location":"user_guide/titiler_with_stac/#using-rescale-adjust-value-range","title":"Using rescale - Adjust Value Range","text":"Important: Raw satellite data often has values outside the 0-255 display range. Without rescaling, images may appear black or washed out.
Tip: If your image appears black or too dark, try adjusting the rescale max value. Adjust based on your data min-max.
Full example - False Color Infrared with rescale:
http://127.0.0.1:8000/stac/tiles/WebMercatorQuad/15/24825/14476.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=ms_analytic&asset_bidx=ms_analytic|7,5,3&rescale=0,2000\n "},{"location":"user_guide/titiler_with_stac/#using-expression-band-math","title":"Using expression - Band Math","text":"The expression parameter lets you perform calculations across bands. This is powerful for creating vegetation indices, water indices, and more.
Syntax: Bands are referenced as b{index} (e.g., b7 for NIR band in the ms_analytic asset).
Important
For STAC, the band index b{index} within an expression corresponds to the index of the resulting image created using multiple assets. If you combine two assets with 2 bands each, the resulting images will have 4 bands, thus an expression could accept b1 -> b4.
# select bands 1 & 2 for two assets and apply an expression\nassets=visual|indexes=1,2&assets=ms_analytic|indexes=1,2&expression=(b1+b2+b3+b4)/4\n Warning
The + sign in URLs is interpreted as a space! Use %2B instead of + in your expressions, otherwise you'll get a syntax error.
NDVI highlights vegetation: (NIR - Red) / (NIR + Red)
http://127.0.0.1:8000/stac/tiles/WebMercatorQuad/15/24825/14476.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=ms_analytic&expression=(b7-b5)/(b7%2Bb5)&rescale=-1,1\n "},{"location":"user_guide/titiler_with_stac/#ndwi-normalized-difference-water-index","title":"NDWI (Normalized Difference Water Index)","text":"NDWI highlights water bodies: (Green - NIR) / (Green + NIR)
http://127.0.0.1:8000/stac/tiles/WebMercatorQuad/15/24825/14476.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=ms_analytic&expression=(b3-b7)/(b3%2Bb7)&rescale=-1,1\n"},{"location":"user_guide/titiler_with_stac/#simple-band-ratio","title":"Simple Band Ratio","text":"# NIR/Red ratio (vegetation vigor)\n&assets=ms_analytic&expression=b7/b5\n"},{"location":"user_guide/titiler_with_stac/#using-colormap_name-apply-color-palettes","title":"Using colormap_name - Apply Color Palettes","text":"When using expressions (which return single-band results), apply colormaps to make the data meaningful:
List of Available colormaps
"},{"location":"user_guide/titiler_with_stac/#ndvi-with-colormap","title":"NDVI with Colormap","text":"http://127.0.0.1:8000/stac/tiles/WebMercatorQuad/15/24825/14476.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=ms_analytic&expression=(b7-b5)/(b7%2Bb5)&colormap_name=rdylgn&rescale=-1,1\n colormap_name=rdylgn: Red-Yellow-Green colormap (red=low NDVI, green=high NDVI)rescale=-1,1: NDVI values range from -1 to 1http://127.0.0.1:8000/stac/tiles/WebMercatorQuad/15/24825/14476.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=ms_analytic&expression=(b3-b7)/(b3%2Bb7)&colormap_name=blues&rescale=-1,1\n"},{"location":"user_guide/titiler_with_stac/#visualize-at-the-image-extent-or-crop-to-custom-bounds","title":"Visualize at the Image Extent or Crop to Custom Bounds","text":"NDVI on Image Extent
http://127.0.0.1:8000/stac/bbox/92.724,20.481,92.761,20.530.png?url=https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json&assets=ms_analytic&expression=(b7-b5)/(b7%2Bb5)&colormap_name=rdylgn&rescale=-1,1\n URL breakdown:
/stac/bbox/92.724,20.481,92.761,20.530.png - Bounding box: minLon,minLat,maxLon,maxLat bbox field<!DOCTYPE html>\n<html>\n<head>\n <title>STAC + TiTiler Map</title>\n <link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\" />\n <script src=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js\"></script>\n</head>\n<body>\n <div id=\"map\" style=\"width: 100%; height: 600px;\"></div>\n <script>\n var map = L.map('map').setView([20.506, 92.743], 14);\n\n // Base map\n L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n attribution: '© OpenStreetMap'\n }).addTo(map);\n\n // TiTiler STAC layer\n var stacUrl = 'https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111333030/2023-05-22/10300110E84B5A00.json';\n var tileUrl = `http://127.0.0.1:8000/stac/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=${encodeURIComponent(stacUrl)}&assets=visual`;\n\n L.tileLayer(tileUrl, {\n opacity: 0.8,\n maxZoom: 18\n }).addTo(map);\n </script>\n</body>\n</html>\n To view the map:
map.html uvicorn main:app --reload) map.html in your browser (double-click or drag into browser)Asset not found: Check asset names in the STAC item JSON - they're case-sensitive.
Black/white tiles: Your data values might be outside the default range. Use rescale to adjust.
Slow tiles: Large files take time. Consider using overviews or lower zoom levels for previews.
Created by Dimple Jain
"}]}