Javascript - 如何计算线串的中点?
Javascript - how to calculate midpoint of linestring?
使用给定的 GeoJSON 线串:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
2.6806640625,
46.437856895024204
],
[
4.7021484375,
50.20503326494332
],
[
13.271484375,
47.010225655683485
],
[
17.2265625,
52.855864177853974
]
]
}
}
]
}
我想找到准确的中点。我不是指给定数组的中间元素,而是计算一个恰好位于线中间的中点。因此,如果一条线的总长度为 30 公里,则中点将位于 15 公里处。
我尝试在 npm 中搜索类似的东西,但找不到。唯一接近的图书馆能够将 n 个点放在线上,然后我可以获得中间的一个。但这对我来说很糟糕,因为精度不是那么好。
完美的选择是在 JavaScript 中实施此 http://postgis.net/docs/manual-1.5/ST_Line_Interpolate_Point.html。
如何实现?
Here's a working CodePen
以下代码将 return 指定距离的点。
对于这种特定情况,中点距离是总长度 / 2
像这样调用主函数,假设我们的线串数组存储在points
var totalLength = totalLen(points);
var midDistance = totalLength / 2;
var midPoint = getPointByDistance(points, midDistance)
Javascript
myData = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
2.6806640625,
46.437856895024204
],
[
4.7021484375,
50.20503326494332
],
[
13.271484375,
47.010225655683485
],
[
17.2265625,
52.855864177853974
]
]
}
}]
}
var points = myData.features[0].geometry.coordinates
var totalLength = totalLen(points);
var midDistance = totalLength / 2;
var midPoint = getPointByDistance(points, midDistance)
alert ("midPoint = " + midPoint[0] + ", " + midPoint[1])
// main function
function getPointByDistance(pnts, distance) {
var tl = totalLen(pnts);
var cl = 0;
var ol;
var result;
pnts.forEach(function(point, i, points) {
ol = cl;
cl += i ? lineLen([points[i-1], point]) : 0;
if (distance <= cl && distance > ol){
var dd = distance - ol;
result = pntOnLine([points[i-1], point], dd);
}
});
return result
};
// returns a point on a single line (two points) using distance // line=[[x0,y0],[x1,y1]]
function pntOnLine(line, distance) {
t = distance / lineLen(line)
xt = (1 - t) * line[0][0] + (t * line[1][0])
yt = (1 - t) * line[0][1] + (t * line[1][1])
return [xt, yt]
};
// returns the total length of a linestring (multiple points) // pnts=[[x0,y0],[x1,y1],[x2,y2],...]
function totalLen(pnts) {
var tl = 0;
pnts.forEach(function(point, i, points) {
tl += i ? lineLen([points[i - 1], point]) : 0;
});
return tl;
};
// returns the length of a line (two points) // line=[[x0,y0],[x1,y1]]
function lineLen(line) {
var xd = line[0][0] - line[1][0];
var yd = line[0][1] - line[1][1];
return Math.sqrt(xd * xd + yd * yd);
};
说明
1.我们找到线串的总长度:
- 函数
lineLen()
计算单行的长度。
- 函数
totalLen()
遍历所有行并计算总长度。
*来源和学分 here (Igor Šarčević)
2。中点距离为全长/2:
- 现在我们有了想要的距离,我们遍历线并检查 this line-startpoint 和 this line-endpoint 的距离和查看中点距离是否在它们之间。
一旦我们知道中点所在的单线,我们就会计算它与此 线起点 的距离(总长度 - 线起点距离)
现在我们用this formula(函数pntOnLine()
)求xt
,yt
(想要的中点)
*感谢 Sen Jacob.
可视化
我包含了一个 HTML5 canvas
来绘制(可视化)线串和中点,您不必包含它们。但如果您想测试代码并实时查看结果,它们会很有用
// Just for visualizing on canvas (not needed)
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
drawLine(points);
//
ctx.beginPath();
ctx.arc(midPoint[0], midPoint[1], 2, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = "red";
ctx.fill();
//
function drawLine(pnts) {
pnts.forEach(function(point, i, points) {
if (i === 0) {
ctx.beginPath();
ctx.moveTo(point[0], point[1]);
} else {
ctx.lineTo(point[0], point[1]);
}
if (i === points.length - 1) {
ctx.stroke();
}
});
}
所以我假设您有一个坐标列表,它描述了球体上带有 waypoints 的线。每个坐标都有纬度和经度。
此实现会遍历线条,直到达到距离的 50%:
var coordinates = [
[
2.6806640625,
46.437856895024204
],
[
4.7021484375,
50.20503326494332
],
[
13.271484375,
47.010225655683485
],
[
17.2265625,
52.855864177853974
]
];
// From
function calcCrow(lat1, lon1, lat2, lon2)
{
var R = 6371; // km
var dLat = toRad(lat2-lat1);
var dLon = toRad(lon2-lon1);
var lat1 = toRad(lat1);
var lat2 = toRad(lat2);
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
return d;
}
// Converts numeric degrees to radians
// From
function toRad(Value)
{
return Value * Math.PI / 180;
}
// Returns a point from a line
// Should use halversine but i'm too bad at math
function getPoint(factor, lat1, lon1, lat2, lon2)
{
while (lat1 < 0) {lat1 += 360}
while (lat2 < lat1) {lat2 += 360}
latPoint = lat1 + factor * (lat2 - lat1)
latPoint = ((latPoint + 180) % 360) - 180
otherLat = latPoint < 0 ? latPoint + 180 : latPoint - 180
latPoint = Math.abs(latPoint) < Math.abs(otherLat) ? latPoint : otherLat
lonPoint = lon1 + factor * (lon2 - lon1)
return [latPoint, lonPoint]
}
function getHalfDistance(coordinates)
{
// Calculate complete distance
var totalDistance = 0;
for (var i = 1; i < coordinates.length; i++) {
totalDistance += calcCrow(coordinates[i-1][0], coordinates[i-1][1], coordinates[i][0], coordinates[i][1])
}
// Find the 50%
var target = 0.5
var currentDistance = 0
for (var i = 1; i < coordinates.length; i++) {
var thisDistance = calcCrow(coordinates[i-1][0], coordinates[i-1][1], coordinates[i][0], coordinates[i][1]);
if (target * totalDistance < currentDistance + thisDistance) {
var midDistance = target * totalDistance - currentDistance;
var factor = midDistance / thisDistance;
return getPoint(factor, coordinates[i-1][0], coordinates[i-1][1], coordinates[i][0], coordinates[i][1]);
}
currentDistance += thisDistance
}
}
console.log(getHalfDistance(coordinates))
使用给定的 GeoJSON 线串:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
2.6806640625,
46.437856895024204
],
[
4.7021484375,
50.20503326494332
],
[
13.271484375,
47.010225655683485
],
[
17.2265625,
52.855864177853974
]
]
}
}
]
}
我想找到准确的中点。我不是指给定数组的中间元素,而是计算一个恰好位于线中间的中点。因此,如果一条线的总长度为 30 公里,则中点将位于 15 公里处。
我尝试在 npm 中搜索类似的东西,但找不到。唯一接近的图书馆能够将 n 个点放在线上,然后我可以获得中间的一个。但这对我来说很糟糕,因为精度不是那么好。
完美的选择是在 JavaScript 中实施此 http://postgis.net/docs/manual-1.5/ST_Line_Interpolate_Point.html。
如何实现?
Here's a working CodePen
以下代码将 return 指定距离的点。
对于这种特定情况,中点距离是总长度 / 2
像这样调用主函数,假设我们的线串数组存储在points
var totalLength = totalLen(points);
var midDistance = totalLength / 2;
var midPoint = getPointByDistance(points, midDistance)
Javascript
myData = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[
2.6806640625,
46.437856895024204
],
[
4.7021484375,
50.20503326494332
],
[
13.271484375,
47.010225655683485
],
[
17.2265625,
52.855864177853974
]
]
}
}]
}
var points = myData.features[0].geometry.coordinates
var totalLength = totalLen(points);
var midDistance = totalLength / 2;
var midPoint = getPointByDistance(points, midDistance)
alert ("midPoint = " + midPoint[0] + ", " + midPoint[1])
// main function
function getPointByDistance(pnts, distance) {
var tl = totalLen(pnts);
var cl = 0;
var ol;
var result;
pnts.forEach(function(point, i, points) {
ol = cl;
cl += i ? lineLen([points[i-1], point]) : 0;
if (distance <= cl && distance > ol){
var dd = distance - ol;
result = pntOnLine([points[i-1], point], dd);
}
});
return result
};
// returns a point on a single line (two points) using distance // line=[[x0,y0],[x1,y1]]
function pntOnLine(line, distance) {
t = distance / lineLen(line)
xt = (1 - t) * line[0][0] + (t * line[1][0])
yt = (1 - t) * line[0][1] + (t * line[1][1])
return [xt, yt]
};
// returns the total length of a linestring (multiple points) // pnts=[[x0,y0],[x1,y1],[x2,y2],...]
function totalLen(pnts) {
var tl = 0;
pnts.forEach(function(point, i, points) {
tl += i ? lineLen([points[i - 1], point]) : 0;
});
return tl;
};
// returns the length of a line (two points) // line=[[x0,y0],[x1,y1]]
function lineLen(line) {
var xd = line[0][0] - line[1][0];
var yd = line[0][1] - line[1][1];
return Math.sqrt(xd * xd + yd * yd);
};
说明
1.我们找到线串的总长度:
- 函数
lineLen()
计算单行的长度。 - 函数
totalLen()
遍历所有行并计算总长度。
*来源和学分 here (Igor Šarčević)
2。中点距离为全长/2:
- 现在我们有了想要的距离,我们遍历线并检查 this line-startpoint 和 this line-endpoint 的距离和查看中点距离是否在它们之间。
一旦我们知道中点所在的单线,我们就会计算它与此 线起点 的距离(总长度 - 线起点距离)
现在我们用this formula(函数
pntOnLine()
)求xt
,yt
(想要的中点)
*感谢 Sen Jacob.
可视化
我包含了一个 HTML5 canvas
来绘制(可视化)线串和中点,您不必包含它们。但如果您想测试代码并实时查看结果,它们会很有用
// Just for visualizing on canvas (not needed)
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
drawLine(points);
//
ctx.beginPath();
ctx.arc(midPoint[0], midPoint[1], 2, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = "red";
ctx.fill();
//
function drawLine(pnts) {
pnts.forEach(function(point, i, points) {
if (i === 0) {
ctx.beginPath();
ctx.moveTo(point[0], point[1]);
} else {
ctx.lineTo(point[0], point[1]);
}
if (i === points.length - 1) {
ctx.stroke();
}
});
}
所以我假设您有一个坐标列表,它描述了球体上带有 waypoints 的线。每个坐标都有纬度和经度。 此实现会遍历线条,直到达到距离的 50%:
var coordinates = [
[
2.6806640625,
46.437856895024204
],
[
4.7021484375,
50.20503326494332
],
[
13.271484375,
47.010225655683485
],
[
17.2265625,
52.855864177853974
]
];
// From
function calcCrow(lat1, lon1, lat2, lon2)
{
var R = 6371; // km
var dLat = toRad(lat2-lat1);
var dLon = toRad(lon2-lon1);
var lat1 = toRad(lat1);
var lat2 = toRad(lat2);
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
return d;
}
// Converts numeric degrees to radians
// From
function toRad(Value)
{
return Value * Math.PI / 180;
}
// Returns a point from a line
// Should use halversine but i'm too bad at math
function getPoint(factor, lat1, lon1, lat2, lon2)
{
while (lat1 < 0) {lat1 += 360}
while (lat2 < lat1) {lat2 += 360}
latPoint = lat1 + factor * (lat2 - lat1)
latPoint = ((latPoint + 180) % 360) - 180
otherLat = latPoint < 0 ? latPoint + 180 : latPoint - 180
latPoint = Math.abs(latPoint) < Math.abs(otherLat) ? latPoint : otherLat
lonPoint = lon1 + factor * (lon2 - lon1)
return [latPoint, lonPoint]
}
function getHalfDistance(coordinates)
{
// Calculate complete distance
var totalDistance = 0;
for (var i = 1; i < coordinates.length; i++) {
totalDistance += calcCrow(coordinates[i-1][0], coordinates[i-1][1], coordinates[i][0], coordinates[i][1])
}
// Find the 50%
var target = 0.5
var currentDistance = 0
for (var i = 1; i < coordinates.length; i++) {
var thisDistance = calcCrow(coordinates[i-1][0], coordinates[i-1][1], coordinates[i][0], coordinates[i][1]);
if (target * totalDistance < currentDistance + thisDistance) {
var midDistance = target * totalDistance - currentDistance;
var factor = midDistance / thisDistance;
return getPoint(factor, coordinates[i-1][0], coordinates[i-1][1], coordinates[i][0], coordinates[i][1]);
}
currentDistance += thisDistance
}
}
console.log(getHalfDistance(coordinates))