Published
Edited
Oct 9, 2019
3 forks
1 star
Also listed in…
mapping
Insert cell
Insert cell
Insert cell
Insert cell
contourValue
Insert cell
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
count = {
// Set the opacity of the hotels based on their position relative to the contours
contourValue
let count = 0;
const tr = d3.select(table).selectAll('tbody tr');
const top_left = map.containerPointToLayerPoint([0, 0]); // KLUDGE
mutable z = top_left;
hotelsLayer.eachLayer(function(hotelLayer){
function inside(polygon) { return d3.polygonContains(polygon, this) }
const id = hotelLayer.feature.properties.id;
// BUG -- hotel positions do not "pan" with contours
// let point = [hotelLayer._point.x, hotelLayer._point.y]; // DO NOT USE ._point!!
let point = [hotelLayer._point.x - top_left.x, hotelLayer._point.y - top_left.y]

let opacity = elevationContours.some(inside, point) ? 1 : 0;
count += opacity;
// Hide the feature on the map if it's not inside one of the polygons
hotelLayer.setStyle({opacity: opacity, fillOpacity: opacity});
// mutable z = 'checking ' + elevationContours.length + " point: " + point;
// mutable z = hotelLayer
// Hide the row in the table if the feature isn't visible on the map
tr.filter(d => d.properties.id == hotelLayer.feature.properties.id)
.style('display', (opacity == 0) ? 'none' : '');
});
return html`${count} hotels in range (${d3.select(table).selectAll('tbody tr').nodes().length} total)`
}
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(charleston, 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
hotelsLayer = {
// Add the hotels to the map as GeoJSON points
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);
return layer;
}
Insert cell
Insert cell
elevationContours = {
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.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])
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
csv = d3.csv('https://pbogden.com/mapping/scrapedhotels.csv');
Insert cell
hotels = 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