d3地图上的线未形成曲线

Lon*_*ger 5 javascript d3.js d3.geo

我已经使用d3.js创建了地图。我想在两个位置之间显示一条曲线。我能够显示一条线,但是有时它不能形成一条完美的曲线。对于某些线,这些线在地图后面(穿过子午线)弯曲到目的地。

这是演示问题的代码笔:https : //codepen.io/peeyush-pant/pen/WqbPax

还有一张图片:

在此处输入图片说明

这是我的投影数据:

var projection = d3.geoEquirectangular();

var path = d3.geoPath()
  .projection(projection);

Run Code Online (Sandbox Code Playgroud)

这是我的界线:

  arcGroup.selectAll("myPath")
    .data(links)
    .enter()
    .append("path")
    .attr("class", "line")
    .attr("id", function (d, i) {
      return "line" + i;
    })
    .attr("d", function (d) {
      return path(d)
    })
    .style("fill", "none")
    .style("stroke", '#fff787')
    .style("stroke-width", 1.5);
Run Code Online (Sandbox Code Playgroud)

谢谢。

And*_*eid 6

D3 geoPath 可用于创建遵循更大圆距离的路径:它们不是为了风格而弯曲,而是根据投影根据需要弯曲,以表示地球上连接两点的最短路径。D3 geoPath 会动态重新采样以实现此目的。

这种行为在网络地理地图库中并不常见,其中大多数将纬度和经度视为笛卡尔数据而不是三维数据:其中纬度和经度是球体上的点。将数据视为笛卡尔坐标系时,连接两点时的线是直线。在 d3 中,这可以通过诸如此类的方法来完成。

如果您希望所有线段都有一致的曲线,我们会将数据视为笛卡尔坐标并插值曲线。由于我们不会d3.geoPath为此使用,因此无需将目的地和源转换为 geojson LineString,我们可以直接使用这些点。

为此,我们可以使用曲线插值器,但如果不在结束和开始目的地之间添加控制点,默认插值器将无法工作。相反,让我们尝试自定义曲线 -有关自定义曲线的更多信息,请参阅这些答案( ab )。

我们的自定义曲线可以在第一个点之后的任何点找到它和它之前的点之间的中点,并偏移一个点以创建一个控制点,在前一个点和当前点之间形成三角形,然后我们只需绘制一条二次曲线它们之间:

var curve = function(context) {
  var custom = d3.curveLinear(context);
  custom._context = context;
  custom.point = function(x,y) {
    x = +x, y = +y;
    switch (this._point) {
      case 0: this._point = 1; 
        this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
        this.x0 = x; this.y0 = y;        
        break;
      case 1: this._point = 2;
      default: 
        var x1 = this.x0 * 0.5 + x * 0.5;
        var y1 = this.y0 * 0.5 + y * 0.5;
        var m = 1/(y1 - y)/(x1 - x);
        var r = -100; // offset of mid point.
        var k = r / Math.sqrt(1 + (m*m) );
        if (m == Infinity) {
          y1 += r;
        }
        else {
          y1 += k;
          x1 += m*k;
        }     
        this._context.quadraticCurveTo(x1,y1,x,y); 
        this.x0 = x; this.y0 = y;        
        break;
    }
  }
  return custom;
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我们可以简单地用类似的东西画线:

d3.line()
 .curve(curve)
 .x(function(d) { return d.lon; })
 .y(function(d) { return d.lat; })
Run Code Online (Sandbox Code Playgroud)

如下所示:

var curve = function(context) {
  var custom = d3.curveLinear(context);
  custom._context = context;
  custom.point = function(x,y) {
    x = +x, y = +y;
    switch (this._point) {
      case 0: this._point = 1; 
        this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
        this.x0 = x; this.y0 = y;        
        break;
      case 1: this._point = 2;
      default: 
        var x1 = this.x0 * 0.5 + x * 0.5;
        var y1 = this.y0 * 0.5 + y * 0.5;
        var m = 1/(y1 - y)/(x1 - x);
        var r = -100; // offset of mid point.
        var k = r / Math.sqrt(1 + (m*m) );
        if (m == Infinity) {
          y1 += r;
        }
        else {
          y1 += k;
          x1 += m*k;
        }     
        this._context.quadraticCurveTo(x1,y1,x,y); 
        this.x0 = x; this.y0 = y;        
        break;
    }
  }
  return custom;
}
Run Code Online (Sandbox Code Playgroud)
d3.line()
 .curve(curve)
 .x(function(d) { return d.lon; })
 .y(function(d) { return d.lat; })
Run Code Online (Sandbox Code Playgroud)
let data = [{
        "source": {
            "lat": 40.712776,
            "lon": -74.005974    
        },
        "destination": {
            "lat": 21.05,
            "lon": 105.55
        }
    },
             {
        "source": {
            "lat": 40.712776,
            "lon": -74.005974    
        },
        "destination": {
            "lat": -35.15,
            "lon": 149.08
        }
    }]

var curve = function(context) {
  var custom = d3.curveLinear(context);
  custom._context = context;
  custom.point = function(x,y) {
    x = +x, y = +y;
    switch (this._point) {
      case 0: this._point = 1; 
        this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
        this.x0 = x; this.y0 = y;        
        break;
      case 1: this._point = 2;
      default: 
        var x1 = this.x0 * 0.5 + x * 0.5;
        var y1 = this.y0 * 0.5 + y * 0.5;
        var m = 1/(y1 - y)/(x1 - x);
        var r = -100; // offset of mid point.
        var k = r / Math.sqrt(1 + (m*m) );
        if (m == Infinity) {
          y1 += r;
        }
        else {
          y1 += k;
          x1 += m*k;
        }     
        this._context.quadraticCurveTo(x1,y1,x,y); 
        this.x0 = x; this.y0 = y;        
        break;
    }
  }
  return custom;
}

var projection = d3.geoEquirectangular().translate([250,150]).scale(500/Math.PI/2);
var path = d3.geoPath(projection);

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300);
  
d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(world) {
 
  var worldOutline = svg.append("path")
    .datum(topojson.mesh(world))
    .attr("d", path );
    
  var line = d3.line()
    .x(function(d) {
      return projection([d.lon,d.lat])[0];
    })
    .y(function(d) {
      return projection([d.lon,d.lat])[1];
    })
    .curve(curve);
    
  svg.selectAll(null)
    .data(data)
    .enter()
    .append("path")
    .datum(function(d) {
      return [d.source,d.destination]; // d3.line expects an array where each item represnts a vertex.
    })
    .attr("d",line)
    .style("stroke","black")
    .style("stroke-width",1.5);

});
Run Code Online (Sandbox Code Playgroud)

下面,只是为了好玩,我比较了使用 的直线d3.line、使用自定义曲线插值器的曲线d3.line以及d3.geoPath带有一些动画的普通旧线:

path {
  fill: none;
  stroke: #ccc;
  stroke-width: 1px;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
Run Code Online (Sandbox Code Playgroud)
 let data = [{
        "source": {
            "lat": 40.712776,
            "lon": -74.005974    
        },
        "destination": {
            "lat": 21.05,
            "lon": 105.55
        }
    },
             {
        "source": {
            "lat": 40.712776,
            "lon": -74.005974    
        },
        "destination": {
            "lat": -35.15,
            "lon": 149.08
        }
    }]

var curve = function(context) {
  var custom = d3.curveLinear(context);
  custom._context = context;
  custom.point = function(x,y) {
    x = +x, y = +y;
    switch (this._point) {
      case 0: this._point = 1; 
        this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y);
        this.x0 = x; this.y0 = y;        
        break;
      case 1: this._point = 2;
      default: 
        var x1 = this.x0 * 0.5 + x * 0.5;
        var y1 = this.y0 * 0.5 + y * 0.5;
        var m = 1/(y1 - y)/(x1 - x);
        var r = -100; // offset of mid point.
        var k = r / Math.sqrt(1 + (m*m) );
        if (m == Infinity) {
          y1 += r;
        }
        else {
          y1 += k;
          x1 += m*k;
        }     
        this._context.quadraticCurveTo(x1,y1,x,y); 
        this.x0 = x; this.y0 = y;        
        break;
    }
  }
  return custom;
}

var projection = d3.geoEquirectangular().translate([250,150]).scale(500/Math.PI/2);
var path = d3.geoPath(projection);

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300);
  
d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then(function(world) {
 
  var worldOutline = svg.append("path")
    .datum(topojson.mesh(world))
    .attr("d", path );
    
  var line = d3.line()
    .x(function(d) {
      return projection([d.lon,d.lat])[0];
    })
    .y(function(d) {
      return projection([d.lon,d.lat])[1];
    })
    .curve(curve);
    
  var fauxArcPaths = svg.selectAll(null)
    .data(data)
    .enter()
    .append("path")
    .datum(function(d) {
      return [d.source,d.destination];
    })
    .attr("d",line)
    .style("stroke","black")
    .style("stroke-width",1.5);
    
  var greatCirclePaths = svg.selectAll(null)
    .data(data)
    .enter()
    .append("path")
    .datum(function(d) {
      return {type:"LineString",coordinates:
        [[d.source.lon,d.source.lat],[d.destination.lon,d.destination.lat]] }
    })
    .attr("d",path)
    .style("stroke","steelblue")
    .style("stroke-width",1.5);
    
  var straightline = d3.line()
    .x(function(d) {
      return projection([d.lon,d.lat])[0];
    })
    .y(function(d) {
      return projection([d.lon,d.lat])[1];
    });
    
  var straightPaths = svg.selectAll(null)
    .data(data)
    .enter()
    .append("path")
    .datum(function(d) {
      return [d.source,d.destination];
    })
    .attr("d",straightline)
    .style("stroke-width",1.5)
    .style("stroke","orange");
   
  // animate:
  d3.interval(function(elapsed) {
      projection.rotate([ -elapsed / 150, elapsed/300 ]);
      straightPaths.attr("d",straightline);
      greatCirclePaths.attr("d",path);
      fauxArcPaths.attr("d",line);
      worldOutline.attr("d",path);
  }, 50);
        
    
  
});
Run Code Online (Sandbox Code Playgroud)