Published
Edited
Sep 24, 2019
Fork of Mapzen DEM
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
mutable draws = 0
Insert cell
Insert cell
Insert cell
container = html`<div id="mymap" style="height:${height}px;">`;
Insert cell
Insert cell
Insert cell
map = {
const oriental = [35.0310, -76.6930];
const charleston = [32.7765, -79.9311];
const florence = [34.583, -78.944];
const map = L.map("mymap").setView(florence, 7);
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 = () => {
mutable zoomLevel = map.getZoom();
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); // Clear contours immediately before repositioning canvas
const top_left = map.containerPointToLayerPoint([0, 0]);
L.DomUtil.setPosition(demCanvas, top_left);
L.DomUtil.setPosition(contourCanvas, top_left);
// reassigning a value to tileObjects kicks off elevation data loading and rendering, in notebook cells below
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', () => { clearTimeout(delay); });
draw()
return map
}
Insert cell
mutable zoomLevel = 42
Insert cell
hotelsLayer = {
var geojsonMarkerOptions = {
radius: 4,
fillColor: "#ff7800",
color: "#000",
weight: 1,
//opacity: 1, // Opacity set elsewhere
//fillOpacity: 0.8
};
// This function defines how GeoJSON points spawn Leaflet layers.
var pointToLayer = function (feature, latlng) {
return L.circleMarker(latlng, geojsonMarkerOptions);
}
// See: https://leafletjs.com/examples/geojson/
let layer = L.geoJSON(hotels, { pointToLayer: pointToLayer }).addTo(map);
let points = L.geoJSON(florence, { pointToLayer: pointToLayer }).addTo(map);
return layer;
}
Insert cell
Insert cell
elevationContours = {
mutable draws++;
const context = contourCanvas.getContext('2d');
const path = d3.geoPath(projection, context);
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();
path(contours.contour(elevationData, contourValue));
context.stroke();
context.fill();
return multiPolygon
}
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`;
// BEGIN NEW
function tms(coords) { return coords.z + '/' + coords.x + '/' + ((1 << coords.z) - coords.y - 1) };
const terrainTileUrl = (coords) => `https://pbogden.com/mapping/tiles/${tms(coords)}.png`;
const bad_tile = 'Bad tile';
const terrainPromises = terrainTileObjects.map(tile => {
return d3.image(terrainTileUrl(tile.coords), {crossOrigin: "anonymous"})
.catch(error => bad_tile); // Catch the error when there are missing tiles
});
// END NEW
// 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) => {
if (image != bad_tile) {
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++) {
const k = x + y * width;
values[k] = demData[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])
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("https://bl.ocks.org/mbostock/raw/818053c76d79d4841790c332656bf9da/94f23ce8973297355271ae07f3c718486ef729e7/stack-blur.js")
Insert cell
florence = d3.json('https://pbogden.com/mapping/data/florence/pts.json');
Insert cell
csv = d3.csv('https://pbogden.com/mapping/scrapedhotels.csv');
Insert cell
json = d3.json('https://pbogden.com/mapping/florence_hotels_response.json');
Insert cell
hotels = json.map(d => (
{ type: "Feature",
geometry: { type: "Point", coordinates: [+d.longitude, +d.latitude] },
properties: {id: d.hotel_id, Name: d.hotel_name, Address: d.address_trans, City: d.city_trans}
}))
Insert cell
hotels2 = csv.map((d, i) => ({ type: "Feature",
geometry: { type: "Point", coordinates: [+d.longitude, +d.latitude] },
properties: { id: i, name: d.hotel_name, address: d.address_trans, city: d.city_trans } }));
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