Notebooks 2.0 is here.

Published
Edited
May 18, 2020
Insert cell
Insert cell
Insert cell
viewof hillshadeAlpha = slider({
min: 0,
max: 1,
value: 0.6,
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", "source-over", "source-atop", "source-in", "source-out", "destination-over", "destination-atop", "destination-in", "destination-out", "lighter", "copy", "xor","lighten","darken","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],
value: "screen"
})
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);
const context = DOM.context2d(width, height);
context.globalCompositeOperation = blendMethod;
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])))) {
context.drawImage(image, (x - x0) * tileSize, (y - y0) * tileSize, tileSize, 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])))) {
context.drawImage(image, (x - x0) * tileSize, (y - y0) * tileSize, tileSize, tileSize);
}

// context.globalCompositeOperation = "normal";
// context.globalAlpha = .1
// 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])))) {
// context.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
// );

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
urlTwo = (x, y, z) => `https://tiles-preview.firststreet.org/probability/depth/2035/100/${z}/${x}/${y}.png`
Insert cell
projection = d3.geoMercator()
.center([-84.191605, 39.758949])
.scale(Math.pow(2, 21) / (2 * Math.PI))
.translate([width / 2, height / 2])
Insert cell
tile = d3.tile()
.size([width, height])
.scale(projection.scale() * 2 * Math.PI)
.translate(projection([0, 0]))
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