Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Unreleased

### Misc

* remove `/bounds` endpoints **breaking change**
* update docker image to python:3.13
* switch to `uv` for development
* switch to `hatch` for python package build-system
* remove `titiler` metapackage **breaking change**
* bump minimum python version to 3.11

### titiler.xarray

* add `opener_options` arg to `titiler.xarray.io.Reader` to allow users to pass args through to a custom opener function ([#1248(https://github.com/developmentseed/titiler/pull/1248)])
Expand All @@ -11,14 +20,6 @@
* add `FsReader` which use `fs_open_dataset` as `dataset_opener`
* create offical application `titiler.xarray.main:app`

### Misc

* update docker image to python:3.13
* switch to `uv` for development
* switch to `hatch` for python package build-system
* remove `titiler` metapackage **breaking change**
* bump minimum python version to 3.11

## 0.24.2 (2025-10-16)

### titiler.core
Expand Down
5 changes: 0 additions & 5 deletions docs/src/advanced/endpoints_factories.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ app.include_router(cog.router)

| Method | URL | Output | Description
| ------ | --------------------------------------------------------------- |-------------------------------------------- |--------------
| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds
| `GET` | `/info` | JSON ([Info][info_model]) | return dataset's basic info
| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return dataset's basic info as a GeoJSON feature
| `GET` | `/statistics` | JSON ([Statistics][stats_model]) | return dataset's statistics
Expand Down Expand Up @@ -137,7 +136,6 @@ app.include_router(stac.router)

| Method | URL | Output | Description
| ------ | --------------------------------------------------------------- |------------------------------------------------- |--------------
| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds
| `GET` | `/assets` | JSON | return the list of available assets
| `GET` | `/info` | JSON ([Info][multiinfo_model]) | return assets basic info
| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][multiinfo_geojson_model]) | return assets basic info as a GeoJSON feature
Expand Down Expand Up @@ -195,7 +193,6 @@ app.include_router(landsat.router)

| Method | URL | Output | Description
| ------ | --------------------------------------------------------------- |--------------------------------------------- |--------------
| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds
| `GET` | `/bands` | JSON | return the list of available bands
| `GET` | `/info` | JSON ([Info][info_model]) | return basic info for a dataset
| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return basic info for a dataset as a GeoJSON feature
Expand Down Expand Up @@ -333,7 +330,6 @@ Endpoints factory for mosaics, built on top of [MosaicJSON](https://github.com/d
| Method | URL | Output | Description
| ------ | --------------------------------------------------------------- |--------------------------------------------------- |--------------
| `GET` | `/` | JSON [MosaicJSON][mosaic_model] | return a MosaicJSON document
| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return mosaic's bounds
| `GET` | `/info` | JSON ([Info][mosaic_info_model]) | return mosaic's basic info
| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][mosaic_geojson_info_model]) | return mosaic's basic info as a GeoJSON feature
| `GET` | `/tiles` | JSON | List of OGC Tilesets available
Expand Down Expand Up @@ -398,7 +394,6 @@ app.include_router(md.router)

| Method | URL | Output | Description
| ------ | --------------------------------------------------------------- |-------------------------------------------- |--------------
| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds
| `GET` | `/info` | JSON ([Info][info_model]) | return dataset's basic info
| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return dataset's basic info as a GeoJSON feature
| `POST` | `/statistics` | GeoJSON ([Statistics][stats_geojson_model]) | return dataset's statistics for a GeoJSON
Expand Down
13 changes: 0 additions & 13 deletions docs/src/endpoints/cog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ The `/cog` routes are based on `titiler.core.factory.TilerFactory` but with `cog

| Method | URL | Output | Description
| ------ | ------------------------------------------------------------------- |---------- |--------------
| `GET` | `/cog/bounds` | JSON | return dataset's bounds
| `GET` | `/cog/info` | JSON | return dataset's basic info
| `GET` | `/cog/info.geojson` | GeoJSON | return dataset's basic info as a GeoJSON feature
| `GET` | `/cog/statistics` | JSON | return dataset's statistics
Expand Down Expand Up @@ -313,18 +312,6 @@ Example:
- `https://myendpoint/cog/WorldCRS84Quad/map.html?url=https://somewhere.com/mycog.tif&tile_scale=2&bidx=1,2,3`


