Public
Edited
Dec 22, 2022
Fork of Mapzen DEM
Insert cell
viewof contourNum = html`<input type=number min=0 value=40 step=any>`
Insert cell
container = html`<div id="mymap" style="height:${height}px;">`;
Insert cell
map._pixelOrigin
Insert cell
elevationContours
Insert cell
mutable z = 42
Insert cell
Insert cell
map = {
const oriental = [35.0310, -76.6930];
const charleston = [32.7765, -79.9311];
const florence = [34.583, -78.944];
const sanMartin = [37.081667, -121.596667]
const map = L.map("mymap").setView(sanMartin, 9);
map.scrollWheelZoom.disable();
L.control.scale({position: 'bottomright'}).addTo(map);

L.tileLayer("https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}@2x.png", {
attribution: "Wikimedia maps | &copy; <a href='http://osm.org/copyright'>OpenStreetMap</a> contributors"
}).addTo(map);
// empty tile layer, used as a convenient way to know which terrain tiles to load (manually)
let terrainLayer = L.tileLayer('', {
attribution: 'Mapzen terrain tiles'
}).addTo(map);
let demPane = map.createPane('dem');
// demPane.appendChild(demCanvas); // Uncomment to show raw DEM tiles
demPane.appendChild(contourCanvas);
// Reposition the single DEM canvas in the Leaflet map
// This also triggers (via terrainTileObjects) a reload and rerendering of the DEM tiles
let delay;
const draw = () => {
clearTimeout(delay);
// a short delay makes it a little easier to pan around without constant redrawing
delay = setTimeout(() => {
const ctx = demCanvas.getContext('2d');
const contourCtx = contourCanvas.getContext('2d');
ctx.clearRect(0, 0, width, height);
contourCtx.clearRect(0, 0, width, height);
const top_left = map.containerPointToLayerPoint([0, 0]);
mutable z = "top_left: " + top_left;
L.DomUtil.setPosition(demCanvas, top_left);
L.DomUtil.setPosition(contourCanvas, top_left);
// trigger elevation data loading and rendering resetting terrainTileObjects
mutable terrainTileObjects = Object.entries(terrainLayer._tiles).map(t => t[1]);
}, 500);
}
// Uncomment the following to redraw the map on pan/zoom
map.on('moveend zoomend', () => { draw(); })
.on('movestart zoomstart', () => { mutable z = "start"; clearTimeout(delay); });
draw()
return map
}
Insert cell
Insert cell
elevationContours = {
const context = contourCanvas.getContext('2d');
const path = d3.geoPath(projection, context);
//const coordinates = contourValues.flatMap((contourValue) => contours.contour(elevationData, contourValue).coordinates);
const coordinates = contours(elevationData).flatMap((polygon) => polygon.coordinates)
//const multiPolygon = contours.contour(elevationData, contourValue)
context.lineWidth = 2;
context.lineJoin = "round";
context.strokeStyle = "crimson";
context.fillStyle = 'rgba(0,0,0,.1)';
context.clearRect(0, 0, width, height);
context.beginPath();
//contourValues.forEach((contourValue) => path(contours.contour(elevationData, contourValue)));
contours(elevationData).forEach((polygon) => path(polygon));
context.stroke();
context.fill();
return coordinates.map(d => d[0]); // Return array of polygon exteriors
}
Insert cell
// Add tint canvas to the DOM
tintCanvas = DOM.canvas(width, height);
Insert cell
mutable terrainTileObjects = [];
Insert cell
width * height
Insert cell
// Load a single canvas with elevation computed from tiles
elevationData = {
const terrainTileUrl = (coords) => `https://elevation-tiles-prod.s3.amazonaws.com/terrarium/${coords.z}/${coords.x}/${coords.y}.png`;
// Get all terrain tiles in map view
const terrainPromises = terrainTileObjects.map(tile => {
return d3.image(terrainTileUrl(tile.coords), {crossOrigin: "anonymous"});
});
const images = await Promise.all(terrainPromises)
// Create large canvas to for assembling individual terrain tiles
const demContext = demCanvas.getContext('2d')
demContext.clearRect(0, 0, width, height);
const mapBounds = container.getBoundingClientRect();
// Draw each tile in the right place on the big canvas
images.forEach((image, i) => {
const bounds = terrainTileObjects[i].el.getBoundingClientRect();
demContext.drawImage(image, bounds.x - mapBounds.x, bounds.y - mapBounds.y);
});
// Load canvas with elevations (before blurring)
const demImageData = demContext.getImageData(0,0,width,height);
const demData = demImageData.data;
const values = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let k = x + y * width;
let k4 = 4 * k;
demImageData.data[k4] = getElevation(4 * k, demData);
demImageData.data[k4 + 1] = 0;
demImageData.data[k4 + 2] = 0;
}
}

StackBlur.R(demImageData, 10);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let k = x + y * width;
values[k] = demImageData.data[4 * k];
}
}
return values;
}
Insert cell
d3.extent(elevationData)
Insert cell
// Convert terrain tile RGB to elevation in meters
getElevation = (index, demData) => {
if (!demData) return 0; // TODO: Check this
if (index < 0 || demData[index] === undefined) return undefined; // TODO: Check this
let elevation = ((demData[index] * 256 + demData[index+1] + demData[index+2] / 256) - 32768);
return (elevation == 32768) ? null : elevation;
}
Insert cell
// Create tinted canvas here (reconstruct
tintImageData = {
const tintData = new Uint8ClampedArray(width * height * 4);
const white = d3.color('#fff');
elevationData.forEach((d,i) => {
const n = i * 4;
// get color based on elevation
const tint = d3.color(tints(d)) || white;
tintData[n] = tint.r;
tintData[n + 1] = tint.g;
tintData[n + 2] = tint.b;
tintData[n + 3] = tint.opacity * 255;

});
const tintImageData = new ImageData(tintData, width, height);
tintCanvas.getContext('2d').putImageData(tintImageData, 0, 0);
return tintImageData;
}
Insert cell
tints = {
const tintInterpolation = d3.interpolateHclLong;
const valueRange = d3.extent(elevationData);
// besides the "low" and "high" colors, I want one in between, in this case 75% of the way between low and high
const middleValue = .65*(Math.max(0, valueRange[0]) + valueRange[1]);
// for no coloring, try d3.scaleLinear([0, 1]).range(['#fff', '#fff']).clamp(true);
//c7d6ba
return d3.scaleSqrt()
.domain([Math.max(0, valueRange[0]), middleValue, .9*valueRange[1]])
.range(['#a7b898','#ffeee5', '#fff'])
.clamp(true)
.interpolate(tintInterpolation);
}
Insert cell
Insert cell
contours = d3.contours().size([width, height]).thresholds(contourNum)
Insert cell
Insert cell
Insert cell
Insert cell
// Add DEM canvas to the DOM (it gets repositioned elsewhere)
demCanvas = DOM.canvas(width, height);
Insert cell
// Add canvas for contours to the DOM (it gets repositioned elsewhere)
contourCanvas = DOM.canvas(width, height);
Insert cell
Insert cell
Insert cell
// https://github.com/flozz/StackBlur
//StackBlur = require("stackblur-canvas")
Insert cell
StackBlur = require(await FileAttachment('stack-blur.js').url());
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