【问题标题】:d3 GeoJSON geoCircle ellipse equivalentd3 GeoJSON geoCircle 椭圆等效
【发布时间】:2018-05-30 00:47:19
【问题描述】:

标题几乎说明了一切。我正在寻找一种方便的方法来生成一个 geoJSON 多边形,该多边形定义一个类似于 d3-geo 的d3.geoCircle()(); 的椭圆我想将此 GeoJSON 椭圆与 d3-geo 一起使用。举个例子,Cesium 有这个capability,它有一个简单的函数,允许你像这样创建一个椭圆:

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 多边形的最佳方法是什么?

【问题讨论】:

    标签: javascript d3.js geojson ellipse cesium


    【解决方案1】:

    D3 没有提供任何真正有用的东西。 Vanilla javascript 可以很容易地实现这一点。首先让我们在笛卡尔坐标空间中创建一个 geojson 椭圆。之后,我们就可以使用半正弦公式来绘制椭圆了。

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

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

    因此,我们可以轻松地遍历一系列角度,计算该角度的半径,然后将此极坐标转换为笛卡尔坐标。也许是这样的:

    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:中心(以像素为单位),旋转:以度为单位的旋转

    这是一个快速的sn-p:

    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] };
    	
    }
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"&gt;&lt;/script&gt;
    1. 应用半正弦公式。

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

    所以,我们可以不计算笛卡尔坐标,而是取极坐标,在harsine公式中以角度为方位,以半径为距离,这应该是比较简单的。

    这可能看起来像:

    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");
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"&gt;&lt;/script&gt;

    注意:a/b 轴以米为单位,x、y、旋转以度为单位

    这是一个相当无聊的演示,也许this simple demonstration 更好:

    我使用的公式假设地球是一个球体,而不是一个椭球体,这可能导致高达 0.3% 的距离误差。但是,根据地图比例,这通常会小于笔划宽度。

    我可能不得不尝试用这个来制作一个特别具有视觉挑战性的天梭指针版本

    片段使用与 IE 不兼容的默认参数值,示例块提供 IE 支持

    【讨论】:

    • 很好的答案。只是一个问题:为什么不使用默认参数:function createEllipse(a, b, x=0, y=0, rotation=0)?你不喜欢吗?
    • 这只是让代码更短/更简洁的一个小窍门。默认参数适用于除 Internet Explorer 之外的所有浏览器...让我改述一下:它适用于所有浏览器。
    • @AndrewReid 我从*Creating D3 map of ellipse envelope data * 来到这里只是为了意识到这个答案设法收集了 5 个赞成票,尽管它包含 2 个严重的缺陷。诚然,其中一个赞成票是我自己的;-) 现在的答案完全错过了旋转部分:所有椭圆的轴都与刻度线对齐。这有两个原因: 1. 在布置解决方案的过程中,您错过了将createEllipse() 中的旋转角度从度数转换为弧度的过程。最后一个 sn-p 放弃了这个计算,但这并不重要,因为那个例子......
    • ...不使用旋转。但是,您也没有在您的 Block 中重新引入它。 2. 在对椭圆的形状进行采样时使用旋转,如下所示:var angle = Math.PI*2 / k * i + rotation 这有点毫无意义。另一方面,在从笛卡尔坐标转换为球坐标时,您没有考虑旋转。将其从前一条语句中删除,然后将其移至 getLatLong([x, y], angle + rotation, r) 即可。
    • 查看更新后的 Block 以查看显示椭圆旋转 45 度的工作演示:blockbuilder.org/altocumulus/1da316e223d85d85dc0e771a891ea605
    猜你喜欢
    • 2015-08-06
    • 1970-01-01
    • 2018-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多