Example of how to create dynamic map distance scales.
Map distance scales
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<title>Map distance scales</title> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
padding: 0; | |
margin: 0; | |
font-family: helvetica, arial, sans-serif; | |
} | |
.parishes { | |
fill: white; | |
stroke: #777; | |
stroke-opacity: 0.5; | |
stroke-width: 0.5px; | |
opacity: 0.8; | |
} | |
.parish-border { | |
fill: none; | |
stroke: #353535; | |
stroke-opacity: 0.4; | |
stroke-width: 0.5px; | |
opacity: 0.8; | |
} | |
.state-border { | |
fill: none; | |
stroke: #585858; | |
} | |
.distance-scale { | |
font-size: 11px; | |
line-height: 11px; | |
position: absolute; | |
font-weight: 500; | |
text-transform: uppercase; | |
color: #000; | |
} | |
.distance-scale-line { | |
stroke: #000; | |
stroke-width: 1; | |
stroke-opacity: 1; | |
opacity: 1; | |
fill: #000; | |
} | |
</style> | |
</head> | |
<body> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script src="//d3js.org/queue.v1.min.js"></script> | |
<script src="//d3js.org/topojson.v1.min.js"></script> | |
<script> | |
var width = 960, | |
height = 500; | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
var projection1 = d3.geo.albers() | |
.center([0, 31.2]) | |
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole) | |
.parallels([29, 33]) | |
.translate([width * 0.1, height / 2]) | |
.scale(1000); | |
var projection2 = d3.geo.albers() | |
.center([0, 31.2]) | |
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole) | |
.parallels([29, 33]) | |
.translate([width * 0.25, height / 2]) | |
.scale(2000); | |
var projection3 = d3.geo.albers() | |
.center([0, 31.2]) | |
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole) | |
.parallels([29, 33]) | |
.translate([width * 0.5, height / 2]) | |
.scale(3000); | |
var projection4 = d3.geo.albers() | |
.center([0, 31.2]) | |
.rotate([91.6, 0]) // Rotate CCW (looking down onto North Pole) | |
.parallels([29, 33]) | |
.translate([width * 0.8, height / 2]) | |
.scale(4000); | |
var map_path1 = d3.geo.path().pointRadius(2).projection(projection1); | |
var map_path2 = d3.geo.path().pointRadius(2).projection(projection2); | |
var map_path3 = d3.geo.path().pointRadius(2).projection(projection3); | |
var map_path4 = d3.geo.path().pointRadius(2).projection(projection4); | |
queue() | |
.defer(d3.json, "parishes.json") | |
.await(ready); | |
function pixelLength(this_topojson, this_projection, miles) { | |
// topojson = topojson.feature(districts, districts.objects['afghanistan-districts']) | |
// Calculates the window pixel length for a given map distance. | |
// Not sure if math is okay, given arcs, projection distortion, etc. | |
var actual_map_bounds = d3.geo.bounds(this_topojson); | |
var radians = d3.geo.distance(actual_map_bounds[0], actual_map_bounds[1]); | |
var earth_radius = 3959; // miles | |
var arc_length = earth_radius * radians; // s = r * theta | |
var projected_map_bounds = [ | |
this_projection(actual_map_bounds[0]), | |
this_projection(actual_map_bounds[1]) | |
]; | |
var projected_map_width = projected_map_bounds[1][0] - projected_map_bounds[0][0]; | |
var projected_map_height = projected_map_bounds[0][1] - projected_map_bounds[1][1]; | |
var projected_map_hypotenuse = Math.sqrt( | |
(Math.pow(projected_map_width, 2)) + (Math.pow(projected_map_height, 2)) | |
); | |
var pixels_per_mile = projected_map_hypotenuse / arc_length; | |
var pixel_distance = pixels_per_mile * miles; | |
return pixel_distance; | |
} | |
function ready(error, data) { | |
if (error) throw error; | |
// First multiple | |
var map1 = svg.append("g") | |
.attr("class", "parishes") | |
.attr("id", "map1") | |
.attr("transform", "translate(0, -90)"); | |
map1.selectAll("path") | |
.data(topojson.feature(data, data.objects.parishes).features) | |
.enter().append("path") | |
.attr("d", map_path1); | |
map1.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; })) | |
.attr("class", "parish-border") | |
.attr("d", map_path1); | |
map1.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; })) | |
.attr("class", "state-border") | |
.attr("d", map_path1); | |
// Second multiple | |
var map2 = svg.append("g") | |
.attr("class", "parishes") | |
.attr("id", "map2") | |
.attr("transform", "translate(0, -60)"); | |
map2.selectAll("path") | |
.data(topojson.feature(data, data.objects.parishes).features) | |
.enter().append("path") | |
.attr("d", map_path2); | |
map2.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; })) | |
.attr("class", "parish-border") | |
.attr("d", map_path2); | |
map2.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; })) | |
.attr("class", "state-border") | |
.attr("d", map_path2); | |
// Third multiple | |
var map3 = svg.append("g") | |
.attr("class", "parishes") | |
.attr("id", "map3") | |
.attr("transform", "translate(0, -30)"); | |
map3.selectAll("path") | |
.data(topojson.feature(data, data.objects.parishes).features) | |
.enter().append("path") | |
.attr("d", map_path3); | |
map3.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; })) | |
.attr("class", "parish-border") | |
.attr("d", map_path3); | |
map3.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; })) | |
.attr("class", "state-border") | |
.attr("d", map_path3); | |
// Fourth multiple | |
var map4 = svg.append("g") | |
.attr("class", "parishes") | |
.attr("id", "map4"); | |
map4.selectAll("path") | |
.data(topojson.feature(data, data.objects.parishes).features) | |
.enter().append("path") | |
.attr("d", map_path4); | |
map4.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a !== b; })) | |
.attr("class", "parish-border") | |
.attr("d", map_path4); | |
map4.append("path") | |
.datum(topojson.mesh(data, data.objects.parishes, function(a, b) { return a === b; })) | |
.attr("class", "state-border") | |
.attr("d", map_path4); | |
// Distance scale | |
// Line path generator | |
var line = d3.svg.line() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return d.y; }) | |
.interpolate("basis"); | |
// Scale1 | |
var pixels_per_hundred_1 = pixelLength(topojson.feature(data, data.objects['parishes']), projection1, 100); | |
var distance_scale1 = svg.selectAll("#distance-scale1") | |
.data([pixels_per_hundred_1]) | |
.enter().append("g") | |
.attr("class", "distance-scale") | |
.attr("id", "distance-scale1") | |
.attr("transform", "translate(-35, 0)") | |
.attr("width", function(d) { return d; }); | |
distance_scale1.append('text') | |
.attr("x", width * 0.1) | |
.attr("y", height * 0.1) | |
.attr("text-anchor", "start") | |
.text("100 miles"); | |
distance_scale1.append('path') | |
.attr("class", "distance-scale-line") | |
.attr("d", function(d, i) { | |
var lineData = [ | |
{"x": width * 0.1, "y": height * 0.1 + 10}, | |
{"x": width * 0.1 + d, "y": height * 0.1 + 10} | |
]; | |
return line(lineData); | |
}); | |
// Scale2 | |
var pixels_per_hundred_2 = pixelLength(topojson.feature(data, data.objects['parishes']), projection2, 100); | |
var distance_scale2 = svg.selectAll("#distance-scale2") | |
.data([pixels_per_hundred_2]) | |
.enter().append("g") | |
.attr("class", "distance-scale") | |
.attr("id", "distance-scale2") | |
.attr("transform", "translate(-70, 0)") | |
.attr("width", function(d) { return d; }); | |
distance_scale2.append('text') | |
.attr("x", width * 0.25) | |
.attr("y", height * 0.1) | |
.attr("text-anchor", "start") | |
.text("100 miles"); | |
distance_scale2.append('path') | |
.attr("class", "distance-scale-line") | |
.attr("d", function(d, i) { | |
var lineData = [ | |
{"x": width * 0.25, "y": height * 0.1 + 10}, | |
{"x": width * 0.25 + d, "y": height * 0.1 + 10} | |
]; | |
return line(lineData); | |
}); | |
// Scale3 | |
var pixels_per_hundred_3 = pixelLength(topojson.feature(data, data.objects['parishes']), projection3, 100); | |
var distance_scale3 = svg.selectAll("#distance-scale3") | |
.data([pixels_per_hundred_3]) | |
.enter().append("g") | |
.attr("class", "distance-scale") | |
.attr("id", "distance-scale3") | |
.attr("transform", "translate(-110, 0)") | |
.attr("width", function(d) { return d; }); | |
distance_scale3.append('text') | |
.attr("x", width * 0.5) | |
.attr("y", height * 0.1) | |
.attr("text-anchor", "start") | |
.text("100 miles"); | |
distance_scale3.append('path') | |
.attr("class", "distance-scale-line") | |
.attr("d", function(d, i) { | |
var lineData = [ | |
{"x": width * 0.5, "y": height * 0.1 + 10}, | |
{"x": width * 0.5 + d, "y": height * 0.1 + 10} | |
]; | |
return line(lineData); | |
}); | |
// Scale4 | |
var pixels_per_hundred_4 = pixelLength(topojson.feature(data, data.objects['parishes']), projection4, 100); | |
var distance_scale4 = svg.selectAll("#distance-scale4") | |
.data([pixels_per_hundred_4]) | |
.enter().append("g") | |
.attr("class", "distance-scale") | |
.attr("id", "distance-scale4") | |
.attr("transform", "translate(-140, 0)") | |
.attr("width", function(d) { return d; }); | |
distance_scale4.append('text') | |
.attr("x", width * 0.8) | |
.attr("y", height * 0.1) | |
.attr("text-anchor", "start") | |
.text("100 miles"); | |
distance_scale4.append('path') | |
.attr("class", "distance-scale-line") | |
.attr("d", function(d, i) { | |
var lineData = [ | |
{"x": width * 0.8, "y": height * 0.1 + 10}, | |
{"x": width * 0.8 + d, "y": height * 0.1 + 10} | |
]; | |
return line(lineData); | |
}); | |
} | |
// Allows iframe on bl.ocks.org. | |
d3.select(self.frameElement).style("height", height + "px"); | |
</script> | |
</body> | |
</html> |
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