This repository has been archived on 2026-03-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
rio-tiler-tms/app.py
2026-02-22 10:51:28 +08:00

148 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""rio-tiler dynamic tile server for I7D16.tif"""
import os
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse
from starlette.requests import Request
from starlette.responses import Response
from rio_tiler.errors import TileOutsideBounds
from rio_tiler.io import Reader
from rio_tiler.profiles import img_profiles
DATA_PATH = str(Path(__file__).parent / "data" / "I7D16.tif")
# Per-band rescale range derived from p2/p98 statistics of the source image
# Band order: Red, Green, Blue (band 4 NIR is ignored)
RESCALE = [
(6130, 40453), # Red
(10641, 41211), # Green
(12482, 36323), # Blue
]
app = FastAPI(
title="rio-tiler",
description="Dynamic tile server for I7D16.tif (NJ 2020 aerial imagery)",
)
@app.get(
"/tiles/{z}/{x}/{y}.png",
responses={
200: {"content": {"image/png": {}}, "description": "Return a PNG tile."},
404: {"description": "Tile outside image bounds."},
},
response_class=Response,
description="Read COG tile and return a PNG image (RGB bands, UInt16 rescaled)",
)
def tile(z: int, x: int, y: int):
"""Return a map tile for the given z/x/y coordinates."""
try:
with Reader(DATA_PATH) as cog:
img = cog.tile(x, y, z, indexes=(1, 2, 3))
except TileOutsideBounds:
raise HTTPException(status_code=404, detail="Tile outside image bounds")
content = img.render(
img_format="PNG",
rescale=RESCALE,
**img_profiles.get("png", {}),
)
return Response(content, media_type="image/png")
@app.get("/tilejson.json", responses={200: {"description": "Return a TileJSON document"}})
def tilejson(request: Request):
"""Return a TileJSON 2.2.0 document describing the available tiles."""
with Reader(DATA_PATH) as cog:
bounds = cog.get_geographic_bounds(cog.tms.rasterio_geographic_crs)
minzoom = cog.minzoom
maxzoom = cog.maxzoom
base_url = str(request.base_url).rstrip("/")
tile_url = f"{base_url}/tiles/{{z}}/{{x}}/{{y}}.png"
return {
"tilejson": "2.2.0",
"name": os.path.basename(DATA_PATH),
"description": "NJ Open Imagery 2020 - NAD83(2011) / New Jersey (ftUS)",
"tiles": [tile_url],
"bounds": list(bounds),
"minzoom": minzoom,
"maxzoom": maxzoom,
"center": [
(bounds[0] + bounds[2]) / 2,
(bounds[1] + bounds[3]) / 2,
minzoom + 2,
],
}
@app.get("/", response_class=HTMLResponse)
def index(request: Request):
"""Simple Leaflet.js viewer for the imagery."""
with Reader(DATA_PATH) as cog:
bounds = cog.get_geographic_bounds(cog.tms.rasterio_geographic_crs)
minzoom = cog.minzoom
maxzoom = cog.maxzoom
base_url = str(request.base_url).rstrip("/")
tile_url = f"{base_url}/tiles/{{z}}/{{x}}/{{y}}.png"
center_lat = (bounds[1] + bounds[3]) / 2
center_lng = (bounds[0] + bounds[2]) / 2
init_zoom = minzoom + 2
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<title>I7D16 Tile Viewer</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
html, body, #map {{ height: 100%; margin: 0; padding: 0; }}
#info {{
position: absolute; top: 10px; right: 10px; z-index: 1000;
background: white; padding: 10px 14px; border-radius: 4px;
font-family: sans-serif; font-size: 13px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
line-height: 1.6;
}}
</style>
</head>
<body>
<div id="map"></div>
<div id="info">
<b>I7D16.tif</b><br>
NJ Open Imagery 2020<br>
Zoom: {minzoom}{maxzoom}<br>
CRS: NAD83(2011) / NJ ftUS
</div>
<script>
var map = L.map('map').setView([{center_lat}, {center_lng}], {init_zoom});
L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
attribution: '© <a href="https://openstreetmap.org">OpenStreetMap</a>',
opacity: 0.4,
maxZoom: 19
}}).addTo(map);
L.tileLayer('{tile_url}', {{
minZoom: {minzoom},
maxZoom: {maxzoom},
opacity: 1.0,
attribution: 'NJ Open Imagery 2020'
}}).addTo(map);
map.fitBounds([
[{bounds[1]}, {bounds[0]}],
[{bounds[3]}, {bounds[2]}]
]);
</script>
</body>
</html>"""