release: bump version to 0.53.0
This commit is contained in:
@@ -68,6 +68,7 @@ TERRAIN_TILE_BATCH_MAX_ITEMS = 128
|
||||
TERRAIN_TILE_BATCH_CONCURRENCY = 16
|
||||
_terrain_tile_cache: OrderedDict[tuple[int, int, int], tuple[bytes, str, dict[str, str]]] = OrderedDict()
|
||||
VESSEL_NAME_FALLBACK_PATTERN = re.compile(r"^mmsi\s*\d+$", re.IGNORECASE)
|
||||
VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED = True
|
||||
|
||||
|
||||
class TerrariumTileRequest(BaseModel):
|
||||
@@ -941,6 +942,39 @@ def _merge_vessel_features(
|
||||
}
|
||||
|
||||
|
||||
async def _load_legacy_vessel_snapshot_features(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
bbox: tuple[float, float, float, float] | None,
|
||||
limit: int,
|
||||
) -> list[dict[str, Any]]:
|
||||
latest_positions = select(
|
||||
VesselPosition.mmsi.label("mmsi"),
|
||||
func.max(VesselPosition.received_at).label("received_at"),
|
||||
)
|
||||
if bbox is not None:
|
||||
lon_min, lat_min, lon_max, lat_max = bbox
|
||||
latest_positions = latest_positions.where(VesselPosition.lon >= lon_min)
|
||||
latest_positions = latest_positions.where(VesselPosition.lon <= lon_max)
|
||||
latest_positions = latest_positions.where(VesselPosition.lat >= lat_min)
|
||||
latest_positions = latest_positions.where(VesselPosition.lat <= lat_max)
|
||||
|
||||
latest_positions = latest_positions.group_by(VesselPosition.mmsi).subquery()
|
||||
result = await db.execute(
|
||||
select(VesselPosition, VesselStatic)
|
||||
.join(
|
||||
latest_positions,
|
||||
(VesselPosition.mmsi == latest_positions.c.mmsi)
|
||||
& (VesselPosition.received_at == latest_positions.c.received_at),
|
||||
)
|
||||
.outerjoin(VesselStatic, VesselStatic.mmsi == VesselPosition.mmsi)
|
||||
.order_by(VesselPosition.received_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
legacy_geojson = convert_vessels_to_geojson(list(result.all()))
|
||||
return legacy_geojson.get("features", [])[:limit]
|
||||
|
||||
|
||||
def _build_vessel_stats(features: List[dict[str, Any]]) -> dict[str, Any]:
|
||||
by_type: dict[str, int] = {}
|
||||
underway = 0
|
||||
@@ -2073,33 +2107,6 @@ async def _load_compute_center_record(db: AsyncSession, source_id: str) -> Colle
|
||||
return result.scalars().first()
|
||||
|
||||
|
||||
@router.get("/geo/vessels")
|
||||
async def get_vessels_geojson(
|
||||
bbox: Optional[str] = Query(
|
||||
None,
|
||||
description="Viewport bbox as lon_min,lat_min,lon_max,lat_max",
|
||||
),
|
||||
type: Optional[str] = Query(
|
||||
None,
|
||||
description="Comma-separated vessel types: cargo,tanker,passenger,fishing,military,other",
|
||||
),
|
||||
limit: Optional[int] = Query(
|
||||
None,
|
||||
ge=0,
|
||||
description="Maximum vessel features to return. Omit or pass 0 for no limit.",
|
||||
),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Legacy vessel endpoint removed in favor of /api/v1/vessels/snapshot."""
|
||||
raise HTTPException(
|
||||
status_code=410,
|
||||
detail=(
|
||||
"Legacy vessel GeoJSON endpoint has been removed. "
|
||||
"Use /api/v1/vessels/snapshot with bbox, zoom, and limit."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def _load_raw_vessel_snapshot_features(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
@@ -2121,18 +2128,38 @@ async def _load_raw_vessel_snapshot_features(
|
||||
observed_since=observed_since,
|
||||
)
|
||||
raw_geojson = convert_aggregated_vessels_to_geojson(aggregated_vessels)
|
||||
features = raw_geojson.get("features", [])
|
||||
raw_features = raw_geojson.get("features", [])
|
||||
features = raw_features
|
||||
legacy_features: list[dict[str, Any]] = []
|
||||
legacy_fallback_used = False
|
||||
if not raw_features and VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED:
|
||||
legacy_features = await _load_legacy_vessel_snapshot_features(
|
||||
db,
|
||||
bbox=bbox,
|
||||
limit=limit,
|
||||
)
|
||||
features, _merge_diagnostics = _merge_vessel_features(raw_features, legacy_features)
|
||||
legacy_fallback_used = bool(legacy_features)
|
||||
|
||||
return features, {
|
||||
"raw_feature_count": len(features),
|
||||
"raw_feature_count": len(raw_features),
|
||||
"raw_unique_mmsi": len(
|
||||
{
|
||||
key
|
||||
for key in (_feature_mmsi_key(feature) for feature in features)
|
||||
for key in (_feature_mmsi_key(feature) for feature in raw_features)
|
||||
if key is not None
|
||||
}
|
||||
),
|
||||
"legacy_feature_count": 0,
|
||||
"legacy_backfilled_mmsi": 0,
|
||||
"legacy_feature_count": len(legacy_features),
|
||||
"legacy_backfilled_mmsi": len(
|
||||
{
|
||||
key
|
||||
for key in (_feature_mmsi_key(feature) for feature in legacy_features)
|
||||
if key is not None
|
||||
}
|
||||
),
|
||||
"legacy_fallback_enabled": VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED,
|
||||
"legacy_fallback_used": legacy_fallback_used,
|
||||
"final_unique_mmsi": len(
|
||||
{
|
||||
key
|
||||
@@ -2466,7 +2493,12 @@ async def get_visualization_geo_summary(db: AsyncSession = Depends(get_db)):
|
||||
select(func.count(func.distinct(VesselPosition.mmsi)))
|
||||
)
|
||||
legacy_unique_mmsi = int(legacy_unique_result.scalar() or 0)
|
||||
vessel_count = max(raw_unique_mmsi, legacy_unique_mmsi)
|
||||
legacy_fallback_active = (
|
||||
VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED
|
||||
and raw_unique_mmsi == 0
|
||||
and legacy_unique_mmsi > 0
|
||||
)
|
||||
vessel_count = legacy_unique_mmsi if legacy_fallback_active else raw_unique_mmsi
|
||||
aisstream_health = await db.get(AISSourceHealth, "aisstream_vessels")
|
||||
|
||||
return {
|
||||
@@ -2477,6 +2509,8 @@ async def get_visualization_geo_summary(db: AsyncSession = Depends(get_db)):
|
||||
"satellite_count": satellite_count,
|
||||
"compute_center_count": compute_center_count,
|
||||
"vessel_count": vessel_count,
|
||||
"vessel_count_source": "legacy_fallback" if legacy_fallback_active else "raw_recent",
|
||||
"vessel_legacy_fallback_enabled": VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED,
|
||||
"vessel_raw_unique_mmsi": raw_unique_mmsi,
|
||||
"vessel_raw_unique_window_hours": raw_unique_window_hours,
|
||||
"vessel_legacy_unique_mmsi": legacy_unique_mmsi,
|
||||
|
||||
Reference in New Issue
Block a user