change to radar as a set of tiles for a smaller image download

This commit is contained in:
Matt Walsh
2025-06-05 14:01:49 -05:00
parent 39e8879697
commit 70fb3b5dbe
103 changed files with 150 additions and 18 deletions

View File

@@ -77,9 +77,9 @@ const removeDopplerRadarImageNoise = (RadarContext) => {
// is this pixel the old rgb?
if ((R === 0 && G === 0 && B === 0)
|| (R === 0 && G === 236 && B === 236)
|| (R === 1 && G === 160 && B === 246)
|| (R === 0 && G === 0 && B === 246)) {
|| (R === 0 && G === 236 && B === 236)
|| (R === 1 && G === 160 && B === 246)
|| (R === 0 && G === 0 && B === 246)) {
// change to your new rgb
// Transparent
@@ -124,14 +124,14 @@ const removeDopplerRadarImageNoise = (RadarContext) => {
B = 19;
A = 255;
} else if ((R === 214 && G === 0 && B === 0)
|| (R === 255 && G === 0 && B === 0)) {
|| (R === 255 && G === 0 && B === 0)) {
// Red
R = 171;
G = 14;
B = 14;
A = 255;
} else if ((R === 192 && G === 0 && B === 0)
|| (R === 255 && G === 0 && B === 255)) {
|| (R === 255 && G === 0 && B === 255)) {
// Brown
R = 115;
G = 31;
@@ -177,9 +177,44 @@ const mergeDopplerRadarImage = (mapContext, radarContext) => {
mapContext.drawImage(radarContext.canvas, 0, 0);
};
const tileSize = { x: 510, y: 320 };
const radarFullSize = { width: 2550, height: 1600 };
const radarFinalSize = { width: 640, height: 367 };
const radarSourceSize = { width: 480, height: 276 };
const scaling = {
width: radarFinalSize.width / radarSourceSize.width,
height: radarFinalSize.height / radarSourceSize.height,
};
// convert a pixel location to a file/tile combination
const pixelToFile = (xPixel, yPixel) => {
const xTile = Math.floor(xPixel / tileSize.x);
const yTile = Math.floor(yPixel / tileSize.y);
if (xTile < 0 || xTile > 9 || yTile < 0 || yTile > 9) return false;
return `${xTile.toFixed(0).padStart(2, '0')}-${yTile.toFixed(0).padStart(2, '0')}`;
};
// convert a pixel location in the overall map to a pixel location on the tile
const modTile = (xPixel, yPixel) => {
const x = Math.round(xPixel) % tileSize.x;
const y = Math.round(yPixel) % tileSize.y;
return { x, y };
};
const mapSizeToFinalSize = (x, y) => ({
x: Math.round(x * scaling.height),
y: Math.round(y * scaling.width),
});
export {
getXYFromLatitudeLongitudeDoppler,
getXYFromLatitudeLongitudeMap,
removeDopplerRadarImageNoise,
mergeDopplerRadarImage,
pixelToFile,
modTile,
mapSizeToFinalSize,
tileSize,
radarFinalSize,
radarFullSize,
};

View File

@@ -1,15 +1,15 @@
import * as utils from './radar-utils.mjs';
const radarFullSize = { width: 2550, height: 1600 };
const radarFinalSize = { width: 640, height: 367 };
import {
radarFinalSize, radarFullSize, pixelToFile, modTile, tileSize, removeDopplerRadarImageNoise, mapSizeToFinalSize,
} from './radar-utils.mjs';
const fetchAsBlob = async (url) => {
const response = await fetch(url);
return response.blob();
};
const baseMapImages = new Promise((resolve) => {
fetchAsBlob('/images/maps/radar.webp').then((blob) => {
const baseMapImages = (tile) => new Promise((resolve) => {
if (tile === false) resolve(false);
fetchAsBlob(`/images/maps/radar-tiles/${tile}.webp`).then((blob) => {
createImageBitmap(blob).then((imageBitmap) => {
// extract the black pixels to overlay on to the final image (boundaries)
const canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
@@ -28,13 +28,17 @@ const baseMapImages = new Promise((resolve) => {
context.putImageData(imageData, 0, 0);
resolve({
fullMap: imageBitmap,
base: imageBitmap,
overlay: canvas,
});
});
});
});
const drawOnBasemap = (baseContext, drawImage, positions) => {
baseContext.drawImage(drawImage, positions.sx, positions.sy, positions.sw, positions.sh, positions.dx, positions.dy, positions.dw, positions.dh);
};
onmessage = async (e) => {
const {
url, RADAR_HOST, OVERRIDES, radarSourceXY, sourceXY, offsetX, offsetY,
@@ -63,9 +67,93 @@ onmessage = async (e) => {
const radarContext = radarCanvas.getContext('2d');
radarContext.imageSmoothingEnabled = false;
// get the base map
const baseMaps = await baseMapImages;
baseContext.drawImage(baseMaps.fullMap, sourceXY.x, sourceXY.y, offsetX * 2, offsetY * 2, 0, 0, radarFinalSize.width, radarFinalSize.height);
// determine the basemap images needed
const baseMapTiles = [
pixelToFile(sourceXY.x, sourceXY.y),
pixelToFile(sourceXY.x + offsetX * 2, sourceXY.y),
pixelToFile(sourceXY.x, sourceXY.y + offsetY * 2),
pixelToFile(sourceXY.x + offsetX * 2, sourceXY.y + offsetY * 2),
];
// get the base maps
const baseMapsPromise = Promise.allSettled(baseMapTiles.map(baseMapImages));
// do some more calculations for assembling the tiles
// the tiles are arranged as follows, with the horizontal axis as x, and correlating with the second set of digits in the image file number
// T[0] T[1]
// T[2] T[3]
// tile 0 gets special treatment, it's placement is the basis for all downstream calculations
const t0Source = modTile(sourceXY.x, sourceXY.y);
const t0Width = tileSize.x - t0Source.x;
const t0Height = tileSize.y - t0Source.y;
const t0FinalSize = mapSizeToFinalSize(t0Width, t0Height);
// these will all be used again for the overlay, calculate them once here
const mapCoordinates = [];
// t[0]
mapCoordinates.push({
sx: t0Source.x,
sw: t0Width,
dx: 0,
dw: t0FinalSize.x,
sy: t0Source.y,
sh: t0Height,
dy: 0,
dh: t0FinalSize.y,
});
// t[1]
mapCoordinates.push({
sx: 0,
sw: tileSize.x - t0Width,
dx: t0FinalSize.x,
dw: mapSizeToFinalSize(tileSize.x - t0Width, 0).x,
sy: t0Source.y,
sh: t0Height,
dy: 0,
dh: t0FinalSize.y,
});
// t[2]
mapCoordinates.push({
sx: t0Source.x,
sw: t0Width,
dx: 0,
dw: t0FinalSize.x,
sy: 0,
sh: tileSize.y - t0Height,
dy: t0FinalSize.y,
dh: mapSizeToFinalSize(0, tileSize.y - t0Height).y,
});
// t[3]
mapCoordinates.push({
sx: 0,
sw: tileSize.x - t0Width,
dx: t0FinalSize.x,
dw: mapSizeToFinalSize(tileSize.x - t0Width, 0).x,
sy: 0,
sh: tileSize.y - t0Height,
dy: t0FinalSize.y,
dh: mapSizeToFinalSize(0, tileSize.y - t0Height).y,
});
// wait for the basemaps to arrive
const baseMaps = (await baseMapsPromise).map((map) => map.value ?? false);
// draw each tile if needed
drawOnBasemap(baseContext, baseMaps[0].base, mapCoordinates[0]);
if (mapCoordinates[1].dx < radarFinalSize.width && baseMaps[1]) {
drawOnBasemap(baseContext, baseMaps[1].base, mapCoordinates[1]);
}
if (mapCoordinates[2].dy < radarFinalSize.height && baseMaps[2]) {
drawOnBasemap(baseContext, baseMaps[2].base, mapCoordinates[2]);
if (mapCoordinates[1].dx < radarFinalSize.width && baseMaps[3]) {
drawOnBasemap(baseContext, baseMaps[3].base, mapCoordinates[3]);
}
}
// baseContext.drawImage(baseMaps.fullMap, sourceXY.x, sourceXY.y, offsetX * 2, offsetY * 2, 0, 0, radarFinalSize.width, radarFinalSize.height);
// test response
const radarResponse = await radarResponsePromise;
@@ -87,7 +175,7 @@ onmessage = async (e) => {
croppedRadarContext.drawImage(radarCanvas, radarSource.x, radarSource.y, croppedRadarCanvas.width, croppedRadarCanvas.height, 0, 0, croppedRadarCanvas.width, croppedRadarCanvas.height);
// clean the image
utils.removeDopplerRadarImageNoise(croppedRadarContext);
removeDopplerRadarImageNoise(croppedRadarContext);
// stretch the radar image
const stretchCanvas = new OffscreenCanvas(radarFinalSize.width, radarFinalSize.height);
@@ -97,8 +185,17 @@ onmessage = async (e) => {
// put the radar on the base map
baseContext.drawImage(stretchCanvas, 0, 0);
// put the road/boundaries overlay on the map
baseContext.drawImage(baseMaps.overlay, sourceXY.x, sourceXY.y, offsetX * 2, offsetY * 2, 0, 0, radarFinalSize.width, radarFinalSize.height);
// put the road/boundaries overlay on the map as needed
drawOnBasemap(baseContext, baseMaps[0].overlay, mapCoordinates[0]);
if (mapCoordinates[1].dx < radarFinalSize.width && baseMaps[1]) {
drawOnBasemap(baseContext, baseMaps[1].overlay, mapCoordinates[1]);
}
if (mapCoordinates[2].dy < radarFinalSize.height && baseMaps[2]) {
drawOnBasemap(baseContext, baseMaps[2].overlay, mapCoordinates[2]);
if (mapCoordinates[1].dx < radarFinalSize.width && baseMaps[3]) {
drawOnBasemap(baseContext, baseMaps[3].overlay, mapCoordinates[3]);
}
}
const processedRadar = baseCanvas.transferToImageBitmap();