### Bounds

`:endpoint:/cog/bounds` general image bounds

- QueryParams:
- **url** (str): Cloud Optimized GeoTIFF URL. **Required**
- **crs** (str): Geographic Coordinate Reference System. Default to `epsg:4326`.

Example:

- `https://myendpoint/cog/bounds?url=https://somewhere.com/mycog.tif`

### Info

`:endpoint:/cog/info` general raster info
Expand Down
1 change: 0 additions & 1 deletion docs/src/endpoints/mosaic.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ Read Mosaic Info/Metadata and create Web map Tiles from a multiple COG. The `mos
| Method | URL | Output | Description
| ------ | -------------------------------------------------------------------------- |---------- |--------------
| `GET` | `/mosaicjson/` | JSON | return a MosaicJSON document
| `GET` | `/mosaicjson/bounds` | JSON | return mosaic's bounds
| `GET` | `/mosaicjson/info` | JSON | return mosaic's basic info
| `GET` | `/mosaicjson/info.geojson` | GeoJSON | return mosaic's basic info as a GeoJSON feature
| `GET` | `/mosaicjson/tiles` | JSON | List of OGC Tilesets available
Expand Down
14 changes: 0 additions & 14 deletions docs/src/endpoints/stac.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ The `/stac` routes are based on `titiler.core.factory.MultiBaseTilerFactory` but
| Method | URL | Output | Description
| ------ | -------------------------------------------------------------------- |---------- |--------------
| `GET` | `/stac/assets` | JSON | return available assets within the STAC item
| `GET` | `/stac/bounds` | JSON | return STAC item bounds
| `GET` | `/stac/info` | JSON | return asset's basic info
| `GET` | `/stac/info.geojson` | GeoJSON | return asset's basic info as a GeoJSON feature
| `GET` | `/stac/asset_statistics` | JSON | return per asset statistics
Expand Down Expand Up @@ -346,19 +345,6 @@ Example:
- `https://myendpoint/stac/WorldCRS84Quad/tilejson.json?url=https://somewhere.com/item.json&tile_scale=2&expression=B01/B02`


### Bounds

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

- QueryParams:
- **url** (str): STAC Item URL. **Required**
- **crs** (str): Geographic Coordinate Reference System. Default to `epsg:4326`.

Example:

- `https://myendpoint/stac/bounds?url=https://somewhere.com/item.json`


### Info

`:endpoint:/stac/info` - Return basic info on STAC item's COG.
Expand Down
24 changes: 12 additions & 12 deletions docs/src/user_guide/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,28 +220,28 @@ The following code (in **map.html**) loads a base map, adds your TiTiler raster
/// Define the local raster path and TiTiler endpoint
// Replace with your own full GeoTIFF path - use the appropriate format for your OS.
var rasterPath = 'file:///path_to_your_raster.tif';
var titilerUrl = 'http://127.0.0.1:8000/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=' + encodeURIComponent(rasterPath);

// Add the TiTiler raster overlay with some transparency
L.tileLayer(titilerUrl, {
tileSize: 256,
opacity: 0.7,
maxZoom: 22
}).addTo(map);

