wayfarer-weather/app/api/radar/route.ts

44 lines
1.6 KiB
TypeScript
Raw Permalink Normal View History

2026-10-31 21:20:16 -05:00
import { NextResponse, type NextRequest } from "next/server";
import { fetchJson } from "@/lib/weather/http";
import { errorResponse, forbidCrossSite } from "@/lib/weather/respond";
import type { RadarFrame } from "@/lib/weather/types";
/**
* GET /api/radar → RainViewer frames, oldest first, ready for Leaflet.
*
* RainViewer publishes an index of frames. Each frame's `path` is a hash, not a
* timestamp, so tile URLs must always be built from this index — never guessed.
* Docs: https://www.rainviewer.com/api/weather-maps-api.html
*/
interface RainViewerIndex {
host: string;
radar: { past: { time: number; path: string }[] };
}
// Last 7 frames ≈ one hour. Keeps tile requests well under RainViewer's
// limit of 100 requests per minute per IP.
const FRAME_COUNT = 7;
const COLOR_SCHEME = 2; // "Universal Blue", the only scheme still offered
const OPTIONS = "1_1"; // smoothed, snow shown in its own colors
export async function GET(request: NextRequest) {
const blocked = forbidCrossSite(request);
if (blocked) return blocked;
try {
const index = await fetchJson<RainViewerIndex>("https://api.rainviewer.com/public/weather-maps.json", "RainViewer", {
cacheSeconds: 300, // new frames arrive every 10 minutes
});
const frames: RadarFrame[] = index.radar.past.slice(-FRAME_COUNT).map((frame) => ({
time: new Date(frame.time * 1000).toISOString(),
url: `${index.host}${frame.path}/256/{z}/{x}/{y}/${COLOR_SCHEME}/${OPTIONS}.png`,
}));
return NextResponse.json(frames);
} catch (error) {
return errorResponse(error);
}
}