Published
Edited
Mar 1, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
drawCountry = gl.regl({
vert: `
precision mediump float;

uniform mat4 model, view, projection;

attribute vec3 position;

void main () {
gl_Position = projection * view * model * vec4(position, 1);
}
`,
frag: `
precision mediump float;

void main () {
gl_FragColor = vec4(0, 0, 0, 1);
}
`,
attributes: {
position: gl.regl.prop("position")
},
uniforms: {
model: gl.regl.prop("model"),
},
elements: gl.regl.prop("elements"),
})
Insert cell
graticule = {
const graticule = geoGraticule()()
const positions = graticule.coordinates.map(points => points.map(([lon, lat]) => polarToCartesian(lon, lat, 1)))
return positions.filter(x => x.length > 3)
}
Insert cell
drawGraticule = gl.regl({
vert: `
precision mediump float;

attribute vec3 position;
uniform mat4 model, view, projection;

void main () {
gl_Position = projection * view * model * vec4(position, 1);
}
`,
frag: `
precision mediump float;

void main () {
gl_FragColor = vec4(0, 0, 0, 0.2);
}
`,
attributes: {
position: gl.regl.prop("position")
},
uniforms: {
model: gl.regl.prop("model"),
view: gl.regl.context('view'),
projection: gl.regl.context('projection'),
},
primitive: 'line strip',
count: gl.regl.prop("count"),
})
Insert cell
setupCamera = gl.regl({
context: {
projection: (context, props) =>
mat4.perspective(
[],
Math.PI / 4,
context.viewportWidth / context.viewportHeight,
0.01,
4 * 0.92 * Math.sqrt(Math.pow(3 * Math.cos(props.time), 2) + Math.pow(3 * Math.sin(props.time), 2)),
),

view: (context, props) =>
mat4.lookAt(
[],
[
3 * Math.cos(props.time),
3 * Math.sin(props.time),
0,
],
[0, 0, 0],
[0, 0, 1],
)
},

uniforms: {
view: gl.regl.context('view'),
projection: gl.regl.context('projection')
}
})
Insert cell
function draw(time) {
gl.regl.clear({
color: [1, 1, 1, 1],
depth: 1,
})
setupCamera({ time }, () => {
const model = mat4.identity([])

const { position, elements } = features
drawCountry({
model,
position,
elements
})

drawGraticule(graticule.map(points => ({
model,
position: points,
count: points.length,
})))
})
}
Insert cell
draw(now / 5000)
Insert cell
function polarToCartesian(lon0, lat0, R) {
const lat = lat0 * Math.PI / 180
const lon = lon0 * Math.PI / 180
const x = R * Math.cos(lat) * Math.cos(lon)
const y = R * Math.cos(lat) * Math.sin(lon)
const z = R * Math.sin(lat)

return [x, y, z];
}
Insert cell
function geoGraticule() {
const { abs, ceil } = Math;
const epsilon = 1e-6;
const { range } = d3;
function graticuleX(y0, y1, dy) {
var y = range(y0, y1 - epsilon, dy).concat(y1);
return function(x) { return y.map(function(y) { return [x, y]; }); };
}

function graticuleY(x0, x1, dx) {
var x = range(x0, x1 - epsilon, dx).concat(x1);
return function(y) { return x.map(function(x) { return [x, y]; }); };
}

var x1, x0, X1, X0,
y1, y0, Y1, Y0,
dx = 10, dy = dx, DX = 90, DY = 360,
x, y, X, Y,
precision = 2.5;

function graticule() {
return {type: "MultiLineString", coordinates: lines()};
}

function lines() {
return range(ceil(X0 / DX) * DX, X1, DX).map(X)
.concat(range(ceil(Y0 / DY) * DY, Y1, DY).map(Y))
.concat(range(ceil(x0 / dx) * dx, x1, dx).filter(function(x) { return abs(x % DX) > epsilon; }).map(x))
.concat(range(ceil(y0 / dy) * dy, y1, dy).filter(function(y) { return abs(y % DY) > epsilon; }).map(y));
}

graticule.lines = function() {
return lines().map(function(coordinates) { return {type: "LineString", coordinates: coordinates}; });
};

graticule.outline = function() {
return {
type: "Polygon",
coordinates: [
X(X0).concat(
Y(Y1).slice(1),
X(X1).reverse().slice(1),
Y(Y0).reverse().slice(1))
]
};
};

graticule.extent = function(_) {
if (!arguments.length) return graticule.extentMinor();
return graticule.extentMajor(_).extentMinor(_);
};

graticule.extentMajor = function(_) {
if (!arguments.length) return [[X0, Y0], [X1, Y1]];
X0 = +_[0][0], X1 = +_[1][0];
Y0 = +_[0][1], Y1 = +_[1][1];
if (X0 > X1) _ = X0, X0 = X1, X1 = _;
if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
return graticule.precision(precision);
};

graticule.extentMinor = function(_) {
if (!arguments.length) return [[x0, y0], [x1, y1]];
x0 = +_[0][0], x1 = +_[1][0];
y0 = +_[0][1], y1 = +_[1][1];
if (x0 > x1) _ = x0, x0 = x1, x1 = _;
if (y0 > y1) _ = y0, y0 = y1, y1 = _;
return graticule.precision(precision);
};

graticule.step = function(_) {
if (!arguments.length) return graticule.stepMinor();
return graticule.stepMajor(_).stepMinor(_);
};

graticule.stepMajor = function(_) {
if (!arguments.length) return [DX, DY];
DX = +_[0], DY = +_[1];
return graticule;
};

graticule.stepMinor = function(_) {
if (!arguments.length) return [dx, dy];
dx = +_[0], dy = +_[1];
return graticule;
};

graticule.precision = function(_) {
if (!arguments.length) return precision;
precision = +_;
x = graticuleX(y0, y1, precision);
y = graticuleY(x0, x1, precision);
X = graticuleX(Y0, Y1, precision);
Y = graticuleY(X0, X1, precision);
return graticule;
};

return graticule
.extentMajor([[-180, -90 + epsilon], [180, 90 - epsilon]])
.extentMinor([[-180, -80 - epsilon], [180, 80 + epsilon]]);
}
Insert cell
Insert cell
countries = {
const world = await fetch("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-10m.json")
.then(res => res.json())
return topojson.feature(world, world.objects.countries)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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