// Fetch the raster's bounding box from TiTiler and adjust the map view accordingly
var boundsUrl = 'http://127.0.0.1:8000/bounds?url=' + encodeURIComponent(rasterPath);
console.log(boundsUrl)
fetch(boundsUrl)
var tileJSONUrl = 'http://127.0.0.1:8000/WebMercatorQuad/tilejson.json?url=' + encodeURIComponent(rasterPath);
console.log(tileJSONUrl)
fetch(tileJSONUrl)
.then(response => response.json())
.then(data => {
console.log("Bounds data:", data);
console.log("Bounds data:", data.bounds);
if (data && data.bounds) {
// data.bounds is [minX, minY, maxX, maxY]
var b = data.bounds;
// Convert to Leaflet bounds: [[southWest_lat, southWest_lng], [northEast_lat, northEast_lng]]
var leafletBounds = [[b[1], b[0]], [b[3], b[2]]];
map.fitBounds(leafletBounds);

// Add the TiTiler raster overlay with some transparency
L.tileLayer(data.tiles[0], {
tileSize: 256,
opacity: 0.7,
maxZoom: data.maxzoom
}).addTo(map);

} else {
console.error("No bounds returned from TiTiler.");
}
Expand Down
12 changes: 0 additions & 12 deletions src/titiler/application/tests/routes/test_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@
from ..conftest import DATA_DIR, mock_rasterio_open, parse_img


@patch("rio_tiler.io.rasterio.rasterio")
def test_bounds(rio, app):
"""test /bounds endpoint."""
rio.open = mock_rasterio_open

response = app.get("/cog/bounds?url=https://myurl.com/cog.tif")
assert response.status_code == 200
body = response.json()
assert len(body["bounds"]) == 4
assert response.headers["Cache-Control"] == "private, max-age=3600"


@patch("rio_tiler.io.rasterio.rasterio")
def test_info(rio, app):
"""test /info endpoint."""
Expand Down
10 changes: 0 additions & 10 deletions src/titiler/application/tests/routes/test_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ def test_read_mosaic(app):
MosaicJSON(**response.json())


def test_bounds(app):
"""test GET /mosaicjson/bounds endpoint"""
response = app.get("/mosaicjson/bounds", params={"url": MOSAICJSON_FILE})
assert response.status_code == 200
body = response.json()
assert len(body["bounds"]) == 4
assert body["bounds"][0] < body["bounds"][2]
assert body["bounds"][1] < body["bounds"][3]


def test_info(app):
"""test GET /mosaicjson/info endpoint"""
response = app.get("/mosaicjson/info", params={"url": MOSAICJSON_FILE})
Expand Down
11 changes: 0 additions & 11 deletions src/titiler/application/tests/routes/test_stac.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,6 @@
from ..conftest import mock_rasterio_open, mock_RequestGet


@patch("rio_tiler.io.stac.httpx")
def test_bounds(httpx, app):
"""test /bounds endpoint."""
httpx.get = mock_RequestGet

response = app.get("/stac/bounds?url=https://myurl.com/item.json")
assert response.status_code == 200
body = response.json()
assert len(body["bounds"]) == 4


@patch("rio_tiler.io.rasterio.rasterio")
@patch("rio_tiler.io.stac.httpx")
def test_info(httpx, rio, app):
Expand Down
43 changes: 12 additions & 31 deletions src/titiler/core/tests/test_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
def test_TilerFactory():
"""Test TilerFactory class."""
cog = TilerFactory()
assert len(cog.router.routes) == 23
assert len(cog.router.routes) == 22
assert len(cog.supported_tms.list()) == NB_DEFAULT_TMS

cog = TilerFactory(router_prefix="something", supported_tms=WEB_TMS)
Expand All @@ -78,7 +78,7 @@ def test_TilerFactory():
assert response.status_code == 422

cog = TilerFactory(add_preview=False, add_part=False, add_viewer=False)
assert len(cog.router.routes) == 14
assert len(cog.router.routes) == 13

app = FastAPI()
cog = TilerFactory()
Expand Down Expand Up @@ -314,12 +314,6 @@ def test_TilerFactory():
root = ET.fromstring(response.content)
assert root is not None

response = client.get(f"/bounds?url={DATA_DIR}/cog.tif")
assert response.status_code == 200
assert response.headers["content-type"] == "application/json"
assert len(response.json()["bounds"]) == 4
assert response.json()["crs"]

response = client.get(f"/info?url={DATA_DIR}/cog.tif")
assert response.status_code == 200
assert response.headers["content-type"] == "application/json"
Expand Down Expand Up @@ -783,7 +777,7 @@ def test_MultiBaseTilerFactory(rio):
rio.open = mock_rasterio_open

stac = MultiBaseTilerFactory(reader=STACReader)
assert len(stac.router.routes) == 25
assert len(stac.router.routes) == 24

app = FastAPI()
app.include_router(stac.router)
Expand All @@ -802,11 +796,6 @@ def test_MultiBaseTilerFactory(rio):
assert response.status_code == 200
assert len(response.json()) == 2

response = client.get(f"/bounds?url={DATA_DIR}/item.json")
assert response.status_code == 200
assert len(response.json()["bounds"]) == 4
assert response.json()["crs"]

# no assets
with pytest.warns(UserWarning):
response = client.get(f"/info?url={DATA_DIR}/item.json")
Expand Down Expand Up @@ -1173,7 +1162,7 @@ def test_MultiBandTilerFactory():
bands = MultiBandTilerFactory(
reader=BandFileReader, path_dependency=CustomPathParams
)
assert len(bands.router.routes) == 24
assert len(bands.router.routes) == 23

app = FastAPI()
app.include_router(bands.router)
Expand Down Expand Up @@ -1558,15 +1547,15 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic)
route_dependencies=[
(
[
{"path": "/bounds", "method": "GET"},
{"path": "/info", "method": "GET"},
{"path": "/tiles/{tileMatrixSetId}/{z}/{x}/{y}", "method": "GET"},
],
[Depends(must_be_bob)],
),
],
router_prefix="something",
)
assert len(cog.router.routes) == 23
assert len(cog.router.routes) == 22

