Published
Edited
Oct 9, 2019
3 forks
1 star
Also listed in…
leaflet
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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more