雨雲レーダーのタイルサーバー
雨雲レーダーのタイルを配信しています
https://storage.googleapis.com/precipitation-almap-430107/tiles/{z}/{x}/{y}.png
© OpenStreetMap contributors https://www.openstreetmap.org/copyright
仕様
現在時刻における情報を、3時間ごとに更新しています
gfs.????????/??/atmos/gfs.t??z.pgrb2.0p25.f???のうち、降水確率(Precipitation rate)のbandを使用しています
タイルを加工する必要性が出てきやすい
gdal_translateの-scaleによって、8ビットのグレースケールに量子化されています
zは0から2まで
3以降のズームで表示したい場合は、z=2のタイルを切り抜いて拡大する必要があります
タイルは、NOAA Global Forecast Systemの天気予報モデルから生成されています
Leafletでのレイヤー実装例
cloud-layer.ts
import L from "leaflet";
const tileCache = new Map<string, Promise<HTMLCanvasElement>>();
const createTile: L.GridLayer["createTile"] = function (
this: L.GridLayer,
coords,
done,
) {
const canvas = document.createElement("canvas");
const tileSize = this.getTileSize();
canvas.style.width = `${tileSize.x}px`;
canvas.style.height = `${tileSize.y}px`;
(async () => {
try {
const z = Math.min(coords.z, 2);
const zoomFactor = 2 ** (coords.z - z);
const x = Math.floor(coords.x / zoomFactor);
const y = Math.floor(coords.y / zoomFactor);
const tileCacheKey = `${x}-${y}-${z}`;
const cachedTile = tileCache.get(tileCacheKey);
const cachingTile = cachedTile ?? fetchTile({ x, y, z });
tileCache.set(tileCacheKey, cachingTile);
const tile = await cachingTile;
canvas.width = tile.width;
canvas.height = tile.height;
const canvasContext = canvas.getContext("2d");
if (!canvasContext) {
throw new Error("Failed to get canvas context");
}
canvasContext.drawImage(
tile,
-canvas.width * (coords.x - x * zoomFactor),
-canvas.height * (coords.y - y * zoomFactor),
canvas.width * zoomFactor,
canvas.height * zoomFactor,
);
done(undefined, canvas);
} catch (exception) {
if (!(exception instanceof Error)) {
throw exception;
}
console.error(exception);
done(exception, canvas);
}
})();
return canvas;
};
const options: L.GridLayerOptions = {
className: "leaflet-cloud-layer",
opacity: 0.375,
};
export const CloudLayer = L.GridLayer.extend({ createTile, options });
const fetchTile = async ({ x, y, z }: { x: number; y: number; z: number }) => {
const tileResponse = await fetch(
new URL(
`${encodeURIComponent(z)}/${encodeURIComponent(x)}/${encodeURIComponent(y)}.png`,
"https://storage.googleapis.com/precipitation-almap-430107/tiles/",
),
);
if (!tileResponse.ok) {
throw new Error(
`Failed to fetch tile: ${tileResponse.status} ${tileResponse.statusText}`,
);
}
const imageBitmap = await createImageBitmap(await tileResponse.blob());
const canvas = document.createElement("canvas");
canvas.width = imageBitmap.width;
canvas.height = imageBitmap.height;
const canvasContext = canvas.getContext("2d");
if (!canvasContext) {
throw new Error("Failed to get canvas context");
}
canvasContext.drawImage(imageBitmap, 0, 0);
const imageData = canvasContext.getImageData(
0,
0,
canvas.width,
canvas.height,
);
for (let i = 0; i < imageData.data.length; i += 4) {
const precipitation = imageData.data[i + 0];
imageData.data[i + 3] = precipitation >= 4 ? 255 : 0;
imageData.data[i + 0] =
imageData.data[i + 1] =
imageData.data[i + 2] =
255 - precipitation;
}
canvasContext.putImageData(imageData, 0, 0);
return canvas;
};
Rain ViewerのWeather Maps APIがサービス移行するため、代わりに自前でタイルを配信することにした
いままで使わせていただいてました