Skip to content
Merged
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
62 changes: 55 additions & 7 deletions leafmap/stac.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ def check_titiler_endpoint(titiler_endpoint: Optional[str] = None) -> Any:
Returns:
The titiler endpoint.
"""
if titiler_endpoint is not None and titiler_endpoint.lower() == "local":
if (
titiler_endpoint is not None
and isinstance(titiler_endpoint, str)
and titiler_endpoint.lower() == "local"
):
titiler_endpoint = run_titiler(show_logs=False)
elif titiler_endpoint is None:
if os.environ.get("TITILER_ENDPOINT") is not None:
Expand All @@ -129,8 +133,12 @@ def check_titiler_endpoint(titiler_endpoint: Optional[str] = None) -> Any:
titiler_endpoint = PlanetaryComputerEndpoint()
else:
titiler_endpoint = "https://giswqs-titiler-endpoint.hf.space"
elif titiler_endpoint in ["planetary-computer", "pc"]:
elif isinstance(titiler_endpoint, str) and titiler_endpoint in [
"planetary-computer",
"pc",
]:
titiler_endpoint = PlanetaryComputerEndpoint()
# If it's already an endpoint object, just return it as-is

return titiler_endpoint

Expand Down Expand Up @@ -706,12 +714,12 @@ def stac_tile(
titiler_endpoint=titiler_endpoint,
)
except Exception as e:
titiler_endpoint = "https://giswqs-titiler-endpoint.hf.space"
fallback_endpoint = "https://giswqs-titiler-endpoint.hf.space"
stats = stac_stats(
collection=collection,
item=item,
assets=assets,
titiler_endpoint=titiler_endpoint,
titiler_endpoint=fallback_endpoint,
)

if "detail" not in stats:
Expand All @@ -732,8 +740,6 @@ def stac_tile(
]
)
kwargs["rescale"] = f"{percentile_2},{percentile_98}"
else:
print(stats["detail"]) # When operation times out.

else:
data = requests.get(url).json()
Expand Down Expand Up @@ -850,6 +856,26 @@ def stac_tile(
titiler_endpoint.url_for_stac_item(), params=kwargs, timeout=10
).json()

# Check if the response contains tiles
if "tiles" not in r:
# Try to provide helpful error message from the API response
if "detail" in r:
error_msg = f"TiTiler endpoint error: {r['detail']}"

Copilot AI Jan 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The common FastAPI/TiTiler validation error shape is a dict with a "detail" key whose value is a list of error dicts (e.g., {"detail": [{"loc": ..., "msg": ...}]}). In that case this branch will raise ValueError with the raw list repr, and the more user-friendly per-field formatting below is never used. Consider detecting isinstance(r.get('detail'), list) and formatting that list similarly (or delegating to a shared formatter) before falling back to str(r['detail']).

Suggested change
error_msg = f"TiTiler endpoint error: {r['detail']}"
detail = r.get("detail")
# Common FastAPI/TiTiler shape: {"detail": [{"loc": ..., "msg": ...}, ...]}
if isinstance(detail, list) and len(detail) > 0 and isinstance(detail[0], dict) and "msg" in detail[0]:
errors = []
for e in detail:
loc = "->".join(str(x) for x in e.get("loc", []))
msg = e.get("msg", "")
errors.append(f"{loc}: {msg}")
error_msg = f"TiTiler endpoint validation failed: {'; '.join(errors)}"
else:
error_msg = f"TiTiler endpoint error: {detail}"

Copilot uses AI. Check for mistakes.
elif isinstance(r, list) and len(r) > 0:
# Handle validation errors (list of error dicts)
if isinstance(r[0], dict) and "msg" in r[0]:
errors = []
for e in r:
loc = "->".join(str(x) for x in e.get("loc", []))
msg = e.get("msg", "")
errors.append(f"{loc}: {msg}")
error_msg = f"TiTiler endpoint validation failed: {'; '.join(errors)}"
else:
error_msg = f"TiTiler endpoint returned error list: {r}"
else:
error_msg = f"TiTiler endpoint returned unexpected response (missing 'tiles' key): {r}"
raise ValueError(error_msg)

tiles = r["tiles"][0]

# Convert 127.0.0.1 to localhost for Google Colab browser access
Expand Down Expand Up @@ -915,7 +941,29 @@ def stac_bounds(
titiler_endpoint.url_for_stac_bounds(), params=kwargs
).json()

bounds = r["bbox"]
# Try to get bounds from different response formats
if "bbox" in r:
bounds = r["bbox"]
elif "properties" in r and isinstance(r["properties"], dict):
# Handle GeoJSON Feature format (Planetary Computer)
# Try to get bounds from properties.{asset}.bounds
for key, value in r["properties"].items():
if isinstance(value, dict) and "bounds" in value:
bounds = value["bounds"]
break
else:
# Fallback to geometry bbox if available
if "geometry" in r and "coordinates" in r["geometry"]:
# Calculate bbox from geometry coordinates
coords = r["geometry"]["coordinates"][0] # Assuming Polygon
lons = [c[0] for c in coords]
lats = [c[1] for c in coords]
bounds = [min(lons), min(lats), max(lons), max(lats)]
else:
return None
else:
return None

return bounds
except Exception as e:
print(e)
Expand Down