Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Map distance scales

Example of how to create dynamic map distance scales.

<!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