Public
Edited
Jun 20, 2021
Importers
19 stars
Insert cell
Insert cell
Insert cell
Insert cell
html`${viewof worldParameters}`
Insert cell
//svg`<svg width="${width}px" height="${height}" style="background: white">${anaglyphRenderer(processedScene)}</svg>`
Insert cell
viewof terrainExaggeration = Inputs.range([0.25, 10], {value: 1.75, step: 0.25, label: "Terrain Exaggeration"})
Insert cell
viewof lineSimplification = Inputs.range([0.1, 3], {value: 0.2, step: 0.1, label: "Line Simplification"})
Insert cell
viewof numOfLevels = Inputs.range([7, 35], {value: 18, step: 1, label: "Number of Levels"})
Insert cell
computeSlipperyCoords(10, 37.29948148,-113.09397012).join('/')
Insert cell
options = Object.assign({
"10/188/402": "Grand Canyon, USA",
"10/906/404": "Mount Fuji, Japan",
"10/561.97/594.3": "Fish River Canyon, Nambia",
"11/916.26/548.78": "Maelifell, Iceland",
"9/309.13/260.36": "Mount Kilimanjaro, Tanzania",
"9/82.83/180.39": "Mount Rainier, WA, USA",
"10/164.44/363.49": "Mt. Saint Helens, USA",
"11/609.28/1111.53": "Cotahuasi Canyon, Peru",
"10/303.92/678.95": "Perito Moreno Glacier, Argentina",
"10/767.43/424.78": "Yarlang Tsangpo, China",
"10/164.66/376.49": "Crater Lake, USA",
"10/190.31/397.5": "Tabernacle Dome, Zion National Park, USA",
"10/532/292": "Naeroyfjord, Norway",
"10/171/395": "Yosemite, USA",
"11/2014/1267": "Mount Taranaki, New Zealand",
"12/784.81/1622.03": "Barringer Crater, AZ, USA",
"11/376.79/705.69": "Glacier National Park, MO, USA",
"10/163/395": "Bay Area, USA",
"10/302.46/289.96": "Pingualuit Crater, QC, Canada",
})
Insert cell
locationLabel = {
let label = "Hello World"
label = options [location]
return label
}
Insert cell
locationLabel.length
Insert cell
label = {
const theta = parameters.rZ - Math.PI / 2
const mag = 10 * Math.sqrt(2)
const shift = theta - Math.PI / 2
const shiftMag = locationLabel.length / 2 * 0.7
const x = Math.cos(theta) * mag// + Math.cos(shift) * shiftMag
const y = Math.sin(theta) * mag// + Math.sin(shift) * shiftMag
const label = [[x,y,1 + terrainExaggeration,1]]
label.text = locationLabel
label.textAnchor = "middle"
return label
}
Insert cell
html`${viewof perspectiveParameters}`
Insert cell
Insert cell
Insert cell
tileImg = fetchImage(`https://a.tiles.mapbox.com/v4/mapbox.terrain-rgb/${location}.png?access_token=${mapboxToken}`)
Insert cell
Insert cell
Insert cell
Insert cell
terrainMatrix = {
const size = Math.sqrt(terrain.length)
const m = d3.range(size).map((i) => {
const start = i * size
const end = start + size
const slice = Array.from(terrain.slice(start, end))
return slice
})
return m
}
Insert cell
md`From here, we diverge. I compute a bunch of height bands, and then use Marching Squares to compute isobands.`
Insert cell
size = Math.sqrt(terrain.length)
Insert cell
heightExtent = d3.extent(terrain)
Insert cell
heightThresholds = {
const range = heightExtent[1] - heightExtent[0]
const targetBand = Math.floor(range / numOfLevels)
const numOfBands = Math.ceil(range / targetBand)
const bandDelta = Math.round(range / numOfBands / targetBand) * targetBand
const minHeight = Math.max(heightExtent[0], 1)
return d3.range(numOfBands).map((i) => {
return Math.ceil(minHeight) + i * bandDelta
})
}
Insert cell
isolines = {
return heightThresholds.map((lower) => {
return {
lines: MarchingSquares.isoBands(terrainMatrix, lower, heightExtent[1] + 1, { noFrame: false, linearRing: true }),
threshold: lower
}
})
.filter(d => {
return d.lines.length > 0
})
}
Insert cell
md`Here's a 2D preview of the isobands I used to make sure this code worked.`
Insert cell
TwoDimensionsChart = {
const levels = isolines.map((lvl) => {
const rings = lvl.lines.map((isoline) => {
const path = d3.line()(isoline)
return svg`<path d="${path}" fill="none" stroke="black" stroke-width="0.1" />`
})
return svg`<g>${rings}</g>`
})
return svg`<svg width="600" height="600" viewBox="0 0 ${size} ${size}">
<g>${levels}</g>
</svg>`
}
Insert cell
xScale = d3.scaleLinear().domain([0, size]).range([-10,10])
Insert cell
yScale = d3.scaleLinear().domain([0, size]).range([-10,10])
Insert cell
zScale = d3.scaleLinear().domain(heightExtent).range([0,terrainExaggeration])
Insert cell
outlineEdges = false
Insert cell
edges = Object.assign({
top: terrainMatrix[0],
bottom: terrainMatrix[terrainMatrix.length - 1],
left: terrainMatrix.map((r) => r[0]),
right: terrainMatrix.map((r) => r[terrainMatrix.length - 1]),
})
Insert cell
md`Using some d3 scales, I compute the {x, y, z} coordinates for each isoband line, and put them into the "scene"`
Insert cell
projectedIsobands = {
const shapes = []
isolines.forEach((lvl, i) => {
//lvl.lines.forEach((isoline) => {
//const lvl = isolines[levelIndex]
lvl.lines.forEach((isoline) => {
let entity = simplify(isoline.map((d) => {
return { x: d[0], y: d[1] }
}),
lineSimplification
).map((c) => {
let copy = [
xScale(c.x),
yScale(c.y),
zScale(lvl.threshold),
1
]
return copy
})
entity.closed = true
entity.fillColor = "#fff"
entity.strokeWidth = 0.5
entity.level = i
entity.elevation = lvl.threshold
shapes.push(entity)
})
})
return processScene(shapes, parameters)
}
Insert cell
scene = {
let scene = []
let scale = 0.075
//const allRings = []
isolines.forEach((lvl, i) => {
//lvl.lines.forEach((isoline) => {
//const lvl = isolines[levelIndex]
lvl.lines.forEach((isoline) => {
let entity = simplify(isoline.map((d) => {
return { x: d[0], y: d[1] }
}),
lineSimplification
).map((c) => {
let copy = [
xScale(c.x),
yScale(c.y),
zScale(lvl.threshold),
1
]
return copy
})
entity.closed = true
entity.fillColor = "#fff"
entity.strokeWidth = 0.5
scene.push(entity)
})
})
scene.push(label)
const east = [[14, 0, terrainExaggeration / 2, 1]]
east.text = "E"
east.textAnchor = "middle"
const west = [[-14, 0, terrainExaggeration / 2, 1]]
west.text = "W"
west.textAnchor = "middle"
const south = [[0, 14, terrainExaggeration / 2, 1]]
south.text = "S"
south.textAnchor = "middle"
const north = [[0, -14, terrainExaggeration / 2, 1]]
north.text = "N"
north.textAnchor = "middle"
scene.push(north)
scene.push(south)
scene.push(east)
scene.push(west)
if (outlineEdges) {
const topEdge = edges.top.map((height, i) => {
return [
xScale(i),
yScale(0),
zScale(height),
1
]
})

scene.push(topEdge)
const bottomEdge = edges.bottom.map((height, i) => {
return [
xScale(i),
yScale(size),
zScale(height),
1
]
})

scene.push(bottomEdge)

const leftEdge = edges.left.map((height, i) => {
return [
xScale(0),
yScale(i),
zScale(height),
1
]
})

scene.push(leftEdge)

const rightEdge = edges.right.map((height, i) => {
return [
xScale(size),
yScale(i),
zScale(height),
1
]
})

scene.push(rightEdge)
}
return scene
}
Insert cell
parameters = Object.assign({ ...worldParameters, ...perspectiveParameters })
Insert cell
processedScene = {
var t0 = performance.now()
const result = processScene(scene, parameters)
var t1 = performance.now()
console.log(`processor: ${t1 - t0}`)
return result
}
Insert cell
anaglyphRenderer = (processedScene) => {
var t0 = performance.now()
const leftLayer = []
const rightLayer = []
const behindProjectionPlane = (item) => {
return item.map(points => {
points.map(d => d[0][2]).find((d => d <= 0)) === undefined &&
points.map(d => d[1][2]).find((d => d <= 0)) === undefined
})

return true
}
let allLeftPath = ''
let allRightPath = ''
const svgStuff = processedScene
.filter(behindProjectionPlane)
.forEach((item) => {
if (item.length > 1) {
const leftPath = "M " + item.map(p => `${p[0][0]} ${p[0][1]}`).join(' L ') + ((item.closed) ? " Z" : '')
const rightPath = "M " + item.map(p => `${p[1][0]} ${p[1][1]}`).join(' L ') + ((item.closed) ? " Z" : '')
// allLeftPath += ' ' + leftPath
// allRightPath += ' ' + rightPath
leftLayer.push(svg`<path d="${leftPath}" fill="${(item.fillColor) ? item.fillColor : "none"}" stroke="${colors.left}" stroke-width="${(item.strokeWidth) ? item.strokeWidth : 1}" />`)
rightLayer.push(svg`<path d="${rightPath}" fill="${(item.fillColor) ? item.fillColor : "none"}" stroke="${colors.right}" stroke-width="${(item.strokeWidth) ? item.strokeWidth : 1}" />`)
} else {
if (item.text) {
let shift = 0
if (item.textAnchor === "middle") { shift = -item.text.length * 9 / 2 }
leftLayer.push(svg`<g transform="translate(${item[0][0][0] + shift},${item[0][0][1]})">${letterPlotter(item.text, { color: colors.left })}</g>`)
rightLayer.push(svg`<g transform="translate(${item[0][1][0] + shift},${item[0][1][1]})">${letterPlotter(item.text, { color: colors.right })}</g>`)
} else {
leftLayer.push(svg`<circle cx="${item[0][0][0]}" cy="${item[0][0][1]}" r="1" fill="${colors.left}" />`)
rightLayer.push(svg`<circle cx="${item[0][1][0]}" cy="${item[0][1][1]}" r="1" fill="${colors.right}" />`)
}
}
})
// if (allLeftPath !== '') {
// leftLayer.push(svg`<path d="${allLeftPath}" fill="white" stroke="${colors.left}" stroke-width="1" style="mix-blend-mode: multiply;" />`)
// }
// if (allRightPath !== '') {
// rightLayer.push(svg`<path d="${allRightPath}" fill="white" stroke="${colors.right}" stroke-width="1" style="mix-blend-mode: multiply;" />`)
// }
var t1 = performance.now()
console.log(`renderer: ${t1 - t0}`)
return svg`<g>
<g transform="translate(${width / 2},${height / 2})" style="mix-blend-mode: multiply;">
${rightLayer}
</g>
<g transform="translate(${width / 2},${height / 2})" style="mix-blend-mode: multiply;">
${leftLayer}
</g>
</g>`
}
Insert cell
gridSize = 257
Insert cell
Insert cell
computeSlipperyCoords = (zoom, lat, lon) => {
const n = Math.pow(2, zoom)
const lon_deg = lon
const lat_rad = lat * Math.PI / 180
const xtile = n * ((lon_deg + 180) / 360)
const ytile = n * (1 - (Math.log(Math.tan(lat_rad) + (1 / Math.cos(lat_rad))) / Math.PI)) / 2
return [zoom, Math.round(xtile * 100) / 100, Math.round(ytile * 100) / 100]
}
Insert cell
computeSlipperyCoords(10, 35.3606, 138.7274)
Insert cell
Insert cell
Insert cell
{
const el = svg`<svg width="${width}px" height="${height}" style="background: white">${anaglyphRenderer(processedScene)}</svg>`
let isMouseDown = false
return Object.assign(el, {
onmousedown: event => {
isMouseDown = true
},
onmousemove: event => {
if (isMouseDown === false) return;
event.preventDefault();
const newRZ = -Math.PI + event.layerX / width * 2 * Math.PI
const newRX = -Math.PI / 2 + event.layerY / height * Math.PI / 2
const newWorldParams = {
...worldParameters,
rZ: newRZ,
rX: newRX
}
const params = {
...perspectiveParameters,
...newWorldParams
}
el.querySelectorAll('*').forEach(n => n.remove());
el.append(anaglyphRenderer(processScene(scene, params)))
},
onmouseup: event => {
isMouseDown = false
}
})
}
Insert cell
Insert cell
Insert cell
viewof perspectiveParameters = {
// near, zRange, eyeGap, scale
const controller = view`<table>
<tr><td>Camera Offset: ${["cameraOffset", Inputs.range([10, 1000], { value: 40, step: 10 })]}</td></tr>
<tr><td>Near (i.e. distance in front of the carema where we start projecting, aka. the projection plane): ${["near", Inputs.range([10, 40], { value: 10, step: 1 })]}</td></tr>
<tr><td>zRange (Far - Near) - i.e. how far past the projection plane to include: ${["zRange", Inputs.range([1, 40], { value: 15, step: 1 })]}</td></tr>
<tr><td>Eye Gap: ${["eyeGap", Inputs.range([0, 4], { value: 0.8, step: 0.01 })]}</td></tr>
<tr><td>Scale: ${["scale", Inputs.range([1, 20001], { value: 3800, step: 10 })]}</td></tr>
<tr><td>Horizontal Shift Window: ${["horizontalShift", Inputs.range([-10, 10], { value: 0, step: 0.1 })]}</td></tr>
<tr><td>Vertical Shift Window: ${["verticalShift", Inputs.range([-10, 10], { value: 0, step: 0.1 })]}</td></tr>
<tr><td>Shift in X': ${["xprime", Inputs.range([-500, 500], { value: 0, step: 1 })]}</td></tr>
<tr><td>Shift in Y': ${["yprime", Inputs.range([-500, 500], { value: 0, step: 1 })]}</td></tr>
</table>`
return controller
}
Insert cell
viewof worldParameters = {
// rX, rY, rZ
const controller = view`<table>
<tr><td>Rotation in X: ${["rX", Inputs.range([-Math.PI / 2, Math.PI / 2], { value: -Math.PI * 3 / 16, step: Math.PI / 32 })]}</td></tr>
<tr><td>Rotation in Y: ${["rY", Inputs.range([-Math.PI / 2, Math.PI / 2], { value: 0, step: Math.PI / 32 })]}</td></tr>
<tr><td>Rotation in Z: ${["rZ", Inputs.range([-Math.PI, Math.PI], { value: -Math.PI / 4, step: Math.PI / 128 })]}</td></tr>
<tr><td>Shift in X: ${["mx", Inputs.range([-100, 100], { value: 0, step: 1 })]}</td></tr>
<tr><td>Shift in Y: ${["my", Inputs.range([-100, 100], { value: 0, step: 1 })]}</td></tr>
<tr><td>Shift in Z: ${["mz", Inputs.range([-100, 100], { value: 0, step: 1 })]}</td></tr>
<tr><td>Move Camera in X: ${["cx", Inputs.range([-100, 100], { value: 0, step: 1 })]}</td></tr>
<tr><td>Move Camera in Y: ${["cy", Inputs.range([-100, 100], { value: 2, step: 1 })]}</td></tr>
<tr><td>Move Camera in Z: ${["cz", Inputs.range([-100, 100], { value: 0, step: 1 })]}</td></tr>
</table>`
return controller
}
Insert cell
MarchingSquares = import(
"https://unpkg.com/marchingsquares@1.3.3/dist/marchingsquares-esm.js?module"
)
Insert cell
viewof left = {
// rX, rY, rZ
const controller = view`<table>
<tr><td>R: ${["r", Inputs.range([0, 255], { value: 255, step: 1 })]}</td></tr>
<tr><td>G: ${["g", Inputs.range([0, 255], { value: 0, step: 1 })]}</td></tr>
<tr><td>B: ${["b", Inputs.range([0, 255], { value: 0, step: 1 })]}</td></tr>
</table>`

return controller
}
Insert cell
Insert cell
colors = Object.assign({
left: `rgb(${left.r},${left.g},${left.b})`,
right: `rgb(${right.r},${right.g},${right.b})`
})
Insert cell
import { renderer, processScene } with { height } from '@tonyhschu/anaglyph-pipeline-in-gpujs'
Insert cell
import {letterPlotter} from '@tonyhschu/experiment-with-plottable-letters'
Insert cell
import {view} from '@tomlarkworthy/view'
Insert cell
simplify = require('simplify-js')
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