d3 GeoJSON中的geoCircle椭圆等效。

4

标题已经说明了一切。我正在寻找一种方便的方法来生成一个类似于d3-geo d3.geoCircle()() 的椭圆形 geoJSON 多边形。我想将这个 GeoJSON 椭圆形与 d3-geo 一起使用。为了举例说明,Cesium 有这种功能,可以通过一个简单的函数创建一个椭圆形,如下所示:

var ellipse = new Cesium.EllipseGeometry({
  center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
  semiMajorAxis : 500000.0,
  semiMinorAxis : 300000.0,
  rotation : Cesium.Math.toRadians(60.0)
});

如果这个函数返回GeoJSON,我就可以了。如何生成一个定义椭圆的GeoJSON多边形?
1个回答

7

D3 在这方面并没有提供太多帮助。使用原生的 JavaScript 就可以相对容易地实现。首先,让我们在笛卡尔坐标空间中创建一个 GeoJSON 椭圆。接下来,我们可以使用 Haversine 公式来画出椭圆。

  1. 在笛卡尔坐标空间中创建一个 GeoJSON 椭圆。

这非常简单,我使用的方法是计算给定角度处椭圆的半径。使用这些极坐标,我们可以拼接成一个椭圆。可以很容易地找到椭圆在给定点的半径公式,我使用了这个source,它给出了:

enter image description here

因此,我们可以轻松地迭代一系列角度,计算该角度处的半径,然后将极坐标转换为笛卡尔坐标。可能是这样的:
function createEllipse(a,b,x=0,y=0,rotation=0) {

  rotation = rotation / 180 * Math.PI;
  var n = n = Math.ceil(36 * (Math.max(a/b,b/a))); // n sampling angles, more for more elongated ellipses
  var coords = [];

  for (var i = 0; i <= n; i++) {
    // get the current angle
    var θ = Math.PI*2/n*i + rotation;

    // get the radius at that angle
    var r = a * b / Math.sqrt(a*a*Math.sin(θ)*Math.sin(θ) + b*b*Math.cos(θ)*Math.cos(θ));

    // get the x,y coordinate that marks the ellipse at this angle
    x1 = x + Math.cos(θ-rotation) * r;
    y1 = y + Math.sin(θ-rotation) * r;

    coords.push([x1,y1]);
  }

  // return a geojson object:
  return { "type":"Polygon", "coordinates":[coords] };

}

注意:a/b表示轴(以像素为单位),x/y表示中心点(以像素为单位),rotation表示旋转角度(以度数为单位)

这是一个简短的代码片段:

var geojson = createEllipse(250,50,200,200,45);

var svg = d3.select("body")
  .append("svg")
  .attr("width",600)
  .attr("height",500);
  
var path = d3.geoPath();

svg.append("path")
 .datum(geojson)
 .attr("d",path);


