Notebooks 2.0 is here.

Published
Edited
May 14, 2020
1 fork
Insert cell
Insert cell
Insert cell
viewof hillshadeAlpha = slider({
min: 0,
max: 1,
value: 1,
step: 0.1,
description: "Hillshade layer alpha"
})
Insert cell
viewof blendMethod = select({
title: "Blend method",
description: "Please choose how you would like to blend layers. Multiply or overlay are recommended.",
options: ["none","screen", "multiply", "overlay", "color dodge"],
value: "none"
})
Insert cell
Insert cell
map = {
const tiles = tile();
const [x0, y0] = tiles[0];
const [x1, y1] = tiles[tiles.length - 1];
const offscreenContext = DOM.context2d((x1 - x0 + 1) * tileSize, (y1 - y0 + 1) * tileSize);
for (const [x, y, image] of await Promise.all(tiles.map(([x, y, z]) => new Promise((resolve, reject) => {
const image = new Image;
image.onerror = reject;
image.crossOrigin="anonymous";
image.onload = () => resolve(image);
image.src = url(x, y, z);
}).then(image => [x, y, image])))) {
offscreenContext.drawImage(image, (x - x0) * tileSize, (y - y0) * tileSize, tileSize, tileSize);
}
const context = DOM.context2d(width, height);
context.drawImage(
offscreenContext.canvas,
Math.round((x0 + tiles.translate[0]) * tiles.scale),
Math.round((y0 + tiles.translate[1]) * tiles.scale),
(x1 - x0 + 1) * tiles.scale,
(y1 - y0 + 1) * tiles.scale
);
const rasterImageData = context.getImageData(0, 0, width, height).data;
offscreenContext.clearRect(0,0,width,height);
context.clearRect(0,0,width,height);
for (const [x, y, image] of await Promise.all(tiles.map(([x, y, z]) => new Promise((resolve, reject) => {
const image = new Image;
image.onerror = reject;
image.crossOrigin="anonymous";
image.onload = () => resolve(image);
image.src = urlTwo(x, y, z);
}).then(image => [x, y, image])))) {
offscreenContext.drawImage(image, (x - x0) * tileSize, (y - y0) * tileSize, tileSize, tileSize);
}
context.drawImage(
offscreenContext.canvas,
Math.round((x0 + tiles.translate[0]) * tiles.scale),
Math.round((y0 + tiles.translate[1]) * tiles.scale),
(x1 - x0 + 1) * tiles.scale,
(y1 - y0 + 1) * tiles.scale
);
const floodImageData = context.getImageData(0, 0, width, height).data;

offscreenContext.clearRect(0,0,width,height);
context.clearRect(0,0,width,height);
const compositeImageData = blendImage(rasterImageData,floodImageData);
const compositeImage = new ImageData(compositeImageData, width, height);
context.putImageData(compositeImage, 0, 0, 0, 0, width, height);
return context.canvas;
}
Insert cell
blendImage = (layer1, layer2) => {
const compositeImageData = new Uint8ClampedArray(layer1.length);

for (let n = 0, l = layer1.length; n < l; n += 4) {
let srcR = layer1[n];
let srcG = layer1[n + 1];
let srcB = layer1[n + 2];
// Alpha is treated as a decimal 0 to 1 instead of 0 to 255
// let srcA = layer1[n + 3] / 255;
let srcA = satelliteAlpha;
let deltR = layer2[n];
let deltG = layer2[n + 1];
let deltB = layer2[n + 2];
// Alpha is treated as a decimal 0 to 1 instead of 0 to 255
// let deltA = layer2[n + 3] / 255;
let deltA = hillshadeAlpha;
let newR, newG, newB;
switch(blendMethod) {
case 'none':
newR = srcR;
newG = srcG;
newB = srcB;
break;
case 'multiply':
newR = srcR * deltR / 255;
newG = srcG * deltG / 255;
newB = srcB * deltB / 255;
break;
case 'screen':
newR = 255 - ( ( (255 - srcR) * (255 - deltR) ) / 255);
newG = 255 - ( ( (255 - srcG) * (255 - deltG) ) / 255);
newB = 255 - ( ( (255 - srcB) * (255 - deltB) ) / 255);
break;
case 'color dodge':
newR = srcR + deltR;
newG = srcG + deltG;
newB = srcB + deltB;
break;

case 'overlay' :
newR = deltR < 128 ? (2 * srcR * deltR / 255) : (255 - ( ( 2 * (255 - srcR) * (255 - deltR) ) / 255));
newG = deltG < 128 ? (2 * srcG * deltG / 255) : (255 - ( ( 2 * (255 - srcG) * (255 - deltG) ) / 255));
newB = deltB < 128 ? (2 * srcB * deltB / 255) : (255 - ( ( 2 * (255 - srcB) * (255 - deltB) ) / 255));
break;
}
// Composite layers based on alpha
newR = newR * srcA + deltR * deltA;
newG = newG * srcA + deltG * deltA;
newB = newB * srcA + deltB * deltA;
let newA = 255;
compositeImageData[n] = newR;
compositeImageData[n + 1] = newG;
compositeImageData[n + 2] = newB;
compositeImageData[n + 3] = newA;
}
return compositeImageData;
}
Insert cell
url = (x, y, z) => `https://api.mapbox.com/v4/nytgraphics.8buxzzd8/${z}/${x}/${y}@2x.webp?access_token=pk.eyJ1Ijoibnl0Z3JhcGhpY3MiLCJhIjoiY2o5YTlyb3ptMTB5bDMybXF2ODRjdThlYyJ9.cytRa2y0Qt3Nedj4eUX67w`
Insert cell
Insert cell
Insert cell
tileSize = 256
Insert cell
path = d3.geoPath(projection)
Insert cell
height = 600
Insert cell
import {slider, text, select, coordinates, worldMapCoordinates, usaMapCoordinates} from "@jashkenas/inputs"
Insert cell
d3 = require("d3-geo@1", "d3-tile@1")
Insert cell
import {serialize} from "@mbostock/saving-svg"
Insert cell
Protobuf = require('https://bundle.run/pbf@3.1.0')
Insert cell
vt = require('https://bundle.run/@mapbox/vector-tile@1.3.1')
Insert cell
json = async url => {
const { features } = await d3.json(url);
return features;
}
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