前言
在canvas绘制箭头的过程中我遇到这样的问题:“我已知线段起点和终点的坐标,想要求线段的方位角和长度”。这实际上就是测量学当中的坐标反算,在这篇博客中我就将简单的介绍一下坐标反算的原理,以及如何使用JS封装一个坐标反算的方法。
1.坐标反算介绍
我们先来看一下坐标反算的概念:
根据直线始点和终点的坐标,计算直线的水平距离和直线的坐标方位角,称为坐标反算。
坐标反算的大致公式如下:
其中公式6.6用于计算线段AB的长度,公式6.7用于计算线段的坐标方位角。
2.计算直线长度
(1)勾股定理计算直线长度
直线长度实际上就是根据勾股定理进行计算的,请看下图:
可以看到ABb构成了一个直角三角形,所以根据勾股定理可得以下的公式 ( 其中L就是直线AB的长度 ):
L2 = ∆x2 + ∆y2
(2)计算坐标增量
下一步就是要求∆x、∆y的值,它们是坐标增量,在坐标正算的时候我们也求过,只不过那时我们使用的是三角函数。而在坐标反算中计算坐标增量就比较简单了,直接使用结束点的坐标减去起始点的坐标。
tip:在计算长度的时候相减的顺序并不重要,可以反过来用起始点减结束点;但是在计算方位角时就不能随意更改顺序了,可能就会导致最终的结果是直线AB的反方位角。
∆x = XB - XA
∆y = YB - YA
(3)封装计算长度的方法
此时我们就可以封装计算直线长度的方法了:
// t-坐标反算-计算直线长度
/**
*
* @param {Array} from 直线起始点坐标
* @param {Array} to 直线结束点坐标
*/
function calcLineLength(from, to) {
return Math.hypot(to[0] - from[0], to[1] - from[1])
}
上面的的方法中我用到了 Math.hypot()
函数,这个函数会返回所有参数的平方根。
3.计算方位角
(1)反算方位角的步骤
我们知道在坐标正算的过程中是用到了坐标方位角的,坐标正算的详情可以参考我的写的这篇博客:canvas绘图学习:坐标正算-CSDN博客
因此我们只需要将上面的过程倒过来就能求得坐标方位角:
第一步,用结束点坐标减去起始点坐标得到坐标增量
第二步,使用坐标增量和反三角函数计算出象限角
第三步,将象限角转换为方位角
(2)计算坐标增量
这里计算坐标增量的公式与上面一致:
∆x = XB - XA
∆y = YB - YA
在之前的坐标正算、计算直线长度 和现在的计算方位角都涉及到坐标增量的计算。但实际上这三个地方的坐标增量都是略有不同的。
坐标正算 | 使用 |
计算直线长度 | 在计算坐标增量时,可以用结束点坐标减起始点坐标;也可以用起始点坐标减结束点坐标。它们计算出来的结果是一致的。通过这种方式计算出来的坐标增量有正负。 |
计算坐标方位角 | 用结束点坐标减起始点坐标最终计算出来的是直线的正方位角(这是我们需要的);用起始点坐标减结束点坐标最终计算出来的是直线的反方位角。通过这种方式计算出来的坐标增量有正负。 例如 : 如果用 B点坐标 - A点坐标 计算出来的是 αAB 如果用 A点坐标 - B点坐标 计算出来的是 αBA |
(3)反三角函数arctan() 计算方位角
之前在坐标正算的时候我们是使用cos(R)
和sin(R)
计算出了 x、y方向上的坐标增量。现在则需要反过来利用两个坐标增量计算出象限角R ,因此这里需要使用反正切函数 arctan()
, 它是tan()
的反函数。例如: tan(45°) = 1 arctan(1) = 45°。
进而可以推导出如下的公式:
tan(R) = ∆y/∆x arctan(∆y/∆x) = R
这个公式用代码表示就是:
// R表示象限角
const R = Math.atan(deltaY / deltaX) * (180 / Math.PI)
在上一节提到过,在坐标反算中计算出来的坐标增量是有正有负的。因此arctan(∆y/∆x)
的取值范围在 π/2 ~ - π/2 之间。下表是几个不同象限的方位角对应的 arctan(∆y/∆x)
的值:
坐标方位角 |
|
30° | 30° |
130° | -50° |
230° | 50° |
330° | -30° |
上面的结果再取一个绝对值就是我们所需的象限角 R = |arctan(∆y/∆x)|
。因此最终的代码如下:
// R表示象限角
const R = Math.abs(Math.atan(deltaY / deltaX) * (180 / Math.PI))
最后在将象限角转换位方位角,转换方式如下:
坐标增量的符号及其所在的象限 | 象限角转方位角 |
∆x > 0 ∆y > 0(第一象限) | α = R |
∆x < 0 ∆y > 0(第二象限) | α = 180° - R |
∆x < 0 ∆y < 0(第三象限) | α = R + 180° |
∆x > 0 ∆y < 0 (第四象限) | α = 360° - R |
(4)反三角函数arctan2() 计算方位角
除了arctan()
外 也可以使用 arctan2()
函数来计算方位角。arctan2(y,x)
所表达的意思是坐标原点为起点,指向(x,y)
的射线在坐标平面上与x轴正方向之间的角的角度。
因此通过arctan2()
函数就可以计算出 起点和终点分别为 (0,0)
和(∆x,∆y)
的直线A2B2 与 x轴正方之间的夹角。而直线A2B2 与 已知的直线AB是平行关系,它们的方位角相同。
arctan2()
计算的结果R2的取值范围在 π ~ - π 之间 ,如果点 (∆x,∆y)
落在三四象限则R2为负,点 (∆x,∆y)
落在一二象限则R2为正。所以arctan2()
所计算出来的角度与方位角的关系如下:
R2的符号以及点 | 与方位角的关系 |
R2 > 0 ( 一、二象限) | α = R2 |
R2 < 0 (三、四象限) | α = R2 + 360° |
最终转换成代码就是:
const R2 = Math.atan2(∆y,∆x) * (180 / Math.PI)
if(R2 > 0) {
α = R2
}else{
α = R2 + 360°
}
(5)封装计算方位角的方法
使用arctan()
函数计算方位角:
// t-坐标反算-计算方位角arctan()
/**
*
* @param {Array} from 直线起始点坐标
* @param {Array} to 直线结束点坐标
*/
function calcLineBearing(from, to) {
const deltaX = to[0] - from[0],
deltaY = to[1] - from[1]
const qAngle = Math.abs((Math.atan(deltaY / deltaX) * 180) / Math.PI)
let bearing
if (deltaX > 0 && deltaY >= 0) {
bearing = qAngle
} else if (deltaX <= 0 && deltaY > 0) {
bearing = 180 - qAngle
} else if (deltaX < 0 && deltaY <= 0) {
bearing = 180 + qAngle
} else if (deltaX >= 0 && deltaY < 0) {
bearing = 360 - qAngle
}
return bearing
}
使用arctan2()
函数计算方位角:
// t-坐标反算-计算方位角arctan2()
/**
*
* @param {Array} from 直线起始点坐标
* @param {Array} to 直线结束点坐标
*/
function calcLineBearing2(from, to) {
const deltaX = to[0] - from[0],
deltaY = to[1] - from[1]
const qAngle2 = (Math.atan2(deltaY , deltaX) * 180) / Math.PI
let bearing
if (qAngle2 > 0) {
bearing = qAngle2
} else {
bearing = qAngle2 + 360
}
return bearing
}
(6)canvas绘图中“方位角”的简便算法
在写这篇文章的过程中我就发现,我所封装的计算方位角的方法太过复杂,这主要是由于 直接用Math.atan()
和Math.atan2()
计算出来的结果并不是我们想要的方位角,因此需要进行转化。
索性在实际的canvas绘图很多时候我们并不一定要计算出直线的方位角,看下面的这两个方法:
function getBearing(from, to) {
return Math.atan2(to[1] - from[1], to[0] - from[0])
}
function getCoordinate(point, angle, length) {
return [
point[0] + Math.cos(angle) * length,
point[1] + Math.sin(angle) * length,
]
}
getBearing
方法就是我调整之后的简化版的计算“方位角”的方法。实际上这个方法的返回值并不是方位角,这一点在前面已经介绍过了。但是我们有这个角度其实就已经足够了。
可以看下面这个例子,已知点 A的坐标、AB的方位角以及AB的长度,就可以使用我在之前封装的坐标正算方法calcCoordinate
计算出B点的坐标。
const A = [900, 500]
const l = 100
const angle = 330
const B = calcCoordinate(A, l, angle)
console.log(B);// [986.6025403784439, 450]
现在我就使用getBearing
和getCoordinate
方法对计算出来的B点坐标进行一下验算:
const angle_check = getBearing(A, B)
const B_check = getCoordinate(A, angle_check, l)
console.log(B_check);//[986.6025403784439, 450]
可以看到使用这两个方法getBearing
和getCoordinate
也能够算出同样的结果。因此在一些情况下可以使用这两个方法代替calcLineBearing
和calcCoordinat
方法。(特别适合那些已知两个点的坐标,求第三个点坐标的情况)