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

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

// 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

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

// 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)|。因此最终的代码如下:

// 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°

最终转换成代码就是:

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]

现在我就使用getBearinggetCoordinate方法对计算出来的B点坐标进行一下验算:

  const angle_check = getBearing(A, B)
  const B_check = getCoordinate(A, angle_check, l)
  console.log(B_check);//[986.6025403784439, 450]

可以看到使用这两个方法getBearinggetCoordinate也能够算出同样的结果。因此在一些情况下可以使用这两个方法代替calcLineBearingcalcCoordinat方法。(特别适合那些已知两个点的坐标,求第三个点坐标的情况)

参考资料

  1. 工程测量(第二版)第六章 直线方位角测量
  2. 【Java AWT 图形界面编程】在 Canvas 画布中绘制箭头图形 ( 数据准备 | 几个关键的计算公式 | 绘制箭头直线和尾翼 )-腾讯云开发者社区-腾讯云
  3. 坐标反算
  4. Math.hypot() - JavaScript | MDN
  5. 知道两点坐标,怎么计算两点方向的方位角_知道两个坐标点怎么算方位角-CSDN博客
  6. 计算两点之间的方位角
  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用原生JavaScript实现Canvas绘图拖拽的示例代码: ```javascript // 获取Canvas元素 var canvas = document.getElementById('myCanvas'); // 获取Canvas上下文 var ctx = canvas.getContext('2d'); // 定义一个矩形对象 var rect = { x: 50, y: 50, width: 100, height: 100, isDragging: false }; // 绘制矩形 function drawRect() { ctx.beginPath(); ctx.rect(rect.x, rect.y, rect.width, rect.height); ctx.fillStyle = '#0095DD'; ctx.fill(); ctx.closePath(); } // 监听鼠标按下事件 canvas.addEventListener('mousedown', function(e) { var mouseX = e.clientX - canvas.offsetLeft; var mouseY = e.clientY - canvas.offsetTop; // 判断鼠标是否在矩形内部 if (mouseX >= rect.x && mouseX <= rect.x + rect.width && mouseY >= rect.y && mouseY <= rect.y + rect.height) { rect.isDragging = true; } }); // 监听鼠标移动事件 canvas.addEventListener('mousemove', function(e) { if (rect.isDragging) { rect.x = e.clientX - canvas.offsetLeft - rect.width / 2; rect.y = e.clientY - canvas.offsetTop - rect.height / 2; // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 重新绘制矩形 drawRect(); } }); // 监听鼠标松开事件 canvas.addEventListener('mouseup', function(e) { rect.isDragging = false; }); // 绘制矩形 drawRect(); ``` 以上代码实现了一个简单的Canvas矩形拖拽功能。当鼠标按下时,判断鼠标是否在矩形内部,如果是,则将矩形的isDragging属性设置为true,表示正在被拖拽。当鼠标移动时,如果矩形正在被拖拽,则根据鼠标的位置重新设置矩形的坐标,并清空画布,重新绘制矩形。当鼠标松开时,将矩形的isDragging属性设置为false,表示拖拽结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值