diff --git a/.idea/workspace.xml b/.idea/workspace.xml index b81758e..c99acad 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -7,19 +7,21 @@ - + - - - - - + + + + + - - - + + + + + - @@ -264,6 +275,7 @@ - \ No newline at end of file diff --git a/client/src/lib/ToolbarProductSelector.svelte b/client/src/lib/ToolbarProductSelector.svelte index 1b3e092..62b2fe3 100644 --- a/client/src/lib/ToolbarProductSelector.svelte +++ b/client/src/lib/ToolbarProductSelector.svelte @@ -49,7 +49,7 @@ if (data.product == "REF") { palette = WXBOX_STANDARD_REF; } else if (data.product == "VEL") { - palette = WXBOX_STANDARD_REF; + palette = WXBOX_STANDARD_VEL; } else if (data.product == "SW") { palette = BENS_SW; } else if (data.product == "ZDR") { @@ -117,7 +117,7 @@ if (map.getLayer("data")) { map.removeLayer('data'); } - plotRadial(map, selectedSite, thisProduct, 1, stationLat, stationLong, thisPalette); + plotRadial(map, selectedSite, thisProduct,1, stationLat, stationLong, thisPalette); } // preserve the layer the user had if they click while already selected. } }); diff --git a/client/src/lib/generated_interop/digitalRadarData.ts b/client/src/lib/generated_interop/digitalRadarData.ts index 1a579dd..8896069 100644 --- a/client/src/lib/generated_interop/digitalRadarData.ts +++ b/client/src/lib/generated_interop/digitalRadarData.ts @@ -14,10 +14,6 @@ import { MessageType } from "@protobuf-ts/runtime"; * @generated from protobuf message cif.digitalRadarData.DigitalRadarData */ export interface DigitalRadarData { - /** - * @generated from protobuf field: int32 vcpNumber = 1 - */ - vcpNumber: number; /** * @generated from protobuf field: int32 elevationNumber = 2 */ @@ -31,10 +27,6 @@ export interface DigitalRadarData { * @generated from protobuf message cif.digitalRadarData.Radial */ export interface Radial { - /** - * @generated from protobuf field: fixed64 collectionTimestamp = 1 - */ - collectionTimestamp: bigint; /** * @generated from protobuf field: int32 azimuthNumber = 2 */ @@ -43,14 +35,6 @@ export interface Radial { * @generated from protobuf field: float azimuthAngleDegrees = 3 */ azimuthAngleDegrees: number; - /** - * @generated from protobuf field: float azimuthSpacingDegrees = 4 - */ - azimuthSpacingDegrees: number; - /** - * @generated from protobuf field: cif.digitalRadarData.RadialStatus radialStatus = 5 - */ - radialStatus: RadialStatus; /** * @generated from protobuf field: int32 elevationNumber = 6 */ @@ -90,14 +74,6 @@ export interface MomentaryMeta { * @generated from protobuf message cif.digitalRadarData.MomentaryData */ export interface MomentaryData { - /** - * @generated from protobuf field: float scale = 1 - */ - scale: number; - /** - * @generated from protobuf field: float offset = 2 - */ - offset: number; /** * @generated from protobuf field: int32 startRange = 3 */ @@ -107,55 +83,20 @@ export interface MomentaryData { */ sampleInterval: number; /** - * @generated from protobuf field: repeated int32 data = 5 + * @generated from protobuf field: repeated double data = 6 */ data: number[]; } -/** - * @generated from protobuf enum cif.digitalRadarData.RadialStatus - */ -export enum RadialStatus { - /** - * @generated from protobuf enum value: RADIAL_STATUS_UNKNOWN = 0; - */ - UNKNOWN = 0, - /** - * @generated from protobuf enum value: RADIAL_STATUS_ELEVATION_START = 1; - */ - ELEVATION_START = 1, - /** - * @generated from protobuf enum value: RADIAL_STATUS_INTERMEDIATE_RADIAL_DATA = 2; - */ - INTERMEDIATE_RADIAL_DATA = 2, - /** - * @generated from protobuf enum value: RADIAL_STATUS_ELEVATION_END = 3; - */ - ELEVATION_END = 3, - /** - * @generated from protobuf enum value: RADIAL_STATUS_VOLUME_SCAN_START = 4; - */ - VOLUME_SCAN_START = 4, - /** - * @generated from protobuf enum value: RADIAL_STATUS_VOLUME_SCAN_END = 5; - */ - VOLUME_SCAN_END = 5, - /** - * @generated from protobuf enum value: RADIAL_STATUS_ELEVATION_START_VCP_FINAL = 6; - */ - ELEVATION_START_VCP_FINAL = 6 -} // @generated message type with reflection information, may provide speed optimized methods class DigitalRadarData$Type extends MessageType { constructor() { super("cif.digitalRadarData.DigitalRadarData", [ - { no: 1, name: "vcpNumber", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, { no: 2, name: "elevationNumber", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, { no: 3, name: "radials", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => Radial } ]); } create(value?: PartialMessage): DigitalRadarData { const message = globalThis.Object.create((this.messagePrototype!)); - message.vcpNumber = 0; message.elevationNumber = 0; message.radials = []; if (value !== undefined) @@ -167,9 +108,6 @@ class DigitalRadarData$Type extends MessageType { while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { - case /* int32 vcpNumber */ 1: - message.vcpNumber = reader.int32(); - break; case /* int32 elevationNumber */ 2: message.elevationNumber = reader.int32(); break; @@ -188,9 +126,6 @@ class DigitalRadarData$Type extends MessageType { return message; } internalBinaryWrite(message: DigitalRadarData, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { - /* int32 vcpNumber = 1; */ - if (message.vcpNumber !== 0) - writer.tag(1, WireType.Varint).int32(message.vcpNumber); /* int32 elevationNumber = 2; */ if (message.elevationNumber !== 0) writer.tag(2, WireType.Varint).int32(message.elevationNumber); @@ -211,11 +146,8 @@ export const DigitalRadarData = new DigitalRadarData$Type(); class Radial$Type extends MessageType { constructor() { super("cif.digitalRadarData.Radial", [ - { no: 1, name: "collectionTimestamp", kind: "scalar", T: 6 /*ScalarType.FIXED64*/, L: 0 /*LongType.BIGINT*/ }, { no: 2, name: "azimuthNumber", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, { no: 3, name: "azimuthAngleDegrees", kind: "scalar", T: 2 /*ScalarType.FLOAT*/ }, - { no: 4, name: "azimuthSpacingDegrees", kind: "scalar", T: 2 /*ScalarType.FLOAT*/ }, - { no: 5, name: "radialStatus", kind: "enum", T: () => ["cif.digitalRadarData.RadialStatus", RadialStatus, "RADIAL_STATUS_"] }, { no: 6, name: "elevationNumber", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, { no: 7, name: "elevationDegrees", kind: "scalar", T: 2 /*ScalarType.FLOAT*/ }, { no: 8, name: "product", kind: "message", T: () => MomentaryProduct } @@ -223,11 +155,8 @@ class Radial$Type extends MessageType { } create(value?: PartialMessage): Radial { const message = globalThis.Object.create((this.messagePrototype!)); - message.collectionTimestamp = 0n; message.azimuthNumber = 0; message.azimuthAngleDegrees = 0; - message.azimuthSpacingDegrees = 0; - message.radialStatus = 0; message.elevationNumber = 0; message.elevationDegrees = 0; if (value !== undefined) @@ -239,21 +168,12 @@ class Radial$Type extends MessageType { while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { - case /* fixed64 collectionTimestamp */ 1: - message.collectionTimestamp = reader.fixed64().toBigInt(); - break; case /* int32 azimuthNumber */ 2: message.azimuthNumber = reader.int32(); break; case /* float azimuthAngleDegrees */ 3: message.azimuthAngleDegrees = reader.float(); break; - case /* float azimuthSpacingDegrees */ 4: - message.azimuthSpacingDegrees = reader.float(); - break; - case /* cif.digitalRadarData.RadialStatus radialStatus */ 5: - message.radialStatus = reader.int32(); - break; case /* int32 elevationNumber */ 6: message.elevationNumber = reader.int32(); break; @@ -275,21 +195,12 @@ class Radial$Type extends MessageType { return message; } internalBinaryWrite(message: Radial, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { - /* fixed64 collectionTimestamp = 1; */ - if (message.collectionTimestamp !== 0n) - writer.tag(1, WireType.Bit64).fixed64(message.collectionTimestamp); /* int32 azimuthNumber = 2; */ if (message.azimuthNumber !== 0) writer.tag(2, WireType.Varint).int32(message.azimuthNumber); /* float azimuthAngleDegrees = 3; */ if (message.azimuthAngleDegrees !== 0) writer.tag(3, WireType.Bit32).float(message.azimuthAngleDegrees); - /* float azimuthSpacingDegrees = 4; */ - if (message.azimuthSpacingDegrees !== 0) - writer.tag(4, WireType.Bit32).float(message.azimuthSpacingDegrees); - /* cif.digitalRadarData.RadialStatus radialStatus = 5; */ - if (message.radialStatus !== 0) - writer.tag(5, WireType.Varint).int32(message.radialStatus); /* int32 elevationNumber = 6; */ if (message.elevationNumber !== 0) writer.tag(6, WireType.Varint).int32(message.elevationNumber); @@ -413,17 +324,13 @@ export const MomentaryMeta = new MomentaryMeta$Type(); class MomentaryData$Type extends MessageType { constructor() { super("cif.digitalRadarData.MomentaryData", [ - { no: 1, name: "scale", kind: "scalar", T: 2 /*ScalarType.FLOAT*/ }, - { no: 2, name: "offset", kind: "scalar", T: 2 /*ScalarType.FLOAT*/ }, { no: 3, name: "startRange", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, { no: 4, name: "sampleInterval", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, - { no: 5, name: "data", kind: "scalar", repeat: 1 /*RepeatType.PACKED*/, T: 5 /*ScalarType.INT32*/ } + { no: 6, name: "data", kind: "scalar", repeat: 1 /*RepeatType.PACKED*/, T: 1 /*ScalarType.DOUBLE*/ } ]); } create(value?: PartialMessage): MomentaryData { const message = globalThis.Object.create((this.messagePrototype!)); - message.scale = 0; - message.offset = 0; message.startRange = 0; message.sampleInterval = 0; message.data = []; @@ -436,24 +343,18 @@ class MomentaryData$Type extends MessageType { while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { - case /* float scale */ 1: - message.scale = reader.float(); - break; - case /* float offset */ 2: - message.offset = reader.float(); - break; case /* int32 startRange */ 3: message.startRange = reader.int32(); break; case /* int32 sampleInterval */ 4: message.sampleInterval = reader.int32(); break; - case /* repeated int32 data */ 5: + case /* repeated double data */ 6: if (wireType === WireType.LengthDelimited) for (let e = reader.int32() + reader.pos; reader.pos < e;) - message.data.push(reader.int32()); + message.data.push(reader.double()); else - message.data.push(reader.int32()); + message.data.push(reader.double()); break; default: let u = options.readUnknownField; @@ -467,23 +368,17 @@ class MomentaryData$Type extends MessageType { return message; } internalBinaryWrite(message: MomentaryData, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { - /* float scale = 1; */ - if (message.scale !== 0) - writer.tag(1, WireType.Bit32).float(message.scale); - /* float offset = 2; */ - if (message.offset !== 0) - writer.tag(2, WireType.Bit32).float(message.offset); /* int32 startRange = 3; */ if (message.startRange !== 0) writer.tag(3, WireType.Varint).int32(message.startRange); /* int32 sampleInterval = 4; */ if (message.sampleInterval !== 0) writer.tag(4, WireType.Varint).int32(message.sampleInterval); - /* repeated int32 data = 5; */ + /* repeated double data = 6; */ if (message.data.length) { - writer.tag(5, WireType.LengthDelimited).fork(); + writer.tag(6, WireType.LengthDelimited).fork(); for (let i = 0; i < message.data.length; i++) - writer.int32(message.data[i]); + writer.double(message.data[i]); writer.join(); } let u = options.writeUnknownFields; diff --git a/client/src/lib/map/default_palettes.ts b/client/src/lib/map/default_palettes.ts index 1ece2c1..f47bebf 100644 --- a/client/src/lib/map/default_palettes.ts +++ b/client/src/lib/map/default_palettes.ts @@ -7,18 +7,26 @@ Color4: 60 255 0 255 255 128 0 128 255 Color4: 70 255 255 255 255 128 128 128 255 Color4: 80 128 128 128 255`; -export const WXBOX_STANDARD_VEL = `color: 0 128 128 128 -color: -10 0 81 0 -color: -30 0 169 0 -color: -50 105 253 103 -color: -60 220 215 252 -color: -120 120 120 253 -color: 10 99 0 0 -color: 40 239 7 0 -color: 45 255 88 1 -color: 55 255 181 1 -color: 100 255 255 0 -color: 120 255 255 255`; +export const WXBOX_STANDARD_VEL = `Units: KTS +Step: 10 +Product: BV +Scale: 1.942 +Color: 120 255 230 170 +Color: 100 255 255 255 +Color: 90 250 191 238 +Color: 65 254 39 201 +Color: 45 100 0 0 +Color: 20 234 41 55 +Color: 10 255 111 96 +Color: 5 245 154 154 +Color: 0 253 245 219 +Color: -5 99 255 141 +Color: -20 0 170 0 +Color: -45 0 70 0 +Color: -60 18 235 255 +Color: -70 8 140 255 +Color: -130 98 27 191 +RF: 96 2 97`; export const BENS_SW = `$$Ben Mitchell Product: SW diff --git a/client/src/lib/map/fragment.glsl b/client/src/lib/map/fragment.glsl index 14e0104..6962693 100644 --- a/client/src/lib/map/fragment.glsl +++ b/client/src/lib/map/fragment.glsl @@ -49,7 +49,10 @@ void main() { float start_distance = 2125.0; float interval = 250.0; - int number_of_gates = 1832; + + ivec2 data_size = textureSize(data, 0); + + int number_of_gates = data_size.y; if (distance_meters < start_distance) { fragColor = vec4(0,0,0,0); @@ -63,12 +66,21 @@ void main() { int gate_number = int(floor((distance_meters - 2125.0) / 250.0)); float rawValue = texelFetch(data, ivec2(gate_number, azimuthNumber), 0).r; + if (abs(rawValue - (-999.0)) < 0.01) { + fragColor = vec4(1,0,0,1); + return; // below threshold + } + if (abs(rawValue - (-9999.0)) < 0.01) { + fragColor = vec4(96.0/255.0,2.0/255.0,97.0/255.0,1); + return; // range folded + } + // colorize! for (int i = paletteLength; i > 0; i--) { vec2 rangeBounds = texelFetch(bounds, ivec2(0, i), 0).rg; float lower = rangeBounds.r; float higher = rangeBounds.g; - if (rawValue >= lower && rawValue < higher) { + if ((rawValue >= lower && rawValue < higher) || (rawValue >= higher && rawValue < lower)) { float mapped_end = higher - lower; float mapped_point = rawValue - lower; float t = mapped_point / mapped_end; @@ -76,13 +88,14 @@ void main() { vec4 color_start = texelFetch(colorData, ivec2(0, i), 0); vec4 color_end = texelFetch(color2Data, ivec2(0, i), 0); - - fragColor = mix(color_start, color_end, t); + if (fragColor == vec4(0,0,0,0)) { + fragColor = vec4(0,1,0,1); + } return; } } - fragColor = vec4(0,0,0,0); + fragColor = vec4(0,0,0,1); return; } \ No newline at end of file diff --git a/client/src/lib/map/map.ts b/client/src/lib/map/map.ts index 489d53c..8811b7d 100644 --- a/client/src/lib/map/map.ts +++ b/client/src/lib/map/map.ts @@ -113,37 +113,25 @@ export async function plotRadial(map: Map, site: string, product: string, elevat this.vertexCount = vertexData.length/2; gl.uniform1fv(gl.getUniformLocation(this.program, 'triangleAzimuthLookup'), new Float32Array(triangleAzimuthLookup)); - function scaleMomentData(radial: Radial, u: number): number { - if (!radial.product || !radial.product.data) return BELOW_THRESHOLD; - if (radial.product?.data?.scale == 0) { - return u; - } else { - if (u == 0) { - return BELOW_THRESHOLD; - } else if (u == 1) { - return RANGE_FOLDED; - } else { - return (u - radial.product.data?.offset) / radial.product?.data?.scale; - } - } - } const raw_data = []; let validRadials = 0; for (const radial of drd.radials) { if (radial.product && radial.product.data && radial.product.data.data) { for (const datapoint of radial.product.data.data) { - raw_data.push(scaleMomentData(radial, datapoint)); + raw_data.push(datapoint); } validRadials++; } } const data = new Float32Array(raw_data); + console.log(data); + console.log(data.length / validRadials); gl.activeTexture(gl.TEXTURE0 + 10); this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32F, 1832, validRadials, 0, gl.RED, gl.FLOAT, data); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32F, data.length / validRadials, validRadials, 0, gl.RED, gl.FLOAT, data); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); diff --git a/crates/ar2/src/main.rs b/crates/ar2/src/main.rs index 4c88765..a3808d1 100644 --- a/crates/ar2/src/main.rs +++ b/crates/ar2/src/main.rs @@ -11,5 +11,12 @@ fn main() { .init(); let mut f = File::open(env::args().nth(1).unwrap()).unwrap(); - println!("{:?}", Ar2v::read(&mut f)); + let ar = Ar2v::read(&mut f).unwrap(); + for elev in &ar.scan.elevations { + for radial in &elev.1.radials { + if radial.1.vel.is_some() { + println!("VEL! e{}r{}", elev.0, radial.0); + } + } + } } diff --git a/crates/ar2/src/parse/mod.rs b/crates/ar2/src/parse/mod.rs index e6c447b..44a646a 100644 --- a/crates/ar2/src/parse/mod.rs +++ b/crates/ar2/src/parse/mod.rs @@ -21,8 +21,8 @@ pub mod util; const DEFAULT_MESSAGE_SIZE: usize = 2432; const MESSAGE_BODY_SIZE: usize = DEFAULT_MESSAGE_SIZE - 12 - 16; -#[derive(Debug)] pub struct Ar2v { + pub scan: Scan, pub volume_header: VolumeHeaderRecord, } impl Ar2v { @@ -232,26 +232,26 @@ impl Ar2v { } // scale the data - let chunks = data.1.chunks(data.0.data_word_size as usize / 8); + let mp = MomentaryProduct { start_range_meters: data.0.data_moment_range as isize, data_spacing_meters: data.0.data_moment_range_sample_interval as isize, - data: chunks + data: if data.0.data_word_size == 16 { + data.1.chunks(2).map(|u| u16::from_be_bytes(u.try_into().unwrap()) as usize).collect::>() + } else { + data.1.iter().map(|u| *u as usize).collect::>() + } + .iter() .map(|u| { - let mut result = [0u8; 8]; - result[..u.len()].copy_from_slice(u); - usize::from_be_bytes(result) - }) - .map(|u| { - if u == 0 { + if *u == 0 { Gate::BelowThreshold - } else if u == 1 { + } else if *u == 1 { Gate::RangeFolded } else if data.0.scale == 0.0 { - Gate::Data(u as f64) + Gate::Data(*u as f64) } else { Gate::Data( - (u as f64) - data.0.offset as f64 / data.0.scale as f64, + ((*u as f64) - data.0.offset as f64) / data.0.scale as f64, ) } }) @@ -302,7 +302,7 @@ impl Ar2v { }; } - Ok(Self { volume_header: vhr }) + Ok(Self { volume_header: vhr, scan }) } } diff --git a/crates/ar2/src/parse/scan.rs b/crates/ar2/src/parse/scan.rs index edd7889..690f696 100644 --- a/crates/ar2/src/parse/scan.rs +++ b/crates/ar2/src/parse/scan.rs @@ -1,14 +1,17 @@ use std::collections::BTreeMap; +#[derive(Clone)] pub struct Scan { pub elevations: BTreeMap, } +#[derive(Clone)] pub struct Elevation { pub elevation_number: usize, pub elevation_angle: f64, pub radials: BTreeMap, } +#[derive(Clone)] pub struct Radial { pub radial_number: usize, pub azimuth_angle: f64, @@ -21,11 +24,13 @@ pub struct Radial { pub phi: Option, pub rho: Option, } +#[derive(Clone)] pub struct MomentaryProduct { pub start_range_meters: isize, pub data_spacing_meters: isize, pub data: Vec, } +#[derive(Clone, Debug)] pub enum Gate { BelowThreshold, RangeFolded, diff --git a/crates/interchange/src/ar2.rs b/crates/interchange/src/ar2.rs index aa54b05..b318927 100644 --- a/crates/interchange/src/ar2.rs +++ b/crates/interchange/src/ar2.rs @@ -2,58 +2,59 @@ use ordered_float::OrderedFloat; use crate::cif::cif_container::MessageType; use crate::{AsCif, cif}; use nexrad_decode::messages::digital_radar_data::RadialStatus; -use wxbox_ar2::{MomentData, Radial, Scan}; +use wxbox_ar2::parse::Ar2v; +use wxbox_ar2::parse::scan::{Gate, MomentaryProduct, Radial}; pub struct Ar2AsCifParams { pub requested_elevation: u8, pub requested_product: String, } -impl AsCif for Scan { +impl AsCif for Ar2v { type Params = Ar2AsCifParams; fn as_cif(&self, params: &Self::Params) -> cif::CifContainer { let mut digital_radar_data = cif::digital_radar_data::DigitalRadarData::default(); digital_radar_data.elevation_number = params.requested_elevation as i32; - digital_radar_data.vcp_number = self.coverage_pattern_number as i32; // find the elevation let mut maybe_elevation = self - .sweeps - .iter() - .find(|u| u.elevation_number == params.requested_elevation); + .scan.elevations + .get(&(params.requested_elevation as usize)); if let Some(elevation) = maybe_elevation { // parse out the radials - let mut radials_we_can_use: Vec<(&Radial, &MomentData)> = vec![]; + let mut radials_we_can_use: Vec<(&Radial, &MomentaryProduct)> = vec![]; let mut elevation = elevation.clone(); - elevation.radials.sort_by_key(|u| OrderedFloat(u.azimuth_angle_degrees)); - for radial in &elevation.radials { + + radials_we_can_use.sort_by_key(|u| OrderedFloat(u.0.azimuth_angle)); + + for (radial_num, radial) in &elevation.radials { match params.requested_product.as_str() { - "REF" if radial.reflectivity.is_some() => { - radials_we_can_use.push((&radial, &radial.reflectivity.as_ref().unwrap())); + "REF" if radial.refl.is_some() => { + radials_we_can_use.push((&radial, &radial.refl.as_ref().unwrap())); } - "VEL" if radial.velocity.is_some() => { - radials_we_can_use.push((&radial, &radial.velocity.as_ref().unwrap())); + "VEL" if radial.vel.is_some() => { + radials_we_can_use.push((&radial, &radial.vel.as_ref().unwrap())); } - "SW" if radial.spectrum_width.is_some() => { + "SW" if radial.sw.is_some() => { radials_we_can_use - .push((&radial, &radial.spectrum_width.as_ref().unwrap())); + .push((&radial, &radial.sw.as_ref().unwrap())); } - "ZDR" if radial.differential_reflectivity.is_some() => { + "ZDR" if radial.zdr.is_some() => { radials_we_can_use - .push((&radial, &radial.differential_reflectivity.as_ref().unwrap())); + .push((&radial, &radial.zdr.as_ref().unwrap())); } - "PHI" if radial.differential_phase.is_some() => { + "PHI" if radial.phi.is_some() => { radials_we_can_use - .push((&radial, &radial.differential_phase.as_ref().unwrap())); + .push((&radial, &radial.phi.as_ref().unwrap())); } - "RHO" if radial.correlation_coefficient.is_some() => { + "RHO" if radial.rho.is_some() => { radials_we_can_use - .push((&radial, &radial.correlation_coefficient.as_ref().unwrap())); + .push((&radial, &radial.rho.as_ref().unwrap())); } - "CFP" if radial.specific_differential_phase.is_some() => { + "CFP" if radial.cfp.is_some() => { radials_we_can_use.push(( &radial, - &radial.specific_differential_phase.as_ref().unwrap(), + &radial.cfp.as_ref().unwrap(), )); } _ => {} @@ -64,43 +65,22 @@ impl AsCif for Scan { .iter() .map(|u| { let mut radial = cif::digital_radar_data::Radial::default(); - radial.elevation_number = u.0.elevation_number as i32; - radial.azimuth_angle_degrees = u.0.azimuth_angle_degrees; - radial.azimuth_number = u.0.azimuth_number as i32; - radial.azimuth_spacing_degrees = u.0.azimuth_spacing_degrees; - radial.collection_timestamp = u.0.collection_timestamp as u64; - radial.elevation_degrees = u.0.elevation_number_degrees; - radial.radial_status = match u.0.radial_status { - RadialStatus::ElevationStart => { - cif::digital_radar_data::RadialStatus::ElevationStart - } - RadialStatus::IntermediateRadialData => { - cif::digital_radar_data::RadialStatus::IntermediateRadialData - } - RadialStatus::ElevationEnd => { - cif::digital_radar_data::RadialStatus::ElevationEnd - } - RadialStatus::VolumeScanStart => { - cif::digital_radar_data::RadialStatus::VolumeScanStart - } - RadialStatus::VolumeScanEnd => { - cif::digital_radar_data::RadialStatus::VolumeScanEnd - } - RadialStatus::ElevationStartVCPFinal => { - cif::digital_radar_data::RadialStatus::ElevationStartVcpFinal - } - } - .into(); + radial.elevation_number = elevation.elevation_number as i32; + radial.azimuth_angle_degrees = u.0.azimuth_angle as f32; + radial.azimuth_number = u.0.radial_number as i32; + radial.elevation_degrees = elevation.elevation_angle as f32; let mut moment_meta = cif::digital_radar_data::MomentaryMeta::default(); moment_meta.product_name = params.requested_product.clone(); let mut moment_data = cif::digital_radar_data::MomentaryData::default(); - moment_data.offset = u.1.offset; - moment_data.sample_interval = u.1.sample_interval as i32; - moment_data.scale = u.1.scale; - moment_data.start_range = u.1.start_range as i32; - moment_data.data = u.1.values.iter().map(|u| *u as i32).collect(); + moment_data.sample_interval = u.1.data_spacing_meters as i32; + moment_data.start_range = u.1.start_range_meters as i32; + moment_data.data = u.1.data.iter().map(|u| match u { + Gate::BelowThreshold => -999.0_f64, + Gate::RangeFolded => -9999.0_f64, + Gate::Data(d) => *d + }).collect(); let mut moment_product = cif::digital_radar_data::MomentaryProduct::default(); moment_product.product_metadata = Some(moment_meta); diff --git a/crates/interchange/src/digitalRadarData.proto b/crates/interchange/src/digitalRadarData.proto index 858e06c..5efe79e 100644 --- a/crates/interchange/src/digitalRadarData.proto +++ b/crates/interchange/src/digitalRadarData.proto @@ -3,20 +3,18 @@ syntax = "proto3"; package cif.digitalRadarData; message DigitalRadarData { - int32 vcpNumber = 1; + reserved 1; + int32 elevationNumber = 2; repeated Radial radials = 3; } message Radial { - fixed64 collectionTimestamp = 1; + reserved 1,4,5; int32 azimuthNumber = 2; float azimuthAngleDegrees = 3; - float azimuthSpacingDegrees = 4; - - RadialStatus radialStatus = 5; int32 elevationNumber = 6; float elevationDegrees = 7; @@ -24,16 +22,6 @@ message Radial { MomentaryProduct product = 8; } -enum RadialStatus { - RADIAL_STATUS_UNKNOWN = 0; - RADIAL_STATUS_ELEVATION_START = 1; - RADIAL_STATUS_INTERMEDIATE_RADIAL_DATA = 2; - RADIAL_STATUS_ELEVATION_END = 3; - RADIAL_STATUS_VOLUME_SCAN_START = 4; - RADIAL_STATUS_VOLUME_SCAN_END = 5; - RADIAL_STATUS_ELEVATION_START_VCP_FINAL = 6; -} - message MomentaryProduct { MomentaryMeta productMetadata = 1; MomentaryData data = 2; @@ -44,11 +32,10 @@ message MomentaryMeta { } message MomentaryData { - float scale = 1; - float offset = 2; + reserved 1, 2, 5; int32 startRange = 3; int32 sampleInterval = 4; - repeated int32 data = 5; + repeated double data = 6; } \ No newline at end of file diff --git a/crates/pal/src/parser.rs b/crates/pal/src/parser.rs index 7cefad5..099f153 100644 --- a/crates/pal/src/parser.rs +++ b/crates/pal/src/parser.rs @@ -119,8 +119,9 @@ pub fn parse(pal_str: &str) -> Result { let mut intermediate_with_infinities_added: Vec<(f64, u8, u8, u8, u8, u8, u8, u8, u8)> = vec![]; + let first_negative = parsed_data[0].0.is_sign_negative(); intermediate_with_infinities_added.push(( - f64::NEG_INFINITY, + if first_negative { f64::NEG_INFINITY } else { f64::INFINITY }, parsed_data[0].1, parsed_data[0].2, parsed_data[0].3, @@ -132,7 +133,7 @@ pub fn parse(pal_str: &str) -> Result { )); intermediate_with_infinities_added.append(&mut parsed_data); intermediate_with_infinities_added.push(( - f64::INFINITY, + if !first_negative { f64::NEG_INFINITY } else { f64::INFINITY }, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].1, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].2, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].3, @@ -143,6 +144,11 @@ pub fn parse(pal_str: &str) -> Result { intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].4, )); + if !first_negative { + // flip it + intermediate_with_infinities_added.reverse(); + } + let mut output = vec![]; for range in intermediate_with_infinities_added.windows(2) { diff --git a/crates/tiler/src/main.rs b/crates/tiler/src/main.rs index a0d3bb8..3c63c98 100644 --- a/crates/tiler/src/main.rs +++ b/crates/tiler/src/main.rs @@ -7,7 +7,7 @@ mod tiles; use crate::config::Config; use crate::grib2::{Grib2DataCache, Grib2TileCache, grib2_handler, grib2_metadata}; -use crate::nexrad::{NexradDataCache, NexradTileCache, nexrad_handler}; +use crate::nexrad::{NexradDataCache, NexradTileCache}; use axum::Router; use axum::http::Method; use axum::routing::get; @@ -68,7 +68,6 @@ async fn main() -> anyhow::Result<()> { let app = Router::new() .route("/grib2/{source}/{z}/{x}/{y}", get(grib2_handler)) .route("/grib2/{source}/metadata", get(grib2_metadata)) - .route("/nexrad/{source}/{site}/{z}/{x}/{y}", get(nexrad_handler)) .route( "/v2/nexrad/{source}/{site}/{sweep}/{product}", get(nexrad_data_handler), diff --git a/crates/tiler/src/nexrad.rs b/crates/tiler/src/nexrad.rs index 5436ed0..50f7b03 100644 --- a/crates/tiler/src/nexrad.rs +++ b/crates/tiler/src/nexrad.rs @@ -18,16 +18,17 @@ use std::collections::HashMap; use std::f64::consts::PI; use std::fmt::Debug; use std::io; -use std::io::ErrorKind; +use std::io::{Cursor, ErrorKind}; use std::sync::Arc; use tracing::{debug, info_span}; use wxbox_ar2::sites::wsr88d::{SITES, Wsr88dSite}; use wxbox_ar2::{MomentValue, Radial, Scan, Sweep, parse}; +use wxbox_ar2::parse::Ar2v; use wxbox_interchange::ar2::{Ar2AsCifParams}; use wxbox_interchange::{serialize_cif_message, AsCif}; use wxbox_pal::ColorPalette; -pub type NexradDataCache = Cache>; +pub type NexradDataCache = Cache>; pub type NexradTileCache = Cache>>; pub type NexradDataConfig = HashMap; @@ -42,67 +43,6 @@ impl Debug for NexradDataSource { } } -#[tracing::instrument(level = "info")] -pub async fn nexrad_handler( - Path((source, site, z, x, y)): Path<(String, String, usize, usize, String)>, - State(state): State, -) -> Result { - debug!("here"); - let mut y = y - .strip_suffix(".png") - .ok_or(io::Error::new(ErrorKind::InvalidInput, "invalid"))?; - let mut size = 256; - if y.ends_with("@2x") { - size = 512; - y = y.strip_suffix("@2x").unwrap(); - } - let y: usize = y.parse()?; - let tile_id = TileId { - source: format!("{source}/{site}"), - z, - x, - y, - size, - }; - - // do we have a pre-prepared tile? if so, return it immediately - if let Some(tile) = state.nexrad_tile_cache.get(&tile_id).await { - return Ok(([(header::CONTENT_TYPE, "image/png")], tile.as_ref().clone())); - } - - // is this even a valid data source? - let data_id = tile_id.data_id(); - let Some(ds) = state.config.data.nexrad.get(&source) else { - return Err(anyhow!("invalid/unknown nexrad state").into()); - }; - - // ok, so we don't have a tile image yet - // this means we are going to have to kick off a task to put that in the cache - // lets check if we have the raw data - let data = if !state.nexrad_data_cache.contains_key(&data_id) { - // we don't, so let's start by starting a task for that - load_nexrad_data(state.nexrad_data_cache, data_id, ds.clone(), &site).await? - } else { - state.nexrad_data_cache.get(&data_id).await.unwrap() - }; - - // we know we need to build the tile, so let's do that now - // it also returns it, so we can conveniently return it right now - let pixel_data = render_to_png( - state.nexrad_tile_cache.clone(), - data, - tile_id, - &site, - ds.clone(), - ) - .await?; - - Ok(( - [(header::CONTENT_TYPE, "image/png")], - pixel_data.as_ref().clone(), - )) -} - #[tracing::instrument(level = "info")] pub async fn nexrad_data_handler( Path((source, site, sweep, product)): Path<(String, String, u8, String)>, @@ -146,7 +86,7 @@ async fn load_nexrad_data( data_id: DataId, _data_source: NexradDataSource, site: &str, -) -> anyhow::Result> { +) -> anyhow::Result> { let _load_span = info_span!("load_nexrad_data"); let mut searchdate = Utc::now(); @@ -201,8 +141,9 @@ async fn load_nexrad_data( } let bytes = r.bytes().await?.to_vec(); + let mut r = Cursor::new(bytes); - let data = Arc::new(parse(bytes)?); + let data = Arc::new(Ar2v::read(&mut r)?); cache.insert(data_id, data.clone()).await;