app = FastAPI()
app.include_router(cog.router, prefix="/something")
Expand All @@ -1582,14 +1571,10 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic)
assert response.headers["content-type"] == "application/json"
assert response.json()["tilejson"]

response = client.get(
f"/something/bounds?url={DATA_DIR}/cog.tif&rescale=0,1000", auth=auth_bob
)
response = client.get(f"/something/info?url={DATA_DIR}/cog.tif", auth=auth_bob)
assert response.status_code == 200

response = client.get(
f"/something/bounds?url={DATA_DIR}/cog.tif&rescale=0,1000", auth=auth_notbob
)
response = client.get(f"/something/info?url={DATA_DIR}/cog.tif", auth=auth_notbob)
assert response.status_code == 401
assert response.json()["detail"] == "You're not Bob"

Expand All @@ -1615,7 +1600,7 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic)

cog = TilerFactory(router_prefix="something")
cog.add_route_dependencies(
scopes=[{"path": "/bounds", "method": "GET"}],
scopes=[{"path": "/info", "method": "GET"}],
dependencies=[Depends(must_be_bob)],
)

Expand All @@ -1630,14 +1615,10 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic)
assert response.headers["content-type"] == "application/json"
assert response.json()["tilejson"]

response = client.get(
f"/something/bounds?url={DATA_DIR}/cog.tif&rescale=0,1000", auth=auth_bob
)
response = client.get(f"/something/info?url={DATA_DIR}/cog.tif", auth=auth_bob)
assert response.status_code == 200

response = client.get(
f"/something/bounds?url={DATA_DIR}/cog.tif&rescale=0,1000", auth=auth_notbob
)
response = client.get(f"/something/info?url={DATA_DIR}/cog.tif", auth=auth_notbob)
assert response.status_code == 401
assert response.json()["detail"] == "You're not Bob"

Expand Down Expand Up @@ -2088,7 +2069,7 @@ def test_ogc_maps_cog():
cog_path = f"{DATA_DIR}/cog.tif"

cog = TilerFactory(add_ogc_maps=True)
assert len(cog.router.routes) == 24
assert len(cog.router.routes) == 23

assert "https://www.opengis.net/spec/ogcapi-maps-1/1.0/conf/core" in cog.conforms_to

Expand Down
Loading