Equidistant lines and HTML5 Location API.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Equidistant Lines</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
</head>
<body>
<style>
body {
margin: 0;
cursor: pointer;
}
svg {
margin: 5px;
background-color: #A7DBD8;
}
.sphere {
fill: none;
stroke: #79A09E;
}
.land {
fill: #E0E4CC;
stroke: #ACAF9F;
stroke-width: 1;
}
.graticule {
fill: none;
stroke: #79A09E;
stroke-width: 1;
stroke-dasharray: 1,1;
}
.equidistant {
stroke: #490A3D;
stroke-width: 1;
fill: none;
}
.dist-km {
fill: #490A3D;
font-family: 'Helvetica Neue';
font-weight: bold;
font-size: 12px;
}
</style>
<div id="map-container"></div>
<script>
// Set the dimensions of the map
var width = 960,
height = 480;
// Generate angles. Each degree is about 111 km
var angles = d3.range(10, 180, 10);
// Create a selection for the container div and append the svg element
var div = d3.select('#map-container'),
svg = div.append('svg');
// Set the size of the SVG element
svg.attr('width', width).attr('height', height);
// Create and configure a geographic projection
var projection = d3.geo.equirectangular()
.translate([width / 2, height / 2])
.scale(height / Math.PI);
// Create and configure a path generator
var pathGenerator = d3.geo.path()
.projection(projection);
// Create and configure the graticule generator (one line every 20 degrees)
var graticule = d3.geo.graticule()
.step([10, 10]);
// Retrieve the geographic data asynchronously
d3.json('countries.geojson', function(err, data) {
// Throw errors on getting or parsing the file
if (err) { throw err; }
// Shpere
var sphere = svg.selectAll('path.sphere').data([{type: 'Sphere'}]);
sphere.enter().append('path').classed('sphere', true);
sphere.attr('d', pathGenerator);
sphere.exit().remove();
// Graticule lines (behind the land)
var lines = svg.selectAll('path.graticule').data([graticule()]);
lines.enter().append('path').classed('graticule', true);
lines.attr('d', pathGenerator);
lines.exit().remove();
// Land
var land = svg.selectAll('path.land').data([data]);
land.enter().append('path').classed('land', true);
land.attr('d', pathGenerator);
land.exit().remove();
});
var origin;
svg.on('click', function(ev) {
// Get the (x, y) position of the mouse (relative to the SVG element)
var pos = d3.mouse(svg.node()),
px = pos[0],
py = pos[1];
// Compute the corresponding geographic coordinates using the inverse projection
var coords = projection.invert([px, py]);
origin = coords;
// Circles
var circleGen = d3.geo.circle()
.origin(coords)
.angle(function(d) { return d; })
.precision(1);
var circlesData = [];
angles.forEach(function(angle) {
circleGen.angle(angle);
circlesData.push(circleGen());
});
circlesData.reverse();
var equidistantPaths = svg.selectAll('path.equidistant').data(circlesData);
equidistantPaths.enter().append('path')
.classed('equidistant', true);
equidistantPaths
.attr('d', pathGenerator);
equidistantPaths.exit().remove();
});
svg.on('mousemove', function(ev) {
if (!origin) { return ; }
// Get the (x, y) position of the mouse (relative to the SVG element)
var pos = d3.mouse(svg.node()),
px = pos[0],
py = pos[1];
// Compute the corresponding geographic coordinates using the inverse projection
var coords = projection.invert([px, py]);
// Calculate the angular and km distance between origin and the mouse position
var distRad = d3.geo.distance(origin, coords),
distKm = 40e3 * distRad / (2 * Math.PI);
// Update the labels
var label = svg.selectAll('text.dist-km').data([distKm]);
label.enter().append('text').classed('dist-km', true);
label
.attr('x', px + 15)
.attr('y', py)
.text(d3.round(distKm, 0) + ' km');
label.exit().remove();
});
</script>
</body>
</html>
# Download and Transform the 1:50m Country Shapefiles from Natural Earth
# http://www.naturalearthdata.com/downloads/110m-physical-vectors/
URL = http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/110m/physical/ne_110m_land.zip
# Download the zip file from the Natural Earth server
ne_110m_land.zip:
curl -LO $(URL)
# Unzip the shapefiles
ne_110m_land.shp: ne_110m_land.zip
unzip ne_110m_land.zip
touch ne_110m_land.shp
# Convert the shapefiles to GeoJSON
land.geojson: ne_110m_land.shp
ogr2ogr -f GeoJSON land.geojson ne_110m_land.shp
# Convert the GeoJSON file to TopoJSON
land.topojson: countries.json
topojson -p -o land.topojson land.geojson
# Remove source and temporary files
clean:
rm ne_110m_land*