Published
Edited
Dec 25, 2019
7 stars
Insert cell
Insert cell
// Interval of thresholds
viewof interval = html`<input type=range min="10" max="400" step="10" value="20">`
Insert cell
{
const renderer = new THREE.WebGLRenderer({antialias: true});
const controls = new THREE.OrbitControls(camera, renderer.domElement);
invalidation.then(() => (controls.dispose(), renderer.dispose()));
renderer.setSize(width, height);
renderer.setPixelRatio(devicePixelRatio);
controls.addEventListener("change", () => renderer.render(scene, camera));
renderer.render(scene, camera);
return renderer.domElement;
}
Insert cell
md`
## 1. Fetch dem data

Dem data can be fetched as 2D array using *fetchTile()* function from my notebook: [Fetch elevation tile](https://observablehq.com/@sw1227/fetch-elevation-tile).

*fetchTile()* is imported at the end of this notebook.

`
Insert cell
// Fetch dem data as 256 x 256 array
// Mt. Nabewari, Japan
dem = fetchTile({z: 13, x: 7067, y: 3284})
Insert cell
Insert cell
// Shape of dem data
shape = ({x: dem[0].length, y: dem.length})
Insert cell
// Flatten 2D array
flatDem = [].concat.apply([], dem)
Insert cell
// min/max elevation
elevRange = d3.extent(flatDem)
Insert cell
// Thresholds for contour
thresh = d3.range(Math.round(elevRange[0]/interval)*interval, elevRange[1], interval)
Insert cell
// Dem data -> Contours
contours = d3.contours()
.size([shape.x, shape.y])
.thresholds(thresh)
(flatDem)
Insert cell
// Color scale with elevation normalization
color = d => d3.interpolateViridis((d-elevRange[0])/(elevRange[1]-elevRange[0]))
Insert cell
// Scale function for cooridinates in contour
// (Without scaling, coordinates corresponds to index)
function scaleContour(contours, scale) {
return contours.map(({type, value, coordinates}) => (
{type, value, coordinates: coordinates.map(rings => (
rings.map(points => (
points.map(([x, y]) => ([
x*scale, y*scale
]))
))
))}
));
}
Insert cell
Insert cell
// TODO: 0.2: handcrafted
heightScale = d => 0.3 * (d-elevRange[0])/(elevRange[1]-elevRange[0]);
Insert cell
// Scale xy cooridinates to [0, 1]
xyContour = scaleContour(contours, 1/256)
Insert cell
// 3D lineString meshes of contours
contourLineStrings = {
const lines = [];
xyContour.forEach(polygon => {
// Set color based on elevation
const lineColor = color(polygon.value);
// 2D to 3D: be carefull about right-handed / lef-handed system
polygon.coordinates.forEach(rings => {
rings.forEach(points => {
const points3 = points.map(([x, y]) => [y-0.5, x-0.5, heightScale(polygon.value)]);
lines.push(lineString(points3, lineColor));
});
});
});
return lines;
}
Insert cell
// Create lineString mesh with given coordinates and color
function lineString(points, color) {
const material = new THREE.LineBasicMaterial({color: color, blending: THREE.AdditiveBlending});
const geometry = new THREE.Geometry();
points.forEach(p => {
geometry.vertices.push(new THREE.Vector3(...p));
});

return new THREE.Line(geometry, material);
}
Insert cell
scene = {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
contourLineStrings.forEach(l => scene.add(l));
return scene;
}
Insert cell
camera = {
const fov = 45;
const aspect = width / height;
const near = 0.01;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0.5, 0.5, 0.5);
camera.up.set(0, 0, 1);
camera.lookAt(new THREE.Vector3(0, 0, 0));
return camera;
}
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