some workings
This commit is contained in:
parent
ced2b9be8b
commit
55936f0af4
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/rtwx.iml" filepath="$PROJECT_DIR$/.idea/rtwx.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="EMPTY_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/nexrad-browser/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/nexrad2/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/nxar2/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/rtwx/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,38 @@
|
||||||
|
# `CLF ...`
|
||||||
|
Currently loaded file.
|
||||||
|
|
||||||
|
| Command | Description | Example |
|
||||||
|
|-----------|-------------------------------------------------------------------------------------|-----------|
|
||||||
|
| `CLF OV` | Overwrite the currently loaded file with a new file from the file selection dialog. | `CLF OV` |
|
||||||
|
| `CLF LD` | Load the currently loaded file into the scope. | `CLF LD` |
|
||||||
|
| `CLF CLR` | Clear the currently loaded file. | `CLF CLR` |
|
||||||
|
|
||||||
|
# `POS ...`
|
||||||
|
Position.
|
||||||
|
|
||||||
|
| Command | Description | Example |
|
||||||
|
|----------------------------------|-------------------------------------------------------------|----------------------------|
|
||||||
|
| `POS SHOW` | Show the current position in latitude/longitude. | `POS SHOW` |
|
||||||
|
| `POS SLL [latitude] [longitude]` | Set the position to the desired latitude/longitude. | `POS SLL 38.8977 -77.0366` |
|
||||||
|
| `POS SITE [site]` | Set the position to the position of the desired radar site. | `POS SITE KRAX` |
|
||||||
|
|
||||||
|
# `PREF` and `PREFSET`
|
||||||
|
Preferences.
|
||||||
|
|
||||||
|
| Command | Description | Example |
|
||||||
|
|---------------------------|---------------------------------------------------|--------------------------------|
|
||||||
|
| `PREF SHOW [pref]` | Show the value of the preference. | `PREF SHOW RR` |
|
||||||
|
| `PREF SET [pref] [value]` | Set the value of the preference. | `PREF SET RR 5` |
|
||||||
|
| `PREF CLR [pref]` | Clear the prefernece (return to default value) | `PREF CLEAR RR` |
|
||||||
|
| `PREFSET SAVE [name]` | Save the current setup of the scope as a prefset. | `PREF SET SAVE krax_tornadoes` |
|
||||||
|
| `PREFSET RCL [name]` | Load the scope from the given prefset. | `PREF SET RCL krax_tornadoes` |
|
||||||
|
| `PREFSET CLR [name]` | Remove the saved prefset. | `PREF SET CLR krax_tornadoes` |
|
||||||
|
| `PREFSET CLRA` | Remove all saved prefsets. | `PREF SET CLRA` |
|
||||||
|
|
||||||
|
## Available Preferences
|
||||||
|
|
||||||
|
| Preference | Description | Value |
|
||||||
|
|------------|---------------------|----------------------------|
|
||||||
|
| RR | Range Rings | 1-100 (default 5) |
|
||||||
|
| RREN | Range Rings Enabled | true, false (default true) |
|
||||||
|
| FCS | Font Character Size | 1-100 (default 12) |
|
|
@ -2,36 +2,27 @@
|
||||||
<head>
|
<head>
|
||||||
<title>NEXRAD Browser</title>
|
<title>NEXRAD Browser</title>
|
||||||
<style>
|
<style>
|
||||||
.grid-parent {
|
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2fr .5fr;
|
#canvas {
|
||||||
grid-template-rows: 1fr;
|
width: 100%;
|
||||||
grid-column-gap: 5px;
|
|
||||||
grid-row-gap: 0px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas-container {
|
body {
|
||||||
border-right: 2px solid black;
|
margin: 0;
|
||||||
height: 100%;
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="grid-parent">
|
<canvas id="canvas">
|
||||||
<div class="canvas-container">
|
Something went wrong while loading the canvas. Sorry :/
|
||||||
<canvas id="canvas"></canvas>
|
</canvas>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="file">Upload Archive II file</label>
|
|
||||||
<input type="file" id="file" />
|
|
||||||
<button id="go">Load</button>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<p style="font-family: 'vt323', monospace;">FONT TEST!</p>
|
||||||
|
|
||||||
<script src="bootstrap.js"></script>
|
<script src="bootstrap.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -4,14 +4,69 @@ wasm.__nxrd_browser_init();
|
||||||
|
|
||||||
console.log("[JS] setup event listeners");
|
console.log("[JS] setup event listeners");
|
||||||
|
|
||||||
const fileSelector = document.getElementById('file');
|
console.log("[JS] initializing the renderer");
|
||||||
|
|
||||||
document.getElementById("go").addEventListener("click", (e) => {
|
|
||||||
const file = fileSelector.files[0];
|
|
||||||
const reader = new FileReader();
|
const DEFAULT_PREFERENCES = {
|
||||||
reader.addEventListener('load', (event) => {
|
RR: 5,
|
||||||
let data = event.target.result;
|
RREN: true,
|
||||||
wasm.load_ar2(data);
|
FCS: 16
|
||||||
});
|
};
|
||||||
reader.readAsArrayBuffer(file);
|
let preferences = DEFAULT_PREFERENCES;
|
||||||
});
|
|
||||||
|
function get_font_size() { return `${preferences.FCS}px `; }
|
||||||
|
|
||||||
|
const canvas = document.getElementById("canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
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;
|
||||||
|
xfull = xy.x + (canvas.width / 2);
|
||||||
|
yfull = xy.y + (canvas.height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.font = get_font_size();
|
||||||
|
ctx.resetTransform();
|
||||||
|
|
||||||
|
recalcBorderCoordinates();
|
||||||
|
|
||||||
|
let xy = latlongXY(current_lat, current_long);
|
||||||
|
|
||||||
|
ctx.translate(xy.x, xy.y);
|
||||||
|
|
||||||
|
// background (black, always)
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fillRect(x0, y0, xfull, yfull);
|
||||||
|
})
|
|
@ -6,6 +6,13 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
#bzip2-rs = "0.1"
|
|
||||||
bzip2 = "0.4"
|
bzip2-rs = { version = "0.1", optional = true }
|
||||||
|
bzip2 = { version = "0.4", optional = true }
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["bzip-impl-bzip2-rs"]
|
||||||
|
bzip-impl-libbzip2 = ["bzip2"]
|
||||||
|
bzip-impl-bzip2-rs = ["bzip2-rs"]
|
|
@ -0,0 +1,26 @@
|
||||||
|
#[cfg(feature = "bzip-impl-libbzip2")]
|
||||||
|
pub mod _internal_ffi {
|
||||||
|
use bzip2::read::BzDecoder;
|
||||||
|
|
||||||
|
pub type BzipImpl<T> = BzDecoder<T>;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "bzip-impl-bzip2-rs")]
|
||||||
|
pub mod _internal_pure {
|
||||||
|
use bzip2_rs::DecoderReader;
|
||||||
|
|
||||||
|
pub type BzipImpl<T> = DecoderReader<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "bzip-impl-bzip2-rs", feature = "bzip-impl-libbzip2"))]
|
||||||
|
compile_error!("Only one bzip2 implementation can be used. Please select bzip-impl-bzip2-rs or bzip-impl-libbzip2, but not both.");
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "bzip-impl-bzip2-rs", feature = "bzip-impl-libbzip2")))]
|
||||||
|
compile_error!("A bzip2 implementation is required. Please select either bzip-impl-bzip2-rs or bzip-impl-libbzip2.");
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "wasm32", feature = "bzip-impl-libbzip2"))]
|
||||||
|
compile_error!("bzip-impl-libbzip2 is not compatible with WebAssembly targets. Please use bzip-impl-bzip2-rs instead.");
|
||||||
|
|
||||||
|
#[cfg(feature = "bzip-impl-libbzip2")]
|
||||||
|
pub type BzipDecoder<T> = _internal_ffi::BzipImpl<T>;
|
||||||
|
#[cfg(feature = "bzip-impl-bzip2-rs")]
|
||||||
|
pub type BzipDecoder<T> = _internal_pure::BzipImpl<T>;
|
|
@ -6,11 +6,12 @@ use std::fmt::{Display, Formatter};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
use bzip2::read::BzDecoder;
|
|
||||||
//use bzip2_rs::DecoderReader;
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
|
use crate::bzip::BzipDecoder;
|
||||||
|
use crate::message::{LEGACY_CTM_HEADER_LEN, MESSAGE_BODY_SIZE, MESSAGE_HEADER_SIZE, MessageHeader};
|
||||||
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod bzip;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Nexrad2Chunk {
|
pub struct Nexrad2Chunk {
|
||||||
|
@ -77,26 +78,6 @@ impl Error for NexradParseError {}
|
||||||
|
|
||||||
pub const NEXRAD2_META_CHUNK_FIXED_LENGTH: usize = 325888;
|
pub const NEXRAD2_META_CHUNK_FIXED_LENGTH: usize = 325888;
|
||||||
|
|
||||||
pub const MESSAGE_HEADER_SIZE: usize = 2 + 1 + 1 + 2 + 2 + 4 + 2 + 2;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MessageHeader {
|
|
||||||
pub message_size: u16,
|
|
||||||
pub rda_redundant_channel: u8,
|
|
||||||
pub message_type: u8,
|
|
||||||
pub message_sequence_number: u16,
|
|
||||||
pub julian_date: u16,
|
|
||||||
pub millis_after_midnight: u32,
|
|
||||||
pub num_of_message_segments: u16,
|
|
||||||
pub message_segment_num: u16
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Message {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const LEGACY_TCM_HEADER_LENGTH: i64 = 12;
|
|
||||||
|
|
||||||
pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk, NexradParseError> {
|
pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk, NexradParseError> {
|
||||||
let mut volume_header = [0u8; 24];
|
let mut volume_header = [0u8; 24];
|
||||||
|
@ -141,7 +122,7 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
|
||||||
let mut compressed_buf = vec![0u8; ldm_size];
|
let mut compressed_buf = vec![0u8; ldm_size];
|
||||||
cursor.read_exact(&mut compressed_buf).map_err(|e| NexradParseError::LdmReadFailed(e))?;
|
cursor.read_exact(&mut compressed_buf).map_err(|e| NexradParseError::LdmReadFailed(e))?;
|
||||||
|
|
||||||
let mut bz_decoder = BzDecoder::new(Cursor::new(compressed_buf));
|
let mut bz_decoder = BzipDecoder::new(Cursor::new(compressed_buf));
|
||||||
|
|
||||||
let mut decompressed_buf = vec![];
|
let mut decompressed_buf = vec![];
|
||||||
|
|
||||||
|
@ -151,11 +132,17 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
|
||||||
|
|
||||||
let mut decompressed = Cursor::new(decompressed_buf);
|
let mut decompressed = Cursor::new(decompressed_buf);
|
||||||
|
|
||||||
|
let mut messages = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
decompressed.seek(SeekFrom::Current(LEGACY_TCM_HEADER_LENGTH)).map_err(|e| NexradParseError::TcmChunkReadFailed(e))?;
|
decompressed.seek(SeekFrom::Current(LEGACY_CTM_HEADER_LEN as i64)).map_err(|e| NexradParseError::TcmChunkReadFailed(e))?;
|
||||||
|
|
||||||
let mut message_header = [0u8; MESSAGE_HEADER_SIZE];
|
let mut message_header = [0u8; MESSAGE_HEADER_SIZE];
|
||||||
decompressed.read_exact(&mut message_header).map_err(|e| NexradParseError::TcmChunkReadFailed(e))?;
|
match decompressed.read_exact(&mut message_header) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) if matches!(e.kind(), io::ErrorKind::UnexpectedEof) => { break; },
|
||||||
|
Err(e) => return Err(NexradParseError::TcmChunkReadFailed(e))
|
||||||
|
}
|
||||||
|
|
||||||
let message_header = MessageHeader {
|
let message_header = MessageHeader {
|
||||||
message_size: u16::from_be_bytes(message_header[0..2].try_into().unwrap()),
|
message_size: u16::from_be_bytes(message_header[0..2].try_into().unwrap()),
|
||||||
|
@ -170,9 +157,15 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
|
||||||
|
|
||||||
debug!("Message: {:#?}", message_header);
|
debug!("Message: {:#?}", message_header);
|
||||||
|
|
||||||
break;
|
let mut body_buf = vec![0u8; MESSAGE_BODY_SIZE];
|
||||||
|
|
||||||
|
decompressed.read_exact(&mut body_buf).map_err(|e| NexradParseError::TcmChunkReadFailed(e))?;
|
||||||
|
|
||||||
|
messages += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("{} messages loaded from chunk", messages);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,34 @@
|
||||||
|
pub const MSG_RDA_STATUS_DATA: u8 = 2;
|
||||||
|
pub const MSG_RDA_PERFORMANCE_DATA: u8 = 3;
|
||||||
|
pub const MSG_RDA_VOLUME_COVERAGE_DATA: u8 = 5;
|
||||||
|
pub const MSG_RDA_CLUTTER_FILTER_BYPASS_MAP: u8 = 13;
|
||||||
|
pub const MSG_RDA_CLUTTER_MAP_DATA: u8 = 15;
|
||||||
|
pub const MSG_RDA_ADAPTABLE_PARAMETERS: u8 = 18;
|
||||||
|
pub const MSG_MODEL_DATA_MESSAGE: u8 = 29;
|
||||||
|
pub const MSG_DIGITAL_RADAR_DATA_GENERIC: u8 = 31;
|
||||||
|
|
||||||
|
pub const LEGACY_CTM_HEADER_LEN: usize = 12;
|
||||||
|
pub const MESSAGE_HEADER_SIZE: usize = 16;
|
||||||
|
pub const DEFAULT_MESSAGE_SIZE: usize = 2432;
|
||||||
|
pub const MESSAGE_BODY_SIZE: usize = DEFAULT_MESSAGE_SIZE - LEGACY_CTM_HEADER_LEN - MESSAGE_HEADER_SIZE;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MessageHeader {
|
||||||
|
pub message_size: u16,
|
||||||
|
pub rda_redundant_channel: u8,
|
||||||
|
pub message_type: u8,
|
||||||
|
pub message_sequence_number: u16,
|
||||||
|
pub julian_date: u16,
|
||||||
|
pub millis_after_midnight: u32,
|
||||||
|
pub num_of_message_segments: u16,
|
||||||
|
pub message_segment_num: u16
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
|
/// Type02
|
||||||
|
RDAStatusData {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue