Published
Edited
Apr 13, 2020
4 forks
Importers
14 stars
Insert cell
Insert cell
fps(wind)
Insert cell
wind = {
const div = html`<div id="wind">`;
const context = DOM.context2d(width, height);
const canvas = context.canvas;
const scl = width / mapWidth;
context.scale(scl, scl);
d3.select(div)
.style('position', 'relative')
.style('width', '100%')
.style('height', height + 'px')
.style('pointer-events', 'none')
.append(() => canvas);
addStates(div);
const MAX_AGE = 25; // Each generation of particles lasts MAX_AGE milliseconds before disappearing
const N_GENERATIONS = 20; // Total duration of animation
const N = 120; // Total # of particles: N * N
const DURATION = N * N_GENERATIONS;

const scale = d3.scaleLinear().domain(extent).range([-2, 2]);
const path = d3.geoPath(projection, context);
// Initialize things with a circle on DC
context.clearRect(0, 0, mapWidth, mapHeight)
context.strokeStyle = "rgb(200,200,200)";
context.beginPath(), path({type: "Point", coordinates: dc}), context.stroke();
// Create a bunch of particles (n * n) on an almost uniform grid
let particles = [];
const n = N;
const eps = 1;
const dx = mapWidth / n;
const dy = mapHeight / n;
for (let i = 0; i < n + 1; i++) {
for (let j = 0; j < n + 1; j++) {
let x0 = (i + eps * (Math.random() - .5)) * dx; // uniform grid with a little jiggle
let y0 = (j + eps * (Math.random() - .5)) * dy; // ditto
particles.push({ x0: x0, y0: y0, x: x0, y: y0, age: Math.round(MAX_AGE * Math.random()) });
}
}

// Animate the vectors
while (true) {
context.beginPath();
context.strokeStyle = 'rgba(70,130,180,.4)'; // steelblue

particles.forEach(advanceParticle);
context.stroke();
context.fillStyle = 'rgba(255,255,255,.05)';
context.fillRect(0, 0, mapWidth, mapHeight);
yield div;
}
function advanceParticle(d) {
if (d.age++ > MAX_AGE) { d.x = d.x0; d.y = d.y0; d.age = 0; };

const coordinates = projection.invert([d.x, d.y]);

if (isNaN(coordinates[0]) | isNaN(coordinates[1])) return;

const u = bilinear(coordinates).velocity

context.moveTo(d.x, d.y)
d.x += scale(u[0]);
d.y -= scale(u[1]);
context.lineTo(d.x, d.y);
}
}
Insert cell
addStates = function(div) {
// Add the states as an SVG overlay
d3.select(div).append(() => states)
.style("position", "absolute")
.style("top", 0)
.style("left", 0)
.selectAll("path.interior")
.style("fill", "none")
.style("stroke", "none");
d3.select(states).selectAll('path.nation')
.style('stroke', '#333')
d3.select(states).selectAll("path.mesh")
.style("fill", "none")
.style("stroke", "#aaa");
}
Insert cell
Insert cell
Insert cell
bilinear(dc)
Insert cell
bilinear([0, 90]) // [lon, lat] = [0, 90] should return {i: 0, j: 0}
Insert cell
bilinear([-180, 0]) // [lon, lat] = [-180, 0] should return {i: 90, j: 180}
Insert cell
// Bilinear interpolation from 1-degree global grid to an arbitrary loc = [lon, lat]
// data[0,0] is [0 E, 90 N]
// lat = 90 - (i + y)
// lon += (lon < 0) ? 360 : 0
// lon = j + x
bilinear = function(loc) {
let lon = loc[0] + ((loc[0] < 0) ? 360 : 0);
let lat = loc[1];
let i = Math.floor(90 - lat);
let j = Math.floor(lon);
let y = 90 - lat - i;
let x = lon - j;
// define: f(x,y) = a + b*y + c*x + d*x*y
//
// then a = f(0,0)
// b = f(0,1) - a
// c = f(1,0) - a
// d = f(1,1) - a - b - c
//
// where f(x,y) is related to data[i][j] as follows:
//
// f(0,0) = data[i][j] -- x -- data[i][j+1] = f(1,0)
// |
// y
// |
// f(0,1) = data[i+1][j] ---- data[i+1][j+1] = f(1,1)
let ip1 = (i < 180) ? i + 1 : i;
let jp1 = (j < 359) ? j + 1 : j;
const velocity = new Array(2);
for (let k = 0; k < 2; k++) {
let a = data[k][i][j];
let b = data[k][ip1][j] - a;
let c = data[k][i][jp1] - a;
let d = data[k][ip1][jp1] - a - b - c;
velocity[k] = a + (b * y) + (c * x) + (d * x * y);
}
return { i: i, j: j, x: x, y: y, lon: lon, lat: lat, loc0: loc[0], loc1: loc[1], velocity}
}
Insert cell
Insert cell
data = FileAttachment("data_20200111_0000.json").json()
// data = FileAttachment("data_LAND_velocity.json").json() // USE THIS FOR TESTING
Insert cell
extent = d3.extent(d3.merge(d3.merge(data)));
Insert cell
Insert cell
mapWidth = 960
Insert cell
mapHeight = 600
Insert cell
// WARNING: make sure this is consistent with the shared map
projection = d3.geoAlbers().scale(1280).translate([mapWidth / 2, mapHeight / 2])
Insert cell
// import {viewof point } from "@pbogden/my-u-s-map-canvas"
import { states, d3 } from "d7ee2573091b5d13"
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more