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 @@
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
@@ -27,7 +29,7 @@
-
+
+
+
+ 1749006685171
+
+
+
+ 1749006685171
+
+
@@ -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;