function createEllipse(a,b,x=0,y=0,rotation=0) {

 rotation = rotation / 180 * Math.PI;
 var n = n = Math.ceil(36 * (Math.max(a/b,b/a))); // n sample angles
 var coords = [];
 
 for (var i = 0; i <= n; i++) {
     // get the current angle
  var θ = Math.PI*2/n*i + rotation;
  
  // get the radius at that angle
  var r = a * b / Math.sqrt(a*a*Math.sin(θ)*Math.sin(θ) + b*b*Math.cos(θ)*Math.cos(θ));
  
  // get the x,y coordinate that marks the ellipse at this angle
  x1 = x + Math.cos(θ-rotation) * r;
  y1 = y + Math.sin(θ-rotation) * r;

  coords.push([x1,y1]);
 }
 
 // return a geojson object:
 return { "type":"Polygon", "coordinates":[coords] };
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

  1. 应用哈弗曼公式。

我知道的关于哈弗曼和相关函数的最佳资源之一是在Moveable Type Scripts上。我几年前得到的公式来自那里,并进行了一些美化修改。我不会在这里分解公式,因为链接的参考资料应该很有用。

因此,我们可以采用极坐标而非计算笛卡尔坐标,并将角度作为方位角、半径作为距离,在哈弗曼公式中使用,这应该相对简单。

这可能看起来像:

function createEllipse(a,b,x=0,y=0,rotation=0) {
 
 var k = Math.ceil(36 * (Math.max(a/b,b/a))); // sample angles
 var coords = [];
 
 for (var i = 0; i <= k; i++) {
 
  // get the current angle
  var angle = Math.PI*2 / k * i + rotation
  
  // get the radius at that angle
  var r = a * b / Math.sqrt(a*a*Math.sin(angle)*Math.sin(angle) + b*b*Math.cos(angle)*Math.cos(angle));

  coords.push(getLatLong([x,y],angle,r));
 }
 return { "type":"Polygon", "coordinates":[coords] };
}
 
function getLatLong(center,angle,radius) {
 
 var rEarth = 6371000; // meters
 
 x0 = center[0] * Math.PI / 180; // convert to radians.
 y0 = center[1] * Math.PI / 180;
 
 var y1 = Math.asin( Math.sin(y0)*Math.cos(radius/rEarth) + Math.cos(y0)*Math.sin(radius/rEarth)*Math.cos(angle) );
 var x1 = x0 + Math.atan2(Math.sin(angle)*Math.sin(radius/rEarth)*Math.cos(y0), Math.cos(radius/rEarth)-Math.sin(y0)*Math.sin(y1));
 
 y1 = y1 * 180 / Math.PI;
 x1 = x1 * 180 / Math.PI;
   
 return [x1,y1];
} 

// Create & Render the geojson:
var geojson = createEllipse(500000,1000000,50,70); // a,b in meters, x,y, rotation in degrees.
var geojson2 = createEllipse(500000,1000000)

var svg = d3.select("body")
  .append("svg")
  .attr("width",600)
  .attr("height",400);
  
var g = svg.append("g");

var projection = d3.geoMercator().translate([300,200]).scale(600/Math.PI/2);

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

g.selectAll("path")
 .data([geojson,geojson2])
 .enter().append("path")
 .attr("d", path);
 
g.selectAll("circle")
  .data([[50,70],[0,0]])
  .enter().append("circle")
  .attr("cx", function(d) { return projection(d)[0] })
  .attr("cy", function(d) { return projection(d)[1] })
  .attr("r", 4)
  .attr("fill","orange");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

注意:a/b轴的单位为米,x、y、旋转角度的单位为度。
这是一个相当无聊的演示,也许这个简单的演示更好:

enter image description here

我使用的公式假设地球是一个球体,而不是一个椭球体,这可能导致距离误差高达0.3%。但是,根据地图比例尺,这通常会小于笔画宽度。
我可能需要尝试制作一个特别具有视觉挑战性的Tissot指数版本。
片段使用默认参数值,这些值与IE不兼容。示例块提供IE支持。

2
这只是一个让代码更短、更简洁的小技巧。默认参数适用于所有浏览器,除了Internet Explorer...让我重新表述一下:它适用于所有浏览器。 - Gerardo Furtado
1
@AndrewReid 我从Creating D3 map of ellipse envelope data来到这里,只是意识到这个答案成功地获得了5个赞,尽管它包含两个严重的缺陷。不可否认,其中一个赞是我自己的;-) 现在的答案完全忽略了旋转部分:所有椭圆的轴都与经纬网对齐。这有两个原因:1. 在解决方案的过程中,您错过了将旋转角度从度转换为弧度的步骤。最后一段代码省略了该计算,但这并不重要,因为那个例子... - altocumulus
1
...没有使用旋转。然而,您在块中也没有重新引入它。2. 当采样椭圆形状时,您使用旋转方式如下:var angle = Math.PI*2 / k * i + rotation,这有点无意义。另一方面,在从笛卡尔坐标系转换为球面坐标系时,您没有考虑旋转。将其从前面的语句中删除,而是将其移动到 getLatLong([x, y], angle + rotation, r) 中即可解决问题。 - altocumulus
1
请查看更新的代码块,以查看旋转45度的椭圆的工作演示:https://blockbuilder.org/altocumulus/1da316e223d85d85dc0e771a891ea605 - altocumulus
1
@altocumulus,我希望很快就能看一下这个...目前正在为一个重要项目的周五截止日期工作 - 等完成之后我会尝试搞清楚我在写这段话时到底是在说什么... - Andrew Reid
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接