feat: fix singlesite rendering, works on all sites now
This commit is contained in:
parent
62a301a4ef
commit
1e4545ada9
4 changed files with 356 additions and 347 deletions
20
.idea/workspace.xml
generated
20
.idea/workspace.xml
generated
|
@ -7,11 +7,11 @@
|
||||||
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="2d855648-9644-469a-afa2-59beb52bb1d6" name="Changes" comment="feat: client-side rendering">
|
<list default="true" id="2d855648-9644-469a-afa2-59beb52bb1d6" name="Changes" comment="feat: proper csr">
|
||||||
|
<change afterPath="$PROJECT_DIR$/client/src/lib/map/map.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/client/src/lib/Map.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/Map.svelte" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/client/src/lib/Map.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/Map.svelte" 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/ToolbarProductSelector.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/lib/ToolbarProductSelector.svelte" 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" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="DarkyenusTimeTracker">
|
<component name="DarkyenusTimeTracker">
|
||||||
<option name="totalTimeSeconds" value="15293" />
|
<option name="totalTimeSeconds" value="17863" />
|
||||||
</component>
|
</component>
|
||||||
<component name="FileTemplateManagerImpl">
|
<component name="FileTemplateManagerImpl">
|
||||||
<option name="RECENT_TEMPLATES">
|
<option name="RECENT_TEMPLATES">
|
||||||
|
@ -138,6 +138,7 @@
|
||||||
<workItem from="1748032143420" duration="1307000" />
|
<workItem from="1748032143420" duration="1307000" />
|
||||||
<workItem from="1748132890039" duration="1214000" />
|
<workItem from="1748132890039" duration="1214000" />
|
||||||
<workItem from="1748634364820" duration="2604000" />
|
<workItem from="1748634364820" duration="2604000" />
|
||||||
|
<workItem from="1748807470356" duration="2817000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="debugging">
|
<task id="LOCAL-00001" summary="debugging">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
|
@ -179,7 +180,15 @@
|
||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1748709699337</updated>
|
<updated>1748709699337</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="6" />
|
<task id="LOCAL-00006" summary="feat: proper csr">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1748807673267</created>
|
||||||
|
<option name="number" value="00006" />
|
||||||
|
<option name="presentableId" value="LOCAL-00006" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1748807673267</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="7" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
@ -192,6 +201,7 @@
|
||||||
<MESSAGE value="mostly functional v2 rendering" />
|
<MESSAGE value="mostly functional v2 rendering" />
|
||||||
<MESSAGE value="csr rendering v2" />
|
<MESSAGE value="csr rendering v2" />
|
||||||
<MESSAGE value="feat: client-side rendering" />
|
<MESSAGE value="feat: client-side rendering" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="feat: client-side rendering" />
|
<MESSAGE value="feat: proper csr" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat: proper csr" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -10,18 +10,7 @@
|
||||||
import CloudAlertIcon from '@lucide/svelte/icons/cloud-alert';
|
import CloudAlertIcon from '@lucide/svelte/icons/cloud-alert';
|
||||||
import { borderLUT, fillLUT } from '$lib/alertLayer';
|
import { borderLUT, fillLUT } from '$lib/alertLayer';
|
||||||
import AlertPopup from '$lib/AlertPopup.svelte';
|
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';
|
|
||||||
import {forwardGeodesic, radToDeg} from "$lib/vincenty";
|
|
||||||
import {degToRad} from "$lib/vincenty.js";
|
|
||||||
import init_palette_parser_wasm, {parse_palette} from "$lib/map/pal/wxbox_pal";
|
|
||||||
import {WXBOX_STANDARD_REF} from "$lib/map/default_palettes";
|
|
||||||
|
|
||||||
const BELOW_THRESHOLD = -9999.0;
|
|
||||||
const RANGE_FOLDED = -9998.0;
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
categories: LayerList;
|
categories: LayerList;
|
||||||
|
@ -161,213 +150,6 @@
|
||||||
map.getCanvas().style.cursor = '';
|
map.getCanvas().style.cursor = '';
|
||||||
popup.remove();
|
popup.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
let r = await fetch('http://localhost:3000/v2/nexrad/l2/KLWX/1/REF');
|
|
||||||
const buf = await r.arrayBuffer();
|
|
||||||
|
|
||||||
const container = CifContainer.fromBinary(new Uint8Array(buf));
|
|
||||||
console.log('wxrad http://localhost:3000/v2/nexrad/l2/KLWX/1/REF: ', container);
|
|
||||||
if (container.messageType.oneofKind == 'digitalRadarData') {
|
|
||||||
const drd: DigitalRadarData = container.messageType.digitalRadarData;
|
|
||||||
|
|
||||||
await init_palette_parser_wasm();
|
|
||||||
|
|
||||||
const dataLayer = {
|
|
||||||
id: 'dataGl',
|
|
||||||
type: 'custom',
|
|
||||||
maxZoom: 13,
|
|
||||||
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);
|
|
||||||
|
|
||||||
this.program = gl.createProgram();
|
|
||||||
gl.attachShader(this.program, vertexShader);
|
|
||||||
gl.attachShader(this.program, fragmentShader);
|
|
||||||
gl.linkProgram(this.program);
|
|
||||||
gl.useProgram(this.program);
|
|
||||||
|
|
||||||
this.aPos = gl.getAttribLocation(this.program, 'a_pos');
|
|
||||||
|
|
||||||
const lat = 38.976111;
|
|
||||||
const long = -77.4875;
|
|
||||||
|
|
||||||
gl.uniform1f(gl.getUniformLocation(this.program, 'radarLat'), lat);
|
|
||||||
gl.uniform1f(gl.getUniformLocation(this.program, 'radarLng'), long);
|
|
||||||
|
|
||||||
const vertexData: number[] = [];
|
|
||||||
const triangleAzimuthLookup: number[] = [];
|
|
||||||
|
|
||||||
const radarCoordinate = maplibregl.MercatorCoordinate.fromLngLat({
|
|
||||||
lng: long,
|
|
||||||
lat: lat
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i =0; i < drd.radials.length; i++) {
|
|
||||||
const radial = drd.radials[i];
|
|
||||||
|
|
||||||
if (radial.product && radial.product.data && radial.product.data.data) {
|
|
||||||
const angle_a = radial.azimuthAngleDegrees;
|
|
||||||
const angle_b = (i == drd.radials.length - 1) ? drd.radials[0].azimuthAngleDegrees : drd.radials[i+1].azimuthAngleDegrees;
|
|
||||||
|
|
||||||
const start_range = radial.product.data.startRange; // m
|
|
||||||
const sample_interval = radial.product.data.sampleInterval; // m
|
|
||||||
const number_of_samples = radial.product.data.data.length;
|
|
||||||
const range = sample_interval * number_of_samples + start_range; // m
|
|
||||||
// add an extra sample for good measure
|
|
||||||
const padded_range = range + sample_interval; // m
|
|
||||||
|
|
||||||
const in_weird_mercator_units = padded_range / radarCoordinate.meterInMercatorCoordinateUnits();
|
|
||||||
const pointAX = radarCoordinate.x + in_weird_mercator_units * Math.sin(degToRad(-angle_a + 180));
|
|
||||||
const pointAY = radarCoordinate.y + in_weird_mercator_units * Math.cos(degToRad(-angle_a + 180));
|
|
||||||
const pointA = new maplibregl.MercatorCoordinate(pointAX, pointAY, 0);
|
|
||||||
|
|
||||||
const pointBX = radarCoordinate.x + in_weird_mercator_units * Math.sin(degToRad(-angle_b + 180));
|
|
||||||
const pointBY = radarCoordinate.y + in_weird_mercator_units * Math.cos(degToRad(-angle_b + 180));
|
|
||||||
const pointB = new maplibregl.MercatorCoordinate(pointBX, pointBY, 0);
|
|
||||||
|
|
||||||
vertexData.push(radarCoordinate.x);
|
|
||||||
vertexData.push(radarCoordinate.y);
|
|
||||||
vertexData.push(pointA.x);
|
|
||||||
vertexData.push(pointA.y);
|
|
||||||
vertexData.push(pointB.x);
|
|
||||||
vertexData.push(pointB.y);
|
|
||||||
triangleAzimuthLookup.push(angle_a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)));
|
|
||||||
|
|
||||||
this.buffer = gl.createBuffer();
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
||||||
gl.bufferData(
|
|
||||||
gl.ARRAY_BUFFER,
|
|
||||||
new Float32Array(vertexData),
|
|
||||||
gl.STATIC_DRAW
|
|
||||||
);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
validRadials++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new Float32Array(raw_data);
|
|
||||||
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.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);
|
|
||||||
gl.uniform1i(gl.getUniformLocation(this.program, 'data'), 10);
|
|
||||||
|
|
||||||
// parse, transform, and send over the palette
|
|
||||||
const palette = parse_palette(WXBOX_STANDARD_REF);
|
|
||||||
gl.uniform1i(gl.getUniformLocation(this.program, 'paletteLength'), palette.length);
|
|
||||||
const rboundsData: number[] = [];
|
|
||||||
const rcolorData: number[] = [];
|
|
||||||
const rcolor2Data: number[] = [];
|
|
||||||
for (const paletteEntry of palette) {
|
|
||||||
const [bounds, color] = paletteEntry;
|
|
||||||
rboundsData.push(...bounds);
|
|
||||||
rcolorData.push(...[color[0].red, color[0].green, color[0].blue, color[0].alpha]);
|
|
||||||
rcolor2Data.push(...[color[1].red, color[1].green, color[1].blue, color[1].alpha]);
|
|
||||||
}
|
|
||||||
// create the bounds texture first
|
|
||||||
const boundsData = new Float32Array(rboundsData);
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + 11);
|
|
||||||
this.boundsTexture = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.boundsTexture);
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, 1, palette.length, 0, gl.RG, gl.FLOAT, boundsData);
|
|
||||||
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);
|
|
||||||
gl.uniform1i(gl.getUniformLocation(this.program, 'bounds'), 11);
|
|
||||||
// then the colorData texture
|
|
||||||
const colorData = new Uint8Array(rcolorData);
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + 12);
|
|
||||||
this.colorDataTexture = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.colorDataTexture);
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, palette.length, 0, gl.RGBA, gl.UNSIGNED_BYTE, colorData);
|
|
||||||
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);
|
|
||||||
gl.uniform1i(gl.getUniformLocation(this.program, 'colorData'), 12);
|
|
||||||
// then the colorData2 texture
|
|
||||||
const color2Data = new Uint8Array(rcolor2Data);
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + 13);
|
|
||||||
this.color2DataTexture = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.color2DataTexture);
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, palette.length, 0, gl.RGBA, gl.UNSIGNED_BYTE, color2Data);
|
|
||||||
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);
|
|
||||||
gl.uniform1i(gl.getUniformLocation(this.program, 'color2Data'), 13);
|
|
||||||
},
|
|
||||||
render(gl, args) {
|
|
||||||
gl.enable(gl.SAMPLE_COVERAGE);
|
|
||||||
gl.sampleCoverage(1.0, false);
|
|
||||||
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + 10);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + 11);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.boundsTexture);
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + 12);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.colorDataTexture);
|
|
||||||
gl.activeTexture(gl.TEXTURE0 + 13);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.color2DataTexture);
|
|
||||||
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, this.vertexCount);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
map.addLayer(dataLayer, 'alerts');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
map.on('error', (e) => {
|
map.on('error', (e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||||
import { buttonVariants } from '$lib/components/ui/button';
|
import { buttonVariants } from '$lib/components/ui/button';
|
||||||
import type { LayerList, PrimaryLayer } from './layerList';
|
import type {Category, LayerList, PrimaryLayer} from './layerList';
|
||||||
import { stationGeojson, type StationStatus } from './stationData';
|
import { stationGeojson, type StationStatus } from './stationData';
|
||||||
|
import {plotRadial} from "$lib/map/map";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selectedPrimaryLayer: string | null;
|
selectedPrimaryLayer: string | null;
|
||||||
|
@ -29,6 +30,118 @@
|
||||||
let currentlySelectingFor: string | null = $state(null);
|
let currentlySelectingFor: string | null = $state(null);
|
||||||
let currentlySelectingCategory: PrimaryLayer[] | null = $state(null);
|
let currentlySelectingCategory: PrimaryLayer[] | null = $state(null);
|
||||||
let open: boolean = $state(false);
|
let open: boolean = $state(false);
|
||||||
|
|
||||||
|
let stationLat: number | null = $state(null);
|
||||||
|
let stationLong: number | null = $state(null);
|
||||||
|
|
||||||
|
function showSinglesite(data: PrimaryLayer) {
|
||||||
|
selectedPrimaryLayer = data.id;
|
||||||
|
idToNameCache[data.id] = data.layer;
|
||||||
|
|
||||||
|
if (map.getLayer("data")) {
|
||||||
|
map.removeLayer('data');
|
||||||
|
}
|
||||||
|
plotRadial(map, selectedSite, "REF", 1, stationLat, stationLong);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDatalayer() {
|
||||||
|
selectedPrimaryLayer = null;
|
||||||
|
if (map.getLayer('data')) {
|
||||||
|
map.removeLayer('data')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectSiteForSinglesite(item: Category) {
|
||||||
|
if (currentlySelectingFor) {
|
||||||
|
if (map.getLayer(`select-${currentlySelectingFor}`)) {
|
||||||
|
map.removeLayer(`select-${currentlySelectingFor}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedPrimaryLayer = item.id;
|
||||||
|
idToNameCache[item.id] = item.category;
|
||||||
|
pickingSiteForCategory = true;
|
||||||
|
currentlySelectingCategory = item.children;
|
||||||
|
selectedSite = null;
|
||||||
|
if (!map.getSource(item.id)) {
|
||||||
|
map.addSource(item.id, {
|
||||||
|
type: 'geojson',
|
||||||
|
data: stationGeojson(stations)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
map.addLayer({
|
||||||
|
id: `select-${item.id}`,
|
||||||
|
type: 'symbol',
|
||||||
|
source: item.id,
|
||||||
|
layout: {
|
||||||
|
'icon-image': ['get', 'icon'],
|
||||||
|
'text-field': ['get', 'icao'],
|
||||||
|
'text-size': 13,
|
||||||
|
'icon-size': 0.23
|
||||||
|
},
|
||||||
|
paint: {
|
||||||
|
'text-color': '#000000'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentlySelectingFor = item.id;
|
||||||
|
map.on('click', `select-${item.id}`, (e) => {
|
||||||
|
if (e && e.features) {
|
||||||
|
// did we already have a site set?
|
||||||
|
let alreadyHadSite = selectedSite != null;
|
||||||
|
pickingSiteForCategory = false;
|
||||||
|
selectedSite = e.features[0].properties.icao;
|
||||||
|
console.log(e.features);
|
||||||
|
stationLat = e.features[0].geometry.coordinates[1];
|
||||||
|
stationLong = e.features[0].geometry.coordinates[0];
|
||||||
|
if (!alreadyHadSite) {
|
||||||
|
selectedPrimaryLayer = null;
|
||||||
|
open = true;
|
||||||
|
} else {
|
||||||
|
if (map.getLayer("data")) {
|
||||||
|
map.removeLayer('data');
|
||||||
|
}
|
||||||
|
plotRadial(map, selectedSite, "REF", 1, stationLat, stationLong);
|
||||||
|
} // preserve the layer the user had if they click while already selected.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
map.on('mouseenter', `select-${item.id}`, () => {
|
||||||
|
map.getCanvas().style.cursor = 'pointer';
|
||||||
|
});
|
||||||
|
map.on('mouseleave', `select-${item.id}`, () => {
|
||||||
|
map.getCanvas().style.cursor = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectStandard(layer: PrimaryLayer) {
|
||||||
|
selectedPrimaryLayer = layer.id;
|
||||||
|
idToNameCache[layer.id] = layer.layer;
|
||||||
|
pickingSiteForCategory = false;
|
||||||
|
currentlySelectingCategory = null;
|
||||||
|
selectedSite = null;
|
||||||
|
if (currentlySelectingFor) {
|
||||||
|
if (map.getLayer(`select-${currentlySelectingFor}`)) {
|
||||||
|
map.removeLayer(`select-${currentlySelectingFor}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (map.getLayer("data")) {
|
||||||
|
map.removeLayer('data');
|
||||||
|
}
|
||||||
|
if (layer.type === 'raster') {
|
||||||
|
|
||||||
|
if (!map.getSource(layer.id)) {
|
||||||
|
map.addSource(layer.id, {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: [
|
||||||
|
layer.tileUrl
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
map.addLayer({
|
||||||
|
id: 'data',
|
||||||
|
type: 'raster',
|
||||||
|
source: `${layer.id}`
|
||||||
|
}, 'alerts');
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DropdownMenu.Root bind:open>
|
<DropdownMenu.Root bind:open>
|
||||||
|
@ -62,30 +175,7 @@
|
||||||
<DropdownMenu.GroupHeading>Data available at {selectedSite}</DropdownMenu.GroupHeading>
|
<DropdownMenu.GroupHeading>Data available at {selectedSite}</DropdownMenu.GroupHeading>
|
||||||
<DropdownMenu.Separator />
|
<DropdownMenu.Separator />
|
||||||
{#each currentlySelectingCategory as data (data.id)}
|
{#each currentlySelectingCategory as data (data.id)}
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item onclick={() => {showSinglesite(data);}}>{data.layer}</DropdownMenu.Item>
|
||||||
onclick={() => {
|
|
||||||
selectedPrimaryLayer = data.id;
|
|
||||||
idToNameCache[data.id] = data.layer;
|
|
||||||
|
|
||||||
if (!map.getSource(`${selectedSite}-${data.id}`)) {
|
|
||||||
idToTileUrlCache[data.id] = data.tileUrl;
|
|
||||||
map.addSource(`${selectedSite}-${data.id}`, {
|
|
||||||
type: 'raster',
|
|
||||||
tiles: [
|
|
||||||
data.tileUrl.replace('{site}', selectedSite)
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (map.getLayer("data")) {
|
|
||||||
map.removeLayer('data');
|
|
||||||
}
|
|
||||||
map.addLayer({
|
|
||||||
id: 'data',
|
|
||||||
type: 'raster',
|
|
||||||
source: `${selectedSite}-${data.id}`
|
|
||||||
}, 'alerts');
|
|
||||||
}}>{data.layer}</DropdownMenu.Item
|
|
||||||
>
|
|
||||||
{/each}
|
{/each}
|
||||||
</DropdownMenu.Group>
|
</DropdownMenu.Group>
|
||||||
<DropdownMenu.Separator />
|
<DropdownMenu.Separator />
|
||||||
|
@ -103,35 +193,7 @@
|
||||||
{#each item.children as layer (layer.id)}
|
{#each item.children as layer (layer.id)}
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
selectedPrimaryLayer = layer.id;
|
selectStandard(layer)
|
||||||
idToNameCache[layer.id] = layer.layer;
|
|
||||||
pickingSiteForCategory = false;
|
|
||||||
currentlySelectingCategory = null;
|
|
||||||
selectedSite = null;
|
|
||||||
if (currentlySelectingFor) {
|
|
||||||
if (map.getLayer(`select-${currentlySelectingFor}`)) {
|
|
||||||
map.removeLayer(`select-${currentlySelectingFor}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (map.getLayer("data")) {
|
|
||||||
map.removeLayer('data');
|
|
||||||
}
|
|
||||||
if (layer.type === 'raster') {
|
|
||||||
if (!map.getSource(layer.id)) {
|
|
||||||
map.addSource(layer.id, {
|
|
||||||
type: 'raster',
|
|
||||||
tiles: [
|
|
||||||
layer.tileUrl
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
map.addLayer({
|
|
||||||
id: 'data',
|
|
||||||
type: 'raster',
|
|
||||||
source: `${layer.id}`
|
|
||||||
}, 'alerts');
|
|
||||||
}
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{layer.layer}
|
{layer.layer}
|
||||||
|
@ -141,80 +203,12 @@
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
{:else}
|
{:else}
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onclick={async () => {
|
onclick={() => {selectSiteForSinglesite(item)}}>{item.category}...</DropdownMenu.Item
|
||||||
if (currentlySelectingFor) {
|
|
||||||
if (map.getLayer(`select-${currentlySelectingFor}`)) {
|
|
||||||
map.removeLayer(`select-${currentlySelectingFor}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedPrimaryLayer = item.id;
|
|
||||||
idToNameCache[item.id] = item.category;
|
|
||||||
pickingSiteForCategory = true;
|
|
||||||
currentlySelectingCategory = item.children;
|
|
||||||
selectedSite = null;
|
|
||||||
if (!map.getSource(item.id)) {
|
|
||||||
map.addSource(item.id, {
|
|
||||||
type: 'geojson',
|
|
||||||
data: stationGeojson(stations)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
map.addLayer({
|
|
||||||
id: `select-${item.id}`,
|
|
||||||
type: 'symbol',
|
|
||||||
source: item.id,
|
|
||||||
layout: {
|
|
||||||
'icon-image': ['get', 'icon'],
|
|
||||||
'text-field': ['get', 'icao'],
|
|
||||||
'text-size': 13,
|
|
||||||
'icon-size': 0.23
|
|
||||||
},
|
|
||||||
paint: {
|
|
||||||
'text-color': '#000000'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
currentlySelectingFor = item.id;
|
|
||||||
map.on('click', `select-${item.id}`, (e) => {
|
|
||||||
if (e && e.features) {
|
|
||||||
// did we already have a site set?
|
|
||||||
let alreadyHadSite = selectedSite != null;
|
|
||||||
pickingSiteForCategory = false;
|
|
||||||
selectedSite = e.features[0].properties.icao;
|
|
||||||
if (!alreadyHadSite) {
|
|
||||||
selectedPrimaryLayer = null;
|
|
||||||
open = true;
|
|
||||||
} else {
|
|
||||||
if (!map.getSource(`${selectedSite}-${selectedPrimaryLayer}`)) {
|
|
||||||
|
|
||||||
map.addSource(`${selectedSite}-${selectedPrimaryLayer}`, {
|
|
||||||
type: 'raster',
|
|
||||||
tiles: [
|
|
||||||
idToTileUrlCache[selectedPrimaryLayer].replace('{site}', selectedSite)
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (map.getLayer("data")) {
|
|
||||||
map.removeLayer('data');
|
|
||||||
}
|
|
||||||
map.addLayer({
|
|
||||||
id: 'data',
|
|
||||||
type: 'raster',
|
|
||||||
source: `${selectedSite}-${selectedPrimaryLayer}`
|
|
||||||
}, 'alerts');
|
|
||||||
} // preserve the layer the user had if they click while already selected.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
map.on('mouseenter', `select-${item.id}`, () => {
|
|
||||||
map.getCanvas().style.cursor = 'pointer';
|
|
||||||
});
|
|
||||||
map.on('mouseleave', `select-${item.id}`, () => {
|
|
||||||
map.getCanvas().style.cursor = '';
|
|
||||||
});
|
|
||||||
}}>{item.category}...</DropdownMenu.Item
|
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<DropdownMenu.Item onclick={() => {selectedPrimaryLayer = null; if (map.getLayer('data')) {map.removeLayer('data')}}}>
|
<DropdownMenu.Item onclick={clearDatalayer}>
|
||||||
None
|
None
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Group>
|
</DropdownMenu.Group>
|
||||||
|
|
223
client/src/lib/map/map.ts
Normal file
223
client/src/lib/map/map.ts
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
|
||||||
|
import maplibregl from "maplibre-gl";
|
||||||
|
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';
|
||||||
|
import {degToRad} from "$lib/vincenty.js";
|
||||||
|
import init_palette_parser_wasm, {parse_palette} from "$lib/map/pal/wxbox_pal";
|
||||||
|
import {WXBOX_STANDARD_REF} from "$lib/map/default_palettes";
|
||||||
|
import type { Map } from "maplibre-gl";
|
||||||
|
|
||||||
|
const BELOW_THRESHOLD = -9999.0;
|
||||||
|
const RANGE_FOLDED = -9998.0;
|
||||||
|
|
||||||
|
export async function plotRadial(map: Map, site: string, product: string, elevation: number, lat: number, long: number) {
|
||||||
|
const r = await fetch(`http://localhost:3000/v2/nexrad/l2/${site}/${elevation}/${product}`);
|
||||||
|
const buf = await r.arrayBuffer();
|
||||||
|
|
||||||
|
const container = CifContainer.fromBinary(new Uint8Array(buf));
|
||||||
|
|
||||||
|
if (container.messageType.oneofKind == 'digitalRadarData') {
|
||||||
|
const drd: DigitalRadarData = container.messageType.digitalRadarData;
|
||||||
|
|
||||||
|
await init_palette_parser_wasm();
|
||||||
|
|
||||||
|
if (map.getLayer("data")) {
|
||||||
|
map.removeLayer('data');
|
||||||
|
}
|
||||||
|
const dataLayer = {
|
||||||
|
id: 'data',
|
||||||
|
type: 'custom',
|
||||||
|
maxZoom: 13,
|
||||||
|
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);
|
||||||
|
|
||||||
|
this.program = gl.createProgram();
|
||||||
|
gl.attachShader(this.program, vertexShader);
|
||||||
|
gl.attachShader(this.program, fragmentShader);
|
||||||
|
gl.linkProgram(this.program);
|
||||||
|
gl.useProgram(this.program);
|
||||||
|
|
||||||
|
this.aPos = gl.getAttribLocation(this.program, 'a_pos');
|
||||||
|
|
||||||
|
gl.uniform1f(gl.getUniformLocation(this.program, 'radarLat'), lat);
|
||||||
|
gl.uniform1f(gl.getUniformLocation(this.program, 'radarLng'), long);
|
||||||
|
|
||||||
|
const vertexData: number[] = [];
|
||||||
|
const triangleAzimuthLookup: number[] = [];
|
||||||
|
|
||||||
|
const radarCoordinate = maplibregl.MercatorCoordinate.fromLngLat({
|
||||||
|
lng: long,
|
||||||
|
lat: lat
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i =0; i < drd.radials.length; i++) {
|
||||||
|
const radial = drd.radials[i];
|
||||||
|
|
||||||
|
if (radial.product && radial.product.data && radial.product.data.data) {
|
||||||
|
const angle_a = radial.azimuthAngleDegrees;
|
||||||
|
const angle_b = (i == drd.radials.length - 1) ? drd.radials[0].azimuthAngleDegrees : drd.radials[i+1].azimuthAngleDegrees;
|
||||||
|
|
||||||
|
const start_range = radial.product.data.startRange; // m
|
||||||
|
const sample_interval = radial.product.data.sampleInterval; // m
|
||||||
|
const number_of_samples = radial.product.data.data.length;
|
||||||
|
const range = sample_interval * number_of_samples + start_range; // m
|
||||||
|
// add an extra sample for good measure
|
||||||
|
const padded_range = range + sample_interval; // m
|
||||||
|
|
||||||
|
const in_weird_mercator_units = padded_range / radarCoordinate.meterInMercatorCoordinateUnits();
|
||||||
|
const pointAX = radarCoordinate.x + in_weird_mercator_units * Math.sin(degToRad(-angle_a + 180));
|
||||||
|
const pointAY = radarCoordinate.y + in_weird_mercator_units * Math.cos(degToRad(-angle_a + 180));
|
||||||
|
const pointA = new maplibregl.MercatorCoordinate(pointAX, pointAY, 0);
|
||||||
|
|
||||||
|
const pointBX = radarCoordinate.x + in_weird_mercator_units * Math.sin(degToRad(-angle_b + 180));
|
||||||
|
const pointBY = radarCoordinate.y + in_weird_mercator_units * Math.cos(degToRad(-angle_b + 180));
|
||||||
|
const pointB = new maplibregl.MercatorCoordinate(pointBX, pointBY, 0);
|
||||||
|
|
||||||
|
vertexData.push(radarCoordinate.x);
|
||||||
|
vertexData.push(radarCoordinate.y);
|
||||||
|
vertexData.push(pointA.x);
|
||||||
|
vertexData.push(pointA.y);
|
||||||
|
vertexData.push(pointB.x);
|
||||||
|
vertexData.push(pointB.y);
|
||||||
|
triangleAzimuthLookup.push(angle_a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
this.buffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||||||
|
gl.bufferData(
|
||||||
|
gl.ARRAY_BUFFER,
|
||||||
|
new Float32Array(vertexData),
|
||||||
|
gl.STATIC_DRAW
|
||||||
|
);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
validRadials++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new Float32Array(raw_data);
|
||||||
|
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.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);
|
||||||
|
gl.uniform1i(gl.getUniformLocation(this.program, 'data'), 10);
|
||||||
|
|
||||||
|
// parse, transform, and send over the palette
|
||||||
|
const palette = parse_palette(WXBOX_STANDARD_REF);
|
||||||
|
gl.uniform1i(gl.getUniformLocation(this.program, 'paletteLength'), palette.length);
|
||||||
|
const rboundsData: number[] = [];
|
||||||
|
const rcolorData: number[] = [];
|
||||||
|
const rcolor2Data: number[] = [];
|
||||||
|
for (const paletteEntry of palette) {
|
||||||
|
const [bounds, color] = paletteEntry;
|
||||||
|
rboundsData.push(...bounds);
|
||||||
|
rcolorData.push(...[color[0].red, color[0].green, color[0].blue, color[0].alpha]);
|
||||||
|
rcolor2Data.push(...[color[1].red, color[1].green, color[1].blue, color[1].alpha]);
|
||||||
|
}
|
||||||
|
// create the bounds texture first
|
||||||
|
const boundsData = new Float32Array(rboundsData);
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + 11);
|
||||||
|
this.boundsTexture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.boundsTexture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, 1, palette.length, 0, gl.RG, gl.FLOAT, boundsData);
|
||||||
|
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);
|
||||||
|
gl.uniform1i(gl.getUniformLocation(this.program, 'bounds'), 11);
|
||||||
|
// then the colorData texture
|
||||||
|
const colorData = new Uint8Array(rcolorData);
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + 12);
|
||||||
|
this.colorDataTexture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.colorDataTexture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, palette.length, 0, gl.RGBA, gl.UNSIGNED_BYTE, colorData);
|
||||||
|
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);
|
||||||
|
gl.uniform1i(gl.getUniformLocation(this.program, 'colorData'), 12);
|
||||||
|
// then the colorData2 texture
|
||||||
|
const color2Data = new Uint8Array(rcolor2Data);
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + 13);
|
||||||
|
this.color2DataTexture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.color2DataTexture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, palette.length, 0, gl.RGBA, gl.UNSIGNED_BYTE, color2Data);
|
||||||
|
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);
|
||||||
|
gl.uniform1i(gl.getUniformLocation(this.program, 'color2Data'), 13);
|
||||||
|
},
|
||||||
|
render(gl, args) {
|
||||||
|
gl.enable(gl.SAMPLE_COVERAGE);
|
||||||
|
gl.sampleCoverage(1.0, false);
|
||||||
|
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + 10);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + 11);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.boundsTexture);
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + 12);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.colorDataTexture);
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + 13);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.color2DataTexture);
|
||||||
|
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, this.vertexCount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
map.addLayer(dataLayer, 'alerts');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue