From cee9d47dc2047f60c8489ab28285829d751c68b2 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 25 May 2025 20:25:53 -0400 Subject: [PATCH] pre-unvicentys --- client/src/lib/Map.svelte | 360 +++++++++++++++++++------------ client/src/lib/map/fragment.glsl | 113 ++++++++-- crates/interchange/src/ar2.rs | 78 ++++--- 3 files changed, 369 insertions(+), 182 deletions(-) diff --git a/client/src/lib/Map.svelte b/client/src/lib/Map.svelte index 5999431..0334f4a 100644 --- a/client/src/lib/Map.svelte +++ b/client/src/lib/Map.svelte @@ -2,18 +2,22 @@ import maplibregl from 'maplibre-gl'; import ToolbarProductSelector from '$lib/ToolbarProductSelector.svelte'; import { Button } from '$lib/components/ui/button'; - import {mount, onMount} from 'svelte'; + import { mount, onMount } from 'svelte'; import type { LayerList } from './layerList'; import type { StationStatus } from './stationData'; - import {toast} from "svelte-sonner"; - import {Toggle} from "$lib/components/ui/toggle"; - import CloudAlertIcon from "@lucide/svelte/icons/cloud-alert"; - import {borderLUT, fillLUT} from "$lib/alertLayer"; - import AlertPopup from "$lib/AlertPopup.svelte"; - import {CifContainer} from "$lib/generated_interop/cifContainer"; - import fragmentSource from "$lib/map/fragment.glsl?raw"; - import vertexSource from "$lib/map/vertex.glsl?raw"; - import {degreesToRadians} from "@turf/turf"; + import { toast } from 'svelte-sonner'; + import { Toggle } from '$lib/components/ui/toggle'; + import CloudAlertIcon from '@lucide/svelte/icons/cloud-alert'; + import { borderLUT, fillLUT } from '$lib/alertLayer'; + import AlertPopup from '$lib/AlertPopup.svelte'; + import { CifContainer } from '$lib/generated_interop/cifContainer'; + import fragmentSource from '$lib/map/fragment.glsl?raw'; + import vertexSource from '$lib/map/vertex.glsl?raw'; + import { degreesToRadians } from '@turf/turf'; + import type { DigitalRadarData, Radial } from './generated_interop/digitalRadarData'; + + const BELOW_THRESHOLD = -9999.0; + const RANGE_FOLDED = -9998.0; interface Props { categories: LayerList; @@ -35,8 +39,8 @@ return; } - map.setLayoutProperty('alerts', 'visibility', showAlertLayer ? "visible" : "none"); - map.setLayoutProperty('alerts-outline', 'visibility', showAlertLayer ? "visible" : "none"); + map.setLayoutProperty('alerts', 'visibility', showAlertLayer ? 'visible' : 'none'); + map.setLayoutProperty('alerts-outline', 'visibility', showAlertLayer ? 'visible' : 'none'); } onMount(() => { @@ -70,42 +74,47 @@ } } - map.addSource('alerts', { type: 'geojson', data: 'https://api.weather.gov/alerts/active?status=actual' }); - map.addLayer({ - id: 'alerts', - type: 'fill', - source: 'alerts', - paint: { - // @ts-expect-error this type is too complicated - 'fill-color': fillLUT, - } - }, sym_layer); - map.addLayer({ - id: 'alerts-outline', - type: 'line', - source: 'alerts', - layout: { - 'line-join': 'round' + map.addLayer( + { + id: 'alerts', + type: 'fill', + source: 'alerts', + paint: { + // @ts-expect-error this type is too complicated + 'fill-color': fillLUT + } }, - paint: { - // @ts-expect-error this type is too complicated - 'line-color': borderLUT, - 'line-width': 3 - } - }, sym_layer); + sym_layer + ); + map.addLayer( + { + id: 'alerts-outline', + type: 'line', + source: 'alerts', + layout: { + 'line-join': 'round' + }, + paint: { + // @ts-expect-error this type is too complicated + 'line-color': borderLUT, + 'line-width': 3 + } + }, + sym_layer + ); let createPopup = (e, full: boolean) => { const randLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26)); const uniqid = randLetter + Date.now(); new maplibregl.Popup({ className: 'popup' }) - .setLngLat(e.lngLat) - .setHTML(`
`) + .setLngLat(e.lngLat) + .setHTML(`
`) - .addTo(map); + .addTo(map); mount(AlertPopup, { target: document.getElementById(uniqid)!, props: { @@ -114,30 +123,31 @@ } }); }; - map.on('click', 'alerts', (e) => { createPopup(e, true) }); + map.on('click', 'alerts', (e) => { + createPopup(e, true); + }); let currentFeatureCoordinates = undefined; const popup = new maplibregl.Popup({ closeButton: false, closeOnClick: false }); map.on('mousemove', 'alerts', (e) => { - // Change the cursor style as a UI indicator. - map.getCanvas().style.cursor = 'pointer'; + // Change the cursor style as a UI indicator. + map.getCanvas().style.cursor = 'pointer'; - // Populate the popup and set its coordinates - // based on the feature found. - const randLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26)); - const uniqid = randLetter + Date.now(); - - popup.setLngLat(e.lngLat).setHTML(`
`).addTo(map); - mount(AlertPopup, { - target: document.getElementById(uniqid)!, - props: { - showFull: false, - ...e.features[0].properties - } - }); + // Populate the popup and set its coordinates + // based on the feature found. + const randLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26)); + const uniqid = randLetter + Date.now(); + popup.setLngLat(e.lngLat).setHTML(`
`).addTo(map); + mount(AlertPopup, { + target: document.getElementById(uniqid)!, + props: { + showFull: false, + ...e.features[0].properties + } + }); }); map.on('mouseenter', 'alerts', () => { map.getCanvas().style.cursor = 'pointer'; @@ -147,110 +157,185 @@ popup.remove(); }); - let r = await fetch('http://localhost:3000/v2/nexrad/l2/KFSD/1/REF'); + let r = await fetch('http://localhost:3000/v2/nexrad/l2/KLZK/1/REF'); const buf = await r.arrayBuffer(); const container = CifContainer.fromBinary(new Uint8Array(buf)); - console.log('wxrad http://localhost:3000/v2/nexrad/l2/KFSD/1/REF: ', container); + console.log('wxrad http://localhost:3000/v2/nexrad/l2/KLZK/1/REF: ', container); + if (container.messageType.oneofKind == 'digitalRadarData') { + const drd: DigitalRadarData = container.messageType.digitalRadarData; + const dataLayer = { + id: 'dataGl', + type: 'custom', + onAdd(map, gl) { + const vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, vertexSource); + gl.compileShader(vertexShader); - const dataLayer = { - id: 'dataGl', - type: 'custom', - onAdd(map, gl) { - const vertexShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertexShader, vertexSource); - gl.compileShader(vertexShader); + const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, fragmentSource); + gl.compileShader(fragmentShader); - const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragmentShader, fragmentSource); - gl.compileShader(fragmentShader); + this.program = gl.createProgram(); + gl.attachShader(this.program, vertexShader); + gl.attachShader(this.program, fragmentShader); + gl.linkProgram(this.program); - this.program = gl.createProgram(); - gl.attachShader(this.program, vertexShader); - gl.attachShader(this.program, fragmentShader); - gl.linkProgram(this.program); + this.aPos = gl.getAttribLocation(this.program, 'a_pos'); - this.aPos = gl.getAttribLocation(this.program, 'a_pos'); - - /* + /* 110.574 km = 1 deg deg = 1/110.574 km 111.320 * cos(latitude) km = 1 deg */ - const lat = 43.58777778; - const long = -96.72944444; + gl.useProgram(this.program); + const lat = 34.8365; + gl.uniform1f(gl.getUniformLocation(this.program, 'radarLat'), lat); + const long = -92.262194; + gl.uniform1f(gl.getUniformLocation(this.program, 'radarLng'), long); - const radar_range_maximum = 560; // ish km - const degrees_of_lat = radar_range_maximum / 110.574; - const degrees_of_long = radar_range_maximum / (111.320 * Math.cos(degreesToRadians(lat))); + const radar_range_maximum = 560; // ish km + const degrees_of_lat = radar_range_maximum / 110.574; + const degrees_of_long = + radar_range_maximum / (111.32 * Math.cos(degreesToRadians(lat))); - // A-C - // B-D - const a = maplibregl.MercatorCoordinate.fromLngLat({ - lng: long - degrees_of_long, - lat: lat + degrees_of_lat - }); - const b = maplibregl.MercatorCoordinate.fromLngLat({ - lng: long - degrees_of_long, - lat: lat - degrees_of_lat - }); - const c = maplibregl.MercatorCoordinate.fromLngLat({ - lng: long + degrees_of_long, - lat: lat + degrees_of_lat - }); - const d = maplibregl.MercatorCoordinate.fromLngLat({ - lng: long + degrees_of_long, - lat: lat - degrees_of_lat - }); + // A-C + // B-D + const a = maplibregl.MercatorCoordinate.fromLngLat({ + lng: long - degrees_of_long, + lat: lat + degrees_of_lat + }); + const b = maplibregl.MercatorCoordinate.fromLngLat({ + lng: long - degrees_of_long, + lat: lat - degrees_of_lat + }); + const c = maplibregl.MercatorCoordinate.fromLngLat({ + lng: long + degrees_of_long, + lat: lat + degrees_of_lat + }); + const d = maplibregl.MercatorCoordinate.fromLngLat({ + lng: long + degrees_of_long, + lat: lat - degrees_of_lat + }); - - this.buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData( + this.buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData( gl.ARRAY_BUFFER, - new Float32Array([ - a.x, - a.y, - b.x, - b.y, - c.x, - c.y, - b.x, - b.y, - d.x, - d.y, - c.x, - c.y, - ]), + new Float32Array([a.x, a.y, b.x, b.y, c.x, c.y, b.x, b.y, d.x, d.y, c.x, c.y]), gl.STATIC_DRAW - ); - }, - render(gl, args) { - gl.useProgram(this.program); - gl.uniformMatrix4fv( + ); + + gl.useProgram(this.program); + + console.log(drd.radials.length); + gl.uniform1i(gl.getUniformLocation(this.program, 'azimuthCount'), drd.radials.length); + console.log('done'); + console.log( + gl.getUniform(this.program, gl.getUniformLocation(this.program, 'azimuthCount')) + ); + gl.uniform1fv( + gl.getUniformLocation(this.program, 'azimuthAngles'), + new Float32Array( + drd.radials.map((u) => { + return u.azimuthAngleDegrees; + }) + ) + ); + gl.uniform1f( + gl.getUniformLocation(this.program, 'azimuthSpacing'), + drd.radials[0].azimuthSpacingDegrees + ); + gl.uniform1f( + gl.getUniformLocation(this.program, 'startRange'), + drd.radials[0].product?.data?.startRange + ); + gl.uniform1f( + gl.getUniformLocation(this.program, 'sampleInterval'), + drd.radials[0].product?.data?.sampleInterval + ); + + 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 dataTexture = gl.createTexture(); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, dataTexture); + const level = 0; + const internalFormat = gl.R32F; + const width = 720; + const height = 1832; + const border = 0; + const format = gl.RED; + const type = gl.FLOAT; + // interleave the data + const data: number[] = []; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const radial = drd.radials[x]; + if (radial.product && radial.product.data) { + data.push(scaleMomentData(radial, radial.product.data.data[y])); + } + } + } + const typedData = new Float32Array(data); + gl.texImage2D( + gl.TEXTURE_2D, + level, + internalFormat, + width, + height, + border, + format, + type, + 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); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + const alignment = 1; + gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment); + + gl.uniform1i(gl.getUniformLocation(this.program, 'scaledData'), 0); + }, + render(gl, args) { + gl.useProgram(this.program); + gl.uniformMatrix4fv( gl.getUniformLocation(this.program, 'u_matrix'), false, args.defaultProjectionData.mainMatrix - ); - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.enableVertexAttribArray(this.aPos); - gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0); - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - gl.drawArrays(gl.TRIANGLES, 0, 6); - } + ); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.enableVertexAttribArray(this.aPos); + gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + }; + map.addLayer(dataLayer); } - map.addLayer(dataLayer); }); map.on('error', (e) => { console.error(e); toast.error('Data loading failed!'); }); - - }); @@ -274,20 +359,17 @@
-
- -
- +
diff --git a/client/src/lib/map/fragment.glsl b/client/src/lib/map/fragment.glsl index 970e243..0fecb24 100644 --- a/client/src/lib/map/fragment.glsl +++ b/client/src/lib/map/fragment.glsl @@ -224,23 +224,57 @@ Geodedic vincenty(Ellipsoid ellipsoid, GlobalCoordinates start, GlobalCoordinate } } +uniform int azimuthCount; +uniform float[720] azimuthAngles; +uniform float azimuthSpacing; +uniform sampler2D scaledData; +uniform float startRange; +uniform float sampleInterval; +uniform float radarLat; +uniform float radarLng; + +struct LocateRadialResult { + int radialIndex; + float radialDistance; + bool didFindRadial; +}; + +LocateRadialResult locateRadial(float forAzimuth) { + int closestRadial; + float bestDistance = 1.0; + bool foundAnything = false; + + for (int i = 0; i < azimuthCount; i++) { + float angle = azimuthAngles[i]; + float this_dist = abs(angle - forAzimuth); + //if (this_dist > azimuthSpacing) { + // continue; + //} + if (foundAnything) { + if (this_dist < bestDistance) { + closestRadial = i; + bestDistance = this_dist; + } + } else { + closestRadial = i; + bestDistance = this_dist; + foundAnything = true; + } + } + + return LocateRadialResult(closestRadial, bestDistance, foundAnything); +} + void main() { Ellipsoid WGS84 = fromAAndInverseF(6378137.0, 298.257223563); float lat; float lng; xyToLngLat(raw_pos.x, raw_pos.y, lat, lng); - float radarLat = 43.58777778; - float radarLng = -96.72944444; - - float a1; - float a2; - float s; - - // should be 43.58777778 - // 88 > x > 89 - // >40.0 - float delta = 0.01; + if (azimuthCount == 0) { + fragColor = vec4(0.0, 0.0, 0.0, 0.0); + return; + } GlobalCoordinates radar = GlobalCoordinates(degreeAngle(radarLat), degreeAngle(radarLng)); canonicalizeGlobalCoordinates(radar); @@ -250,11 +284,58 @@ void main() { Geodedic vincentyResult = vincenty(WGS84, radar, samplePoint); - // - // abs(lat-43.5877) 1832) { + fragColor = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + + vec2 coords = vec2(float(maybeRadial.radialIndex), float(gate)); + float rawValue = texture(scaledData, coords).r; + + if (rawValue > 80.0) { + fragColor = vec4(128.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 1.0); + } else if (rawValue > 70.0) { + fragColor = vec4(1.0, 1.0, 1.0, 1.0); + } else if (rawValue > 60.0) { + fragColor = vec4(1.0, 0.0, 1.0, 1.0); + } else if (rawValue > 50.0) { + fragColor = vec4(1.0, 0.0, 0.0, 1.0); + } else if (rawValue > 40.0) { + fragColor = vec4(1.0, 1.0, 0.0, 1.0); + } else if (rawValue > 30.0) { + fragColor = vec4(0.0, 1.0, 0.0, 1.0); + } else if (rawValue > 20.0) { + fragColor = vec4(64.0 / 255.0, 128.0 / 255.0, 1.0, 1.0); + } else if (rawValue > 10.0) { + fragColor = vec4(164.0 / 255.0, 164.0 / 255.0, 1.0, 1.0); } else { - fragColor = vec4(1.0, 0.0, 0.0, 0.5); + fragColor = vec4(0.0, 0.0, 0.0, 0.0); } } diff --git a/crates/interchange/src/ar2.rs b/crates/interchange/src/ar2.rs index 6511ee0..daae479 100644 --- a/crates/interchange/src/ar2.rs +++ b/crates/interchange/src/ar2.rs @@ -1,14 +1,13 @@ +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 crate::{cif, AsCif}; -use crate::cif::cif_container::MessageType; pub struct Ar2AsCifParams { pub requested_elevation: u8, - pub requested_product: String + pub requested_product: String, } - impl AsCif for Scan { type Params = Ar2AsCifParams; fn as_cif(&self, params: &Self::Params) -> cif::CifContainer { @@ -17,7 +16,10 @@ impl AsCif for Scan { digital_radar_data.vcp_number = self.coverage_pattern_number as i32; // find the elevation - let maybe_elevation = self.sweeps.iter().find(|u| u.elevation_number == params.requested_elevation); + let maybe_elevation = self + .sweeps + .iter() + .find(|u| u.elevation_number == params.requested_elevation); if let Some(elevation) = maybe_elevation { // parse out the radials let mut radials_we_can_use: Vec<(&Radial, &MomentData)> = vec![]; @@ -25,30 +27,38 @@ impl AsCif for Scan { match params.requested_product.as_str() { "REF" if radial.reflectivity.is_some() => { radials_we_can_use.push((&radial, &radial.reflectivity.as_ref().unwrap())); - }, + } "VEL" if radial.velocity.is_some() => { radials_we_can_use.push((&radial, &radial.velocity.as_ref().unwrap())); - }, + } "SW" if radial.spectrum_width.is_some() => { - radials_we_can_use.push((&radial, &radial.spectrum_width.as_ref().unwrap())); - }, + radials_we_can_use + .push((&radial, &radial.spectrum_width.as_ref().unwrap())); + } "ZDR" if radial.differential_reflectivity.is_some() => { - radials_we_can_use.push((&radial, &radial.differential_reflectivity.as_ref().unwrap())); - }, + radials_we_can_use + .push((&radial, &radial.differential_reflectivity.as_ref().unwrap())); + } "PHI" if radial.differential_phase.is_some() => { - radials_we_can_use.push((&radial, &radial.differential_phase.as_ref().unwrap())); - }, + radials_we_can_use + .push((&radial, &radial.differential_phase.as_ref().unwrap())); + } "RHO" if radial.correlation_coefficient.is_some() => { - radials_we_can_use.push((&radial, &radial.correlation_coefficient.as_ref().unwrap())); - }, + radials_we_can_use + .push((&radial, &radial.correlation_coefficient.as_ref().unwrap())); + } "CFP" if radial.specific_differential_phase.is_some() => { - radials_we_can_use.push((&radial, &radial.specific_differential_phase.as_ref().unwrap())); - }, + radials_we_can_use.push(( + &radial, + &radial.specific_differential_phase.as_ref().unwrap(), + )); + } _ => {} } } - digital_radar_data.radials = radials_we_can_use.iter() + digital_radar_data.radials = radials_we_can_use + .iter() .map(|u| { let mut radial = cif::digital_radar_data::Radial::default(); radial.elevation_number = u.0.elevation_number as i32; @@ -58,13 +68,26 @@ impl AsCif for Scan { 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(); + 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(); let mut moment_meta = cif::digital_radar_data::MomentaryMeta::default(); moment_meta.product_name = params.requested_product.clone(); @@ -78,9 +101,10 @@ impl AsCif for Scan { let mut moment_product = cif::digital_radar_data::MomentaryProduct::default(); moment_product.product_metadata = Some(moment_meta); + moment_product.data = Some(moment_data); radial.product = Some(moment_product); - + radial }) .collect() @@ -90,4 +114,4 @@ impl AsCif for Scan { container.message_type = Some(MessageType::DigitalRadarData(digital_radar_data)); container } -} \ No newline at end of file +}