block by thomasthoren 6a543c4d804f35a240f9

Map distance scales

Full Screen

Example of how to create dynamic map distance scales.

index.html

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