Skip to content

Instantly share code, notes, and snippets.

@nrabinowitz
Created December 9, 2012 20:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nrabinowitz/4246925 to your computer and use it in GitHub Desktop.
Save nrabinowitz/4246925 to your computer and use it in GitHub Desktop.
Raster geocoding with D3.js

This demonstrates raster-based reverse geocoding using canvas and D3.js. Geocoding is based on the color of the pixel at a given projected position. Note that the canvas is only shown here for the sake of explanation and debugging - this would in fact probably work faster if the canvas was not attached to the document at all.

The biggest remaining issue here is precision, which depends on:

  • the size of the canvas, and
  • the projection used.

Determining the optimum size based on the accuracy of your data is left as an exercise for the reader. Edge cases will also fail here, generally returning null - one option might be to stroke neighborhoods in a color, and then return an "uncertain" value for any non-grayscale pixel.

/**
* Raster-based geocoder, using an off-screen canvas
*/
d3.geo.rasterCoder = function() {
// projection is arbitrary, so let's use a fast one
var projection = d3.geo.equirectangular(),
canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
// XXX: determine canvas size based on precision parameter
canvasMax = 960,
colorMap,
features;
function coder(point) {
var coords = projection(point),
pixel = context.getImageData(~~coords[0], ~~coords[1], 1, 1).data;
return pixel[3] ? features[colorMap[d3.rgb(pixel[0], pixel[1], pixel[2])]] : null;
}
function redrawCanvas() {
// get the bounding box for the data
var left = Infinity,
bottom = -Infinity,
right = -Infinity,
top = Infinity;
// reset projection
projection
.scale(1)
.translate([0, 0]);
features.forEach(function(feature) {
d3.geo.bounds(feature).forEach(function(coords) {
coords = projection(coords);
var x = coords[0],
y = coords[1];
if (x < left) left = x;
if (x > right) right = x;
if (y > bottom) bottom = y;
if (y < top) top = y;
});
});
// projected size
var pWidth = Math.abs(right - left),
pHeight = Math.abs(bottom - top),
widthDetermined = pWidth > pHeight,
aspect = pWidth / pHeight,
// pixel size
width = widthDetermined ? canvasMax : ~~(canvasMax * aspect),
height = widthDetermined ? ~~(canvasMax / aspect) : canvasMax,
scale = width / pWidth;
canvas.width = width;
canvas.height = height;
// set x translation
transX = -left * scale,
// set y translation
transY = -top * scale;
// update projection
projection
.scale(scale)
.translate([transX, transY]);
// set up projection
var path = d3.geo.path()
.projection(projection)
.context(context),
// set up colors
colors = d3.scale.linear()
.domain([0, features.length])
.range(['#000000', '#ffffff']);
colorMap = {};
// clear canvas with a different color
context.clearRect(0, 0, width, height);
features.forEach(function(feature, i) {
var color = colors(i);
colorMap[color] = i;
context.fillStyle = color;
context.strokeStyle = color;
context.beginPath();
path(feature);
context.fill();
context.stroke();
});
}
// for debugging
coder.canvas = function() {
return canvas;
};
coder.features = function(x) {
if (!arguments.length) return features;
features = x;
if (features) redrawCanvas();
return coder;
};
coder.size = function(x) {
if (!arguments.length) return canvasMax;
canvasMax = x;
if (features) redrawCanvas();
return coder;
};
return coder;
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Raster reverse geocoding with D3.js</title>
<style>
path {
fill: none;
stroke: black;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script src="geocoder.js"></script>
<script>
var geocoder;
d3.json("la.json", function(err, collection) {
// initialize the geocoder
geocoder = d3.geo.rasterCoder()
.size(800)
.features(collection.features);
// just for debugging
document.body.appendChild(geocoder.canvas());
// some LA locations
var points = [
[-118.40309143066406,34.07199987534163],
[-118.11264038085938,34.016241889667015],
[-118.45664978027344,33.99745799229644],
[-119,34]
];
d3.select('body').selectAll('p')
.data(points)
.enter().insert('p', 'canvas')
.text(function(d) {
var neighborhood = geocoder(d);
return JSON.stringify(d) + ' : ' + (neighborhood && neighborhood.properties.name);
})
});
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment