canvas绘图学习:坐标反算

前言

在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)封装计算长度的方法

此时我们就可以封装计算直线长度的方法了:

JavaScript 复制代码
// 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

在之前的坐标正算、计算直线长度 和现在的计算方位角都涉及到坐标增量的计算。但实际上这三个地方的坐标增量都是略有不同的。

坐标正算 使用 cos() 和 sin() 计算出来的坐标增量都是正数
计算直线长度 在计算坐标增量时,可以用结束点坐标减起始点坐标;也可以用起始点坐标减结束点坐标。它们计算出来的结果是一致的。通过这种方式计算出来的坐标增量有正负。
计算坐标方位角 用结束点坐标减起始点坐标最终计算出来的是直线的正方位角(这是我们需要的);用起始点坐标减结束点坐标最终计算出来的是直线的反方位角。通过这种方式计算出来的坐标增量有正负。例如 :如果用 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

这个公式用代码表示就是:

JavaScript 复制代码
// R表示象限角
const R = Math.atan(deltaY / deltaX) * (180 / Math.PI)

在上一节提到过,在坐标反算中计算出来的坐标增量是有正有负的。因此arctan(∆y/∆x)的取值范围在 π/2 ~ - π/2 之间。下表是几个不同象限的方位角对应的 arctan(∆y/∆x)的值:

坐标方位角 arctan(∆y/∆x) 的结果
30° 30°
130° -50°
230° 50°
330° -30°

上面的结果再取一个绝对值就是我们所需的象限角 R = |arctan(∆y/∆x)|。因此最终的代码如下:

JavaScript 复制代码
// 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的符号以及点 (∆x,∆y)所处的象限 与方位角的关系
R2 > 0 ( 一、二象限) α = R2
R2 < 0 (三、四象限) α = R2 + 360°

最终转换成代码就是:

JavaScript 复制代码
const R2 = Math.atan2(∆y,∆x) * (180 / Math.PI)

if(R2 > 0) {
  α = R2 
}else{
  α = R2 + 360°
}

(5)封装计算方位角的方法

使用arctan()函数计算方位角:

JavaScript 复制代码
// 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()函数计算方位角:

JavaScript 复制代码
// 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
}

参考资料

  1. 工程测量(第二版)第六章 直线方位角测量
  2. 【Java AWT 图形界面编程】在 Canvas 画布中绘制箭头图形 ( 数据准备 | 几个关键的计算公式 | 绘制箭头直线和尾翼 )-腾讯云开发者社区-腾讯云
  3. 坐标反算
  4. Math.hypot() - JavaScript | MDN
  5. 知道两点坐标,怎么计算两点方向的方位角_知道两个坐标点怎么算方位角-CSDN博客
  6. 计算两点之间的方位角
相关推荐
花花鱼6 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k093310 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang135831 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning31 分钟前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人41 分钟前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
番茄小酱00142 分钟前
Expo|ReactNative 中实现扫描二维码功能
javascript·react native·react.js
子非鱼9211 小时前
【Ajax】跨域
javascript·ajax·cors·jsonp
超雄代码狂1 小时前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石1 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程1 小时前
【前端基础】CSS基础
前端·css