fix(titiler): drop @1x suffix + append SAS token top-level #24
2 changed files with 19 additions and 12 deletions
|
|
@ -50,7 +50,7 @@ PC_SAS_TOKEN = "https://planetarycomputer.microsoft.com/api/sas/v1/token/sentine
|
||||||
# Self-hosted TiTiler on the Pi, exposed same-origin through the osint.rpi.local
|
# Self-hosted TiTiler on the Pi, exposed same-origin through the osint.rpi.local
|
||||||
# nginx vhost. Relative template — Leaflet resolves it against the page origin,
|
# nginx vhost. Relative template — Leaflet resolves it against the page origin,
|
||||||
# so the browser never touches a raw loopback port or titiler.xyz.
|
# so the browser never touches a raw loopback port or titiler.xyz.
|
||||||
TITILER_COG_TILES = f"{TITILER_PUBLIC_BASE}/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}@1x"
|
TITILER_COG_TILES = f"{TITILER_PUBLIC_BASE}/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}"
|
||||||
SENTINEL1_TTL = 20 * 60 # 15–30 min quota-friendly window
|
SENTINEL1_TTL = 20 * 60 # 15–30 min quota-friendly window
|
||||||
SENTINEL1_ATTRIBUTION = "Copernicus Sentinel-1 / Microsoft Planetary Computer"
|
SENTINEL1_ATTRIBUTION = "Copernicus Sentinel-1 / Microsoft Planetary Computer"
|
||||||
|
|
||||||
|
|
@ -1194,9 +1194,15 @@ async def _pc_call(coro: Awaitable[Any]) -> Any:
|
||||||
|
|
||||||
|
|
||||||
def sign_cog_url(href: str, token: str) -> str:
|
def sign_cog_url(href: str, token: str) -> str:
|
||||||
"""Append a SAS token to a PC blob URL (respect existing query string)."""
|
"""Append a SAS token to a PC blob URL (respect existing query string).
|
||||||
|
|
||||||
|
PC's SAS endpoint returns the token as an already-percent-encoded query
|
||||||
|
string (``st=…&se=…&sp=rl&…&sig=…``). Azure only honours those parameters
|
||||||
|
when they sit top-level on the blob URL — wrapping them under a single
|
||||||
|
``token=`` param yields 403/409, so we append the token verbatim.
|
||||||
|
"""
|
||||||
sep = "&" if "?" in href else "?"
|
sep = "&" if "?" in href else "?"
|
||||||
return f"{href}{sep}token={quote(token, safe='')}"
|
return f"{href}{sep}{token}"
|
||||||
|
|
||||||
|
|
||||||
def sentinel1_tile_url(signed_cog: str) -> str:
|
def sentinel1_tile_url(signed_cog: str) -> str:
|
||||||
|
|
|
||||||
|
|
@ -661,18 +661,19 @@ def test_planespotters_headers_add_contact_when_ua_is_generic(monkeypatch):
|
||||||
# ── Sentinel-1 SAR (Planetary Computer STAC → signed COG template) ────────
|
# ── Sentinel-1 SAR (Planetary Computer STAC → signed COG template) ────────
|
||||||
|
|
||||||
def test_sign_cog_url_appends_token():
|
def test_sign_cog_url_appends_token():
|
||||||
assert sign_cog_url("https://blob.example/x.tif", "tok=abc") == \
|
# PC returns the token pre-encoded as a query string; append verbatim.
|
||||||
"https://blob.example/x.tif?token=tok%3Dabc"
|
assert sign_cog_url("https://blob.example/x.tif", "st=s&se=e&sig=x%3D") == \
|
||||||
|
"https://blob.example/x.tif?st=s&se=e&sig=x%3D"
|
||||||
# Existing query string → append with &
|
# Existing query string → append with &
|
||||||
assert sign_cog_url("https://blob.example/x.tif?st=1", "tok") == \
|
assert sign_cog_url("https://blob.example/x.tif?foo=1", "st=s&sig=x") == \
|
||||||
"https://blob.example/x.tif?st=1&token=tok"
|
"https://blob.example/x.tif?foo=1&st=s&sig=x"
|
||||||
|
|
||||||
|
|
||||||
def test_sentinel1_tile_url_contains_titiler_rescale_and_cfastie():
|
def test_sentinel1_tile_url_contains_titiler_rescale_and_cfastie():
|
||||||
signed = "https://blob.example/x.tif?token=secret"
|
signed = "https://blob.example/x.tif?token=secret"
|
||||||
url = sentinel1_tile_url(signed)
|
url = sentinel1_tile_url(signed)
|
||||||
assert url.startswith(TITILER_COG_TILES + "?")
|
assert url.startswith(TITILER_COG_TILES + "?")
|
||||||
assert "WebMercatorQuad/{z}/{x}/{y}@1x" in url
|
assert "WebMercatorQuad/{z}/{x}/{y}?" in url
|
||||||
assert "url=https%3A%2F%2Fblob.example%2Fx.tif%3Ftoken%3Dsecret" in url
|
assert "url=https%3A%2F%2Fblob.example%2Fx.tif%3Ftoken%3Dsecret" in url
|
||||||
assert "rescale=0%2C500" in url
|
assert "rescale=0%2C500" in url
|
||||||
assert "colormap_name=cfastie" in url
|
assert "colormap_name=cfastie" in url
|
||||||
|
|
@ -723,12 +724,12 @@ def test_fetch_sentinel1_vv_signed_tile_url(monkeypatch):
|
||||||
assert out["opacity"] == 0.8
|
assert out["opacity"] == 0.8
|
||||||
assert out["itemId"].startswith("S1A")
|
assert out["itemId"].startswith("S1A")
|
||||||
assert out["attribution"] == SENTINEL1_ATTRIBUTION
|
assert out["attribution"] == SENTINEL1_ATTRIBUTION
|
||||||
assert "WebMercatorQuad/{z}/{x}/{y}@1x" in out["tileUrl"]
|
assert "WebMercatorQuad/{z}/{x}/{y}?" in out["tileUrl"]
|
||||||
assert "rescale=0%2C500" in out["tileUrl"]
|
assert "rescale=0%2C500" in out["tileUrl"]
|
||||||
assert "colormap_name=cfastie" in out["tileUrl"]
|
assert "colormap_name=cfastie" in out["tileUrl"]
|
||||||
# SAS token "sig=abc123" is signed as ?token=sig%3Dabc123, then the whole
|
# SAS token "sig=abc123" is appended top-level, then the whole COG URL is
|
||||||
# COG URL is percent-encoded again as a query param (=> sig%253Dabc123).
|
# percent-encoded again as a query param (=> sig%3Dabc123).
|
||||||
assert "sig%253Dabc123" in out["tileUrl"]
|
assert "sig%3Dabc123" in out["tileUrl"]
|
||||||
# STAC search payload shape
|
# STAC search payload shape
|
||||||
post_url, post_json = calls[0][1], calls[0][2]
|
post_url, post_json = calls[0][1], calls[0][2]
|
||||||
assert post_url.endswith("/api/stac/v1/search")
|
assert post_url.endswith("/api/stac/v1/search")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue