d3 GeoJSON geoCircle椭圆等效 [英] d3 GeoJSON geoCircle ellipse equivalent

查看:111
本文介绍了d3 GeoJSON geoCircle椭圆等效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

标题几乎说明了一切.我正在寻找一种生成geoJSON多边形的便捷方法,该多边形定义类似于d3-geo的d3.geoCircle()();的椭圆.我想将此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多边形的最佳方法是什么?

解决方案

D3在此方面没有提供任何真正的帮助. Vanilla javascript可以很容易地实现这一目标.首先,让我们在笛卡尔坐标空间中创建一个geojson椭圆.之后,我们可以使用Haversine公式绘制椭圆.

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

这非常简单,我使用的方法是计算给定角度下的椭圆半径.使用这些极坐标,我们可以将椭圆缝合在一起.可以很容易地找到给定点处的椭圆半径的公式,我使用了,它为我们提供了:

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

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

这是一个简短的摘要:

 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. 应用Haversine公式.

我所知道的有关hasversine及其相关功能的最佳资源之一是活动类型脚本.我的配方几年前就来自那里,并且进行了一些化妆品修饰.我不会在这里分解公式,因为链接的引用应该很有用.

因此,除了计算笛卡尔坐标外,我们还可以采用极坐标,并在Haversine公式中将角度用作方位角并将半径用作距离,这应该是相对微不足道的.

这看起来像:

 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,旋转度为度

那是一个非常无聊的演示,也许这个简单的演示更好:

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

我可能要尝试用这种方法制作一个特别具有视觉挑战性的天梭式的版本.

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

The title pretty much says it all. I'm looking for a convenient way to generate a geoJSON polygon defining an ellipse similar to d3-geo's d3.geoCircle()(); I want to use this GeoJSON ellipse with d3-geo. To clarify with and example, Cesium has this capability with a simple function allowing you to create an ellipse like so:

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

If that function returned GeoJSON I'd be set. What's the best way to generate a GeoJSON polygon defining an ellipse?

解决方案

D3 doesn't offer anything that can really help here. Vanilla javascript can achieve this fairly easily. First let's create a geojson ellipse in Cartesian coordinate space. After, we can use the haversine formula to draw the ellipse.

  1. Create a geojson ellipse in Cartesian coordinate space.

This is pretty straightforward, the method I'm using is to calculate the radius of the ellipse at a given angle. Using these polar coordinates we can stitch together an ellipse. The formula for the radius of an ellipse at a given point can be found pretty easily, I used this source, which gives us:

So, we can easily iterate through a series of angles, calculate the radius at that angle, and then translate this polar coordinate into a Cartesian coordinate. Perhaps something like:

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] };

}

Note: a/b: axes (in pixels), x/y: center (in pixels), rotation: rotation in degrees

Here's that in a quick snippet:

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. Apply the haversine formula.

One of the best resources on the haversine and related functions I know of is at Moveable Type Scripts. The formula I have came from there a few years back and has had a few cosmetic modifications. I'm not going to break down the formula here, as the linked reference should be useful.

So, rather than calculating the Cartesian coordinates, we can take the polar coordinate and use the angle as bearing and the radius as distance in the haversine formula, which should be relatively trivial.

This could look like:

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>

Note: a/b axes in meters, x,y,rotation in degrees

That's a pretty boring demonstration, perhaps this simple demonstration is better:

The formula I'm using assumes a earth is a sphere, not an ellipsoid, this can lead to errors in distance of up to 0.3%. However, depending on map scale, this will often be less than the stroke width.

I might have to try and make a particularly visually challenging version of a tissot's indicatrix with this

Snippets use default parameter values that are not compatible with IE, example block offers IE support

这篇关于d3 GeoJSON geoCircle椭圆等效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