rtwx/nexrad-browser/www/index.js

385 lines
11 KiB
JavaScript
Raw Normal View History

2023-11-03 20:33:22 +00:00
import * as wasm from "./wasm/nexrad_browser.js";
await wasm.default();
wasm.__nxrd_browser_init();
console.log("[JS] setup event listeners");
2023-11-04 04:08:31 +00:00
console.log("[JS] initializing the renderer");
const DEFAULT_PREFERENCES = {
RR: 5,
RREN: true,
2023-11-04 05:23:23 +00:00
FCS: 20
2023-11-04 04:08:31 +00:00
};
let preferences = DEFAULT_PREFERENCES;
2023-11-04 05:23:23 +00:00
function get_font_size() { return `${preferences.FCS}px monospace`; }
2023-11-04 04:08:31 +00:00
const canvas = document.getElementById("canvas");
2023-11-04 05:23:23 +00:00
function setupCanvas(canvas) {
// Get the device pixel ratio, falling back to 1.
var dpr = window.devicePixelRatio || 1;
// Get the size of the canvas in CSS pixels.
var rect = canvas.getBoundingClientRect();
// Give the canvas pixel dimensions of their CSS
// size * the device pixel ratio.
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
var ctx = canvas.getContext('2d');
// Scale all drawing operations by the dpr, so you
// don't have to worry about the difference.
ctx.scale(dpr, dpr);
return ctx;
}
function rescaleCanvas(canvas, ctx) {
var dpr = window.devicePixelRatio || 1;
// Get the size of the canvas in CSS pixels.
var rect = canvas.getBoundingClientRect();
// Give the canvas pixel dimensions of their CSS
// size * the device pixel ratio.
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
// Scale all drawing operations by the dpr, so you
// don't have to worry about the difference.
ctx.scale(dpr, dpr);
}
const ctx = setupCanvas(canvas);
2023-11-04 04:08:31 +00:00
const FIFTY_MILES = 0.0916;
let current_lat = 38.8977;
let current_long = -77.036560;
function calcRenderbox() {
return [current_long - FIFTY_MILES, current_lat - FIFTY_MILES, current_long + FIFTY_MILES, current_lat + FIFTY_MILES];
}
function latlongXY(lat, long) {
let bbox = calcRenderbox();
let pixelWidth = canvas.width;
let pixelHeight = canvas.height;
let bboxWidth = bbox[2] - bbox[0];
let bboxHeight = bbox[3] - bbox[1];
let widthPct = ( long - bbox[0] ) / bboxWidth;
let heightPct = ( lat - bbox[1] ) / bboxHeight;
let x = Math.floor( pixelWidth * widthPct );
let y = Math.floor( pixelHeight * ( 1 - heightPct ) );
return { x, y };
}
let x0 = 0;
let y0 = 0;
let xfull = canvas.width;
let yfull = canvas.height;
function recalcBorderCoordinates() {
let xy = latlongXY(current_lat, current_long);
x0 = xy.x - canvas.width;
y0 = xy.y - canvas.height;
2023-11-04 05:23:23 +00:00
xfull = x0 + canvas.width;
yfull = y0 + canvas.width;
}
const red = "#ef0000";
const green = "#4af626";
const white = "#dedede";
let blinkyColor = "#dedede";
setInterval(() => {
if (blinkyColor === red) {
blinkyColor = white;
} else {
blinkyColor = red;
}
}, 350);
function zulu() {
let date = new Date();
return `${date.getUTCHours().toString().padStart(2, '0')}:${date.getUTCMinutes().toString().padStart(2, '0')}:${date.getUTCSeconds().toString().padStart(2, '0')}Z`;
}
2023-11-05 02:38:49 +00:00
function vcp(vc) {
if (vc === 31) { return "CLEAR AIR MODE LONG PULSE"; }
else if (vc === 35) { return "CLEAR AIR MODE"; }
else if (vc === 12) { return "PRECIP MODE"; }
else if (vc === 112) { return "PRECIP MODE SZ-2 PRF"; }
else if (vc === 212) { return "PRECIP MODE SZ-2"; }
else if (vc === 215) { return "PRECIP MODE VERT"; }
}
2023-11-04 05:23:23 +00:00
let command_buf = "";
let buf_response_mode = false;
2023-11-04 21:38:51 +00:00
let radar_inoperative = true;
let site_string = "SITE INFORMATION UNAVAILABLE";
let icao = "INOP";
let selected_mode = "INOP";
let delay_string = "DELAY UNAVAILABLE";
let display_buf = [];
function recalcDisplayBuf() {
display_buf = [];
let line = 0;
for (let i = 0; i < command_buf.length; i++) {
let char = command_buf[i];
if (char === "\n") { line += 1; continue; }
if (display_buf.length < line+1) { display_buf[line] = ""; }
display_buf[line] += char;
}
}
function cmd_err(err) {
buf_response_mode = true;
command_buf = err;
recalcDisplayBuf();
}
let ar2 = undefined;
2023-11-05 02:38:49 +00:00
let new_file_available = false;
document.getElementById("file").onchange = () => {
new_file_available = true;
}
2023-11-04 21:38:51 +00:00
async function load() {
2023-11-05 02:38:49 +00:00
new_file_available = false;
2023-11-04 21:38:51 +00:00
const file = document.getElementById("file").files[0];
2023-11-05 02:38:49 +00:00
document.getElementById("file").value = null;
2023-11-04 21:38:51 +00:00
const reader = new FileReader();
reader.addEventListener('load', (event) => {
let data = event.target.result;
let loaded = wasm.load_ar2(data);
console.log(loaded);
cmd_err("");
ar2 = loaded;
});
reader.readAsArrayBuffer(file);
}
function exec(command) {
2023-11-04 05:23:23 +00:00
console.log("exec1!");
2023-11-04 21:38:51 +00:00
let tokens = command.split(" ");
if (tokens[0] === "MODE" && tokens[1] === "SET") {
let mode = tokens[2];
let valid_modes = ["REF", "VEL", "SW", "ZDR", "PHI", "RHO", "CFP"];
if (!valid_modes.includes(mode)) {
cmd_err("INVALID MODE");
return;
} else if (radar_inoperative) {
cmd_err("RADAR INOPERATIVE\nCANNOT SET MODE");
return;
} else {
selected_mode = mode;
return;
}
}
if (tokens[0] === "CLF" && tokens[1] === "OV") {
document.getElementById("file").click();
return;
}
if (tokens[0] === "CLF" && tokens[1] === "RELOAD") {
// TRIGGER RELOAD
cmd_err("SYSTEM PROCESSING");
load();
return;
}
if (command === "TEST UNSET INOP") {
radar_inoperative = false;
icao = "TEST";
site_string = "TEST VCP 000 TEST AIR MODE";
selected_mode = "REF";
return;
}
if (command === "TEST INVALID COMMAND") {
cmd_err("TEST SUCCESSFUL");
return;
}
cmd_err("UNRECOGNIZED COMMAND");
2023-11-04 05:23:23 +00:00
}
2023-11-04 21:38:51 +00:00
function shouldNewline() {
if (command_buf.startsWith("MODE SET")) { return true; }
return false;
}
let ar2_date = undefined;
function convertDate() {
if (ar2 !== undefined) {
let unix_timestamp_millis = 86400000 * ar2.volume_header_record.date + ar2.volume_header_record.time;
ar2_date = new Date(unix_timestamp_millis);
2023-11-04 05:23:23 +00:00
} else {
2023-11-04 21:38:51 +00:00
return undefined;
2023-11-04 05:23:23 +00:00
}
2023-11-04 04:08:31 +00:00
}
2023-11-04 21:38:51 +00:00
function reRender() {
if (ar2 !== undefined) {
radar_inoperative = false;
icao = ar2.volume_header_record.icao;
2023-11-05 02:38:49 +00:00
site_string = `${icao} VCP ${ar2.meta_rda_status_data.vcp} ${vcp(ar2.meta_rda_status_data.vcp)}`;
2023-11-04 21:38:51 +00:00
selected_mode = "REF";
} else {
radar_inoperative = true;
icao = "INOP";
site_string = "SITE INFORMATION UNAVAILABLE";
selected_mode = "INOP";
}
convertDate();
if (ar2_date !== undefined) {
const delay = Date.now() - ar2_date;
const SEC = 1000, MIN = 60 * SEC, HRS = 60 * MIN;
const humanDiff = `${Math.floor(delay/HRS)}:${Math.floor((delay%HRS)/MIN).toLocaleString('en-US', {minimumIntegerDigits: 2})}:${Math.floor((delay%MIN)/SEC).toLocaleString('en-US', {minimumIntegerDigits: 2})}`;
delay_string = "SITE DELAY " + humanDiff;
}
2023-11-04 05:23:23 +00:00
//document.getElementById("input-detection").focus();
rescaleCanvas(canvas, ctx);
2023-11-04 04:08:31 +00:00
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.resetTransform();
recalcBorderCoordinates();
let xy = latlongXY(current_lat, current_long);
ctx.translate(xy.x, xy.y);
// background (black, always)
ctx.fillStyle = "black";
2023-11-04 05:23:23 +00:00
ctx.fillRect(x0, y0, xfull * 2, yfull * 2);
ctx.font = get_font_size();
ctx.fillStyle = green;
2023-11-04 21:38:51 +00:00
ctx.fillText(`NEXRAD ${icao} ${zulu()}`, x0 + 50, y0 + 50);
2023-11-04 05:23:23 +00:00
2023-11-04 21:38:51 +00:00
if (radar_inoperative) {
ctx.fillStyle = blinkyColor;
ctx.fillText("RADR INOP NO DATA LOADED", x0 + 50, y0 + 50 + preferences.FCS);
ctx.fillStyle = green;
}
2023-11-05 02:38:49 +00:00
if (new_file_available) {
ctx.fillStyle = blinkyColor;
ctx.fillText("NEW DATA AVAIL RLD RQD", x0 + 50, y0 + 50 + preferences.FCS * 2);
ctx.fillStyle = green;
}
2023-11-04 05:23:23 +00:00
ctx.textAlign = "right";
ctx.fillText("RADAR SITE", xfull - 75, y0 + 50);
2023-11-04 21:38:51 +00:00
ctx.fillText(`${site_string}`, xfull - 75, y0 + 50 + preferences.FCS);
ctx.fillText(`${delay_string}`, xfull - 75, y0 + 50 + preferences.FCS * 2);
2023-11-04 05:23:23 +00:00
ctx.fillText("MODE", xfull - 75, y0 + canvas.height / 3);
2023-11-04 21:38:51 +00:00
// this hurts me physically
if (selected_mode === "REF") {
ctx.fillStyle = white;
ctx.fillText(">REF< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS);
ctx.fillStyle = green;
} else {
ctx.fillText(" REF ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS);
}
if (selected_mode === "VEL") {
ctx.fillStyle = white;
ctx.fillText(" >VEL< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS);
ctx.fillStyle = green;
} else {
ctx.fillText(" VEL ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS);
}
if (selected_mode === "SW") {
ctx.fillStyle = white;
ctx.fillText(" >SW <", xfull - 75, y0 + canvas.height / 3 + preferences.FCS);
ctx.fillStyle = green;
} else {
ctx.fillText(" SW ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS);
}
if (selected_mode === "ZDR") {
ctx.fillStyle = white;
ctx.fillText(">ZDR< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2);
ctx.fillStyle = green;
} else {
ctx.fillText(" ZDR ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2);
}
if (selected_mode === "PHI") {
ctx.fillStyle = white;
ctx.fillText(" >PHI< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2);
ctx.fillStyle = green;
} else {
ctx.fillText(" PHI ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2);
}
if (selected_mode === "RHO") {
ctx.fillStyle = white;
ctx.fillText(" >RHO<", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2);
ctx.fillStyle = green;
} else {
ctx.fillText(" RHO ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2);
}
if (selected_mode === "CFP") {
ctx.fillStyle = white;
ctx.fillText(" >CFP< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*3);
ctx.fillStyle = green;
} else {
ctx.fillText(" CFP ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*3);
}
if (radar_inoperative) {
ctx.fillStyle = red;
ctx.fillText(" >RADR INOP<", xfull - 75, y0 + canvas.height / 3 + preferences.FCS * 3);
ctx.fillStyle = green;
}
2023-11-04 05:23:23 +00:00
ctx.textAlign = "left";
2023-11-04 21:38:51 +00:00
for (let line = 0; line < display_buf.length; line++) {
ctx.fillText(display_buf[line], x0 + 50, y0 + canvas.height / 2 + (preferences.FCS * line));
}
}
setInterval(reRender, 10);
2023-11-04 05:23:23 +00:00
2023-11-04 21:38:51 +00:00
document.onkeyup = (e) => {
if (e.key.toUpperCase() === "ESCAPE") {
command_buf = "";
} else if (e.key.toUpperCase() === "ENTER") {
let command = command_buf.replace("\n", " ");
command_buf = "";
exec(command);
} else if (e.key.toUpperCase() === "BACKSPACE") {
command_buf = command_buf.slice(0, command_buf.length - 1);
} else if (e.key.toUpperCase() === " " && shouldNewline()) {
command_buf += "\n";
} else {
if (e.key.length !== 1) { return; }
if (buf_response_mode) {
buf_response_mode = false;
command_buf = "";
}
command_buf += e.key.toUpperCase();
}
recalcDisplayBuf();
reRender();
}