windelsis
Version:
`Windelsis` is a JavaScript library that visualizes weather data on interactive maps using Leaflet. It provides tools to render temperature, precipitation, and wind velocity layers, as well as utilities for grid-based weather data management.
216 lines (185 loc) • 8.16 kB
JavaScript
function getColorForValue(value, colorScale) {
// if value is less than the first value in the scale, return the first color
if (value <= colorScale[0].value) {
const [r, g, b] = colorScale[0].color;
return { r, g, b };
}
if (value >= colorScale[colorScale.length - 1].value) {
const [r, g, b] = colorScale[colorScale.length - 1].color;
return { r, g, b };
}
// find the two colors that the value is between
for (let i = 0; i < colorScale.length - 1; i++) {
const current = colorScale[i];
const next = colorScale[i + 1];
if (value >= current.value && value <= next.value) {
const factor = (value - current.value) / (next.value - current.value);
const r = Math.round(current.color[0] + factor * (next.color[0] - current.color[0]));
const g = Math.round(current.color[1] + factor * (next.color[1] - current.color[1]));
const b = Math.round(current.color[2] + factor * (next.color[2] - current.color[2]));
return { r, g, b };
}
}
// fallback
const lastColor = colorScale[colorScale.length - 1].color;
return { r: lastColor[0], g: lastColor[1], b: lastColor[2] };
}
const COLOR_SCALES = {
temperature: [
{ value: -15, color: [113, 190, 207] }, // Azul claro
{ value: -8, color: [137, 204, 197] }, // Verde azulado
{ value: -4, color: [120, 184, 206] }, // Azul medio
{ value: 0, color: [98, 129, 207] }, // Azul más oscuro
{ value: 1, color: [128, 167, 132] }, // Verde grisáceo
{ value: 10, color: [181, 202, 96] }, // Verde amarillento
{ value: 21, color: [242, 177, 59] }, // Amarillo anaranjado
{ value: 30, color: [235, 96, 49] }, // Naranja rojizo
{ value: 47, color: [112, 45, 21] } // Marrón oscuro
],
precipitation: [
{ value: 0, color: [255, 255, 255] }, // Blanco
{ value: 1, color: [200, 255, 255] }, // Azul muy claro
{ value: 5, color: [100, 200, 255] }, // Azul claro
{ value: 10, color: [0, 100, 255] }, // Azul
{ value: 25, color: [0, 0, 255] }, // Azul oscuro
{ value: 50, color: [128, 0, 255] } // Violeta
]
};
/**
* Use of the canvasLayer plugin for Leaflet to render data on a map
* https://github.com/Sumbera/gLayers.Leaflet
*/
class DataRenderer {
constructor(map, data, options = {}) {
this.map = map;
this.data = data;
this.canvasLayer = null;
this._timer = null;
this.options = Object.assign({
pixelSize: 5,
opacity: 0.3,
controlName: 'Data Layer',
layerControl: map.layerControl,
colorScale: COLOR_SCALES.temperature,
demoMode: false,
}, options);
}
init() {
this._paneName = this.options.paneName || "overlayPane"; // for leaflet < 1
var pane = this.map._panes.overlayPane;
if (this.map.getPane) {
pane = this.map.getPane(this._paneName);
if (!pane) {
pane = this.map.createPane(this._paneName);
}
}
this.canvasLayer = L.canvasLayer({
pane: pane
}).delegate(this);
this.options.layerControl.addOverlay(this.canvasLayer, this.options.controlName);
return this.canvasLayer;
}
onDrawLayer(info) {
if (!this.data || !this.data.data || this.data.data.length === 0) {
console.log(this.data, 'No available data to draw');
return;
}
if (this._timer) clearTimeout(this._timer);
this._timer = setTimeout(() => {
const ctx = info.canvas.getContext('2d', { willReadFrequently: true });
ctx.clearRect(0, 0, info.canvas.width, info.canvas.height);
ctx.globalAlpha = this.options.opacity;
ctx.globalCompositeOperation = 'multiply';
const width = info.canvas.width;
const height = info.canvas.height;
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const latLng = this.map.containerPointToLatLng([x, y]);
const value = this.interpolateValue(latLng.lat, latLng.lng);
if (value == null || Number.isNaN(value)) continue;
const { r, g, b } = getColorForValue(value, this.options.colorScale);
const a = Math.floor(this.options.opacity * 255);;
const index = (y * width + x) * 4;
data[index] = r;
data[index + 1] = g;
data[index + 2] = b;
data[index + 3] = a;
}
}
if(this.options.demoMode) {
const header = this.data.header;
const nx = header.nx;
const ny = header.ny;
const dx = header.dx;
const dy = header.dy;
const lo1 = header.lo1;
const la1 = header.la1;
for (let i = 0; i < ny; i++) {
for (let j = 0; j < nx; j++) {
const lat = la1 - i * dy;
const lng = lo1 + j * dx;
const containerPoint = this.map.latLngToContainerPoint([lat, lng]);
const px = Math.round(containerPoint.x);
const py = Math.round(containerPoint.y);
if (px >= 0 && px < width && py >= 0 && py < height) {
const index = (py * width + px) * 4;
data[index] = 0;
data[index + 1] = 0;
data[index + 2] = 0;
data[index + 3] = 255;
}
}
}
}
ctx.putImageData(imageData, 0, 0);
}, 100);
}
interpolateValue(lat, lng) {
const { header, data } = this.data;
const { lo1, lo2, la1, la2, nx, ny, dx, dy } = header;
const i = Math.floor((la1 - lat) / dy);
const j = Math.floor((lng - lo1) / dx);
if (i < 0 || i >= ny - 1 || j < 0 || j >= nx - 1) {
return null;
}
const t1 = data[i * nx + j];
const t2 = data[i * nx + (j + 1)];
const t3 = data[(i + 1) * nx + j];
const t4 = data[(i + 1) * nx + (j + 1)];
const x1 = lo1 + j * dx;
const x2 = lo1 + (j + 1) * dx;
const y1 = la1 - i * dy;
const y2 = la1 - (i + 1) * dy;
const t12 = t1 + (t2 - t1) * (lng - x1) / (x2 - x1);
const t34 = t3 + (t4 - t3) * (lng - x1) / (x2 - x1);
const t = t12 + (t34 - t12) * (lat - y1) / (y2 - y1);
return t;
}
update(data) {
this.data = data;
if (this.canvasLayer && this.map.hasLayer(this.canvasLayer)){
this.canvasLayer.needRedraw();
}
}
setOptions(options = {}) {
Object.assign(this.options, options);
return this;
}
_clearTemperature() {
if (this.canvasLayer && this.canvasLayer._canvas) {
const ctx = this.canvasLayer._canvas.getContext('2d');
ctx.clearRect(0, 0, this.canvasLayer._canvas.width, this.canvasLayer._canvas.height);
}
}
_destroyTemperatureLayer() {
if (this._timer) clearTimeout(this._timer);
this._clearTemperature();
if (this.canvasLayer) {
this.map.removeLayer(this.canvasLayer);
this.canvasLayer = null;
}
}
}
export { DataRenderer, COLOR_SCALES };