在 topojson 上查找比例距离

Finding Scaled Distance on topojson

我一直在评估将像素 space 与给定投影的距离(以实际单位表示)相关联的不同方法。我发现以下内容非常有用:

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;

但我当前的应用程序将通过减少所需的计算步骤而受益匪浅。是否有任何更简单或更 'elegant' 的解决方案可供 topojson 开发人员使用?

我想我明白你在问什么,它与 d3 没有任何关系,而只是 JavaScript。真正的问题是:

How can I make this a reusable function that doesn't re-run the bulk of the calculations without resorting to global variables?

答案是闭包:

function pixelLength(this_topojson, this_projection) {

    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;

    return function(miles){
      var pixel_distance = pixels_per_mile * miles;
      return pixel_distance;
    }
  }

现在 pixelLength 将 return 一个函数,可用于计算相同的 topojson 和投影的像素距离一遍又一遍:

var pixelCalc = pixelLength(topojson.feature(data, data.objects['parishes']), projection4);

pixelCalc(1); // pixels for 1 mile
pixelCalc(100); // pixels for 100 miles

这是实际操作:

<!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 projection4 = d3.geo.albers()
    .center([0, 31.2])
    .rotate([91.6, 0])  // Rotate CCW (looking down onto North Pole)
    .parallels([29, 33])
    .translate([width / 2, height / 2])
    .scale(6000);

  var map_path4 = d3.geo.path().pointRadius(2).projection(projection4);

  queue()
    .defer(d3.json, "https://jsonblob.com/api/e98bb5d0-df6f-11e6-90ab-c5f0718ee343")
    .await(ready);

  function pixelLength(this_topojson, this_projection) {

    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;
    
    return function(miles){
      var pixel_distance = pixels_per_mile * miles;
      return pixel_distance;
    }
  }

  function ready(error, data) {
    if (error) throw error;

    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");

    // Scale4
     
    var pixelCalc = pixelLength(topojson.feature(data, data.objects['parishes']), projection4);

    var distance_scale4 = svg.selectAll("#distance-scale4")
        .data(d3.range(1, 100, 5))
      .enter().append("g")
        .attr("class", "distance-scale")
        .attr("id", "distance-scale4")
        .attr("transform", function(d,i){
          return "translate(20, " + (i * 20 + 40) + ")"
        })
        .attr("width", function(d) { return d; });

    distance_scale4.append('text')
      .attr("text-anchor", "start")
      .text(function(d){
        return d + " miles";
      });

    distance_scale4.append('path')
      .attr("class", "distance-scale-line")
      .attr("d", function(d, i) {
        var p = pixelCalc(d);
        var lineData = [
          {"x": 0, "y": 0},
          {"x": p, "y": 0}
        ];

        return line(lineData);
      });
  }

</script>
</body>
</html>

编辑

等等你要的只是点与点之间的距离?啊,伙计,我误会了。它应该只是:

// since earth is sphere
var radians = d3.geo.distance([p1.Longitude, p1.Latitude], [p2.Longitude, p2.Latitude]);
var numberMiles = radians * 3959; // radius of earth