work
Some checks failed
Verify Latest Dependencies / Verify Latest Dependencies (push) Has been cancelled
build and test / wxbox - latest (push) Has been cancelled

This commit is contained in:
core 2025-06-09 21:05:18 -04:00
parent 26a2212f70
commit 522e212d94
Signed by: core
GPG key ID: FDBF740DADDCEECF
14 changed files with 157 additions and 316 deletions

36
.idea/workspace.xml generated
View file

@ -7,19 +7,21 @@
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="2d855648-9644-469a-afa2-59beb52bb1d6" name="Changes" comment="feat: wxbox-ar2 round two">
<list default="true" id="2d855648-9644-469a-afa2-59beb52bb1d6" name="Changes" comment="feat: optimizing wxbox-ar2">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/Cargo.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/benches/parse_benchmark.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/benches/parse_benchmark.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/flamegraph.svg" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/flamegraph.svg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/src/lib.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/lib/ToolbarProductSelector.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/ToolbarProductSelector.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/lib/generated_interop/digitalRadarData.ts" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/generated_interop/digitalRadarData.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/lib/map/default_palettes.ts" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/map/default_palettes.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/lib/map/fragment.glsl" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/map/fragment.glsl" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/lib/map/map.ts" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/map/map.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/src/main.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/src/parse/error.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/src/parse/error.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/src/parse/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/src/parse/mod.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/src/parse/scan.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/src/parse/scan.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/src/parse/types.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/src/parse/types.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/ar2/src/parse/util.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/ar2/src/parse/util.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/interchange/src/ar2.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/interchange/src/ar2.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/interchange/src/digitalRadarData.proto" beforeDir="false" afterPath="$PROJECT_DIR$/crates/interchange/src/digitalRadarData.proto" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/pal/src/parser.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/pal/src/parser.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/tiler/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/tiler/src/main.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/crates/tiler/src/nexrad.rs" beforeDir="false" afterPath="$PROJECT_DIR$/crates/tiler/src/nexrad.rs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -27,7 +29,7 @@
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="DarkyenusTimeTracker">
<option name="totalTimeSeconds" value="26794" />
<option name="totalTimeSeconds" value="31634" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
@ -148,6 +150,7 @@
<workItem from="1748132890039" duration="1214000" />
<workItem from="1748634364820" duration="2604000" />
<workItem from="1748807470356" duration="7770000" />
<workItem from="1749250715883" duration="365000" />
</task>
<task id="LOCAL-00001" summary="debugging">
<option name="closed" value="true" />
@ -245,7 +248,15 @@
<option name="project" value="LOCAL" />
<updated>1749001700392</updated>
</task>
<option name="localTasksCounter" value="13" />
<task id="LOCAL-00013" summary="feat: optimizing wxbox-ar2">
<option name="closed" value="true" />
<created>1749006685171</created>
<option name="number" value="00013" />
<option name="presentableId" value="LOCAL-00013" />
<option name="project" value="LOCAL" />
<updated>1749006685171</updated>
</task>
<option name="localTasksCounter" value="14" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -264,6 +275,7 @@
<MESSAGE value="feat: new ui" />
<MESSAGE value="bug: try to fix some multimap weirdness" />
<MESSAGE value="feat: wxbox-ar2 round two" />
<option name="LAST_COMMIT_MESSAGE" value="feat: wxbox-ar2 round two" />
<MESSAGE value="feat: optimizing wxbox-ar2" />
<option name="LAST_COMMIT_MESSAGE" value="feat: optimizing wxbox-ar2" />
</component>
</project>

View file

@ -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.
}
});

View file

@ -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<DigitalRadarData> {
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>): 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<DigitalRadarData> {
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<DigitalRadarData> {
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<Radial> {
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<Radial> {
}
create(value?: PartialMessage<Radial>): 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<Radial> {
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<Radial> {
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<MomentaryData> {
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>): 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<MomentaryData> {
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<MomentaryData> {
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;

View file

@ -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

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}
}
}
}

View file

@ -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::<Vec<_>>()
} else {
data.1.iter().map(|u| *u as usize).collect::<Vec<_>>()
}
.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 })
}
}

View file

@ -1,14 +1,17 @@
use std::collections::BTreeMap;
#[derive(Clone)]
pub struct Scan {
pub elevations: BTreeMap<usize, Elevation>,
}
#[derive(Clone)]
pub struct Elevation {
pub elevation_number: usize,
pub elevation_angle: f64,
pub radials: BTreeMap<usize, Radial>,
}
#[derive(Clone)]
pub struct Radial {
pub radial_number: usize,
pub azimuth_angle: f64,
@ -21,11 +24,13 @@ pub struct Radial {
pub phi: Option<MomentaryProduct>,
pub rho: Option<MomentaryProduct>,
}
#[derive(Clone)]
pub struct MomentaryProduct {
pub start_range_meters: isize,
pub data_spacing_meters: isize,
pub data: Vec<Gate>,
}
#[derive(Clone, Debug)]
pub enum Gate {
BelowThreshold,
RangeFolded,

View file

@ -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);

View file

@ -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;
}

View file

@ -119,8 +119,9 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
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<Palette, PaletteParseError> {
));
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<Palette, PaletteParseError> {
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) {

View file

@ -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),

View file

@ -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<DataId, Arc<wxbox_ar2::Scan>>;
pub type NexradDataCache = Cache<DataId, Arc<Ar2v>>;
pub type NexradTileCache = Cache<TileId, Arc<Vec<u8>>>;
pub type NexradDataConfig = HashMap<String, NexradDataSource>;
@ -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<AppState>,
) -> Result<impl IntoResponse, AppError> {
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<Arc<Scan>> {
) -> anyhow::Result<Arc<Ar2v>> {
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;