1. 算法分析

image.png

1.1 三点定位的介绍

假设有三个基站信标: A、B、C, 其根据rssi信号的强度不同,形成不同半径的圆形范围。

而上图是一种非常理想的情况,即终端测得的信号非常稳定和准确,ABC三个终端采集的信号强度形成的圆交于一点。那么这个点就是我们当前用户终端的准确位置。

然鹅,。。。现实的信号强度受环境影响,以及信号转化成距离的精确度影响。往往会出现👇的几种情况:

image.png

image.png

image.png

1.2 三点定位的条件判断

那么我们需要根据多个信号形成的状态圆,分别进行考虑计算:
根据上面的几张图,我们可知两个圆会出现相交,相切,相离,内含等情况。

1.2.1 计算相交的情况

image.png

设点A坐标为(x1, y1), B坐标为(x2, y2).通常A、B点可视为部署信标的坐标。r1,r2为信号圆的半径。通常为当前终端采集到的AB信标的信号强度。

由此我们可以逐步推导E点公式
获取欧几里得距离 AB^2 = (x2 - x1)^2 + (y2 - y1)^2
同时可得公式

  • AE^2 = r1^2 - CE^2
  • EB^2 = r2^2 - CE^2
  • AB^2 = (AE + EB) ^2

连立可解得公式: AE = (AB^2 + r1^2 - r2^2) / 2 * AB
同时解得:CE = sqrt(r1^2 - AE^2)

这样我们就求得了E点坐标,接下来就可以通过夹角很容易算出C,D点的坐标

Ex = x1 + (x2 - x1) * AE / AB
Ey = y1 + (y2 - y1) * AE / AB

接下来:

先计算出夹角:

angle = asin((y2 - y1) / AB)

然后计算出两个圆心的左右排列顺序

direct = x1 - x2 > 0 ? -1 : 1

计算出C点和D点坐标

Cx = Ex + direct _sin(angle) _CE

Cy = Ey - cos(angle) * CE

Dx = Ex - direct _sin(angle) _CE

Dy = Ey - cos(angle) * CE

这时候我们该选哪个点作为质心计算的三角点呢?我们还需要引入第三个信标作为参考

我们只需要根据CD 两点到第三点信标坐标的曼哈顿距离来进行判断即可:

RestPointerC = abs(Cx - x3) + abs(Cy - y3)
RestPointerD = abs(Dx - x3) + abs(Dy - y3)

然后RestPointerC,RestPointerD的距离哪个小,选取作为质心加权的参考点。

1.2.2 计算相切的情况

这种情况就相对简单

Ex = x1 + (x2 - x1) * AE / AB
Ey = y1 + (y2 - y1) * AE / AB

AE即为A圆的半径,同时选取Ex,Ey点作为参考点

1.2.3 计算相离的情况

image.png

根据二者信号强度的比例来计算对应的参考点O
distX = Bx - Ax
disY = By - Ay
Ox = Ax + (AB + (r1 - r2) _ 0.5) / AB distX
_Oy = Ay +
(AB + (r1 - r2) _ 0.5) / AB distY_

1.2.4 计算内含的情况

image.png
同样根据强度比例获取对应的参考点O
distX = Bx - Ax
disY = By - Ay
Ox = Ax + (r1 + r2 + AB) 0.5 distX / AB
Oy = Ay + (r1 + r2 + AB) 0.5 distY / AB


理解了上述存在的几种信号分布情况,我们可以两两计算出对应的三点坐标:

1.3 三点定位的质心加权

我们知道三角形的三个点对应的坐标分别为x1、y1, x2、y2, x3、y3

通过三角质心的公式可以推导出:

x = (x1 + x2 + x3) / 3
y = (y1 + y2 + y3) / 3

但是这里的质心坐标的权重是一样的,我们还需要对其进行加权优化.
我们依据Rssi的信号强度(圆半径)来进行加权,优化的公式为:

x = (x1 / (r1 + r2) + x2 / (r1 + r3) + x3 / (r2 + r3)) / (1 / (r1 + r2) + 1/ (r1 + r3) + 1 / (r2 + r3))

从上述公式可以看出,我们将下面的除数3根据信号强度的加权因子重新进行了加权计算。


质心加权算法的优化

两个已知节点的半径和的倒数这个因子的选取,例如r1和r2成为决定未知节点位置的关键。然而r1r2中数值较大的一个会在这个因子中起到更大的作用,使得较小的数值对应的节点所发挥的作用弱化,造成定位误差。

因此参考相关资料,通过改善参考因子的决定权,采取增加幂值的做法,进一步权衡加权因子,防止偏大的信号修正过度。

具体算法如下:

  1. // 优化的三点质心加权
  2. const x1 = (retPoint1.x * (1 / r1 + 1 / Math.pow(r2, r1 / r2))
  3. + retPoint2.x * (1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  4. + retPoint3.x * (1 / r1 + 1 / Math.pow(r3, r1 / r3))) / (
  5. 2 * (1 / r1 + 1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  6. );
  7. const y1 = (retPoint1.y * (1 / r1 + 1 / Math.pow(r2, r1 / r2))
  8. + retPoint2.y * (1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  9. + retPoint3.y * (1 / r1 + 1 / Math.pow(r3, r1 / r3))) / (
  10. 2 * (1 / r1 + 1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  11. );

即通过幂值进行加权,使得在系数中原本起主要作用的信号较强的一方所起作用稍微弱化,以防止过度修正。

参考文献:基于RSSI测距的改进加权质心定位算法

2. 完整代码实现:

  1. /*
  2. * @Author: kunnisser
  3. * @Date: 2020-12-03 10:17:56
  4. * @LastEditors: kunnisser
  5. * @LastEditTime: 2021-01-21 16:00:43
  6. * @FilePath: /kunigame/kuni/src/state/postion/index.ts
  7. * @Description: ---- 三点加权质心定位算法 ----
  8. */
  9. import KnScene from "ts@/kuni/lib/gameobjects/kn_scene";
  10. import Game from "ts@/kuni/lib/core";
  11. class Triangulation extends KnScene {
  12. public game: Game;
  13. public shootType: number;
  14. public tween: any;
  15. constructor(game: Game, key: string) {
  16. super(game, key);
  17. this.game = game;
  18. this.resouces = {
  19. }
  20. }
  21. boot() {
  22. }
  23. create() {
  24. this.tween = this.game.add.tween();
  25. this.addBackground();
  26. let r1 = 100 + Math.random() * 100;
  27. let r2 = 30 + Math.random() * 100;
  28. let r3 = 200 + Math.random() * 100;
  29. let point1 = {
  30. x: 600,
  31. y: 400,
  32. }
  33. let point2 = {
  34. x: 600,
  35. y: 450,
  36. }
  37. let point3 = {
  38. x: 300,
  39. y: 300
  40. }
  41. let c1 = this.game.add.graphics().generateLineCircle(0xd10311, [point1.x, point1.y, r1]);
  42. let c2 = this.game.add.graphics().generateLineCircle(0xe8a91a, [point2.x, point2.y, r2]);
  43. let c3 = this.game.add.graphics().generateLineCircle(0x1a73e8, [point3.x, point3.y, r3]);
  44. this.addChild(c1);
  45. this.addChild(c2);
  46. this.addChild(c3);
  47. let retPoint1 = this.computedPointer(r1, r2, point1, point2, point3);
  48. let retPoint2 = this.computedPointer(r1, r3, point1, point3, point2);
  49. let retPoint3 = this.computedPointer(r2, r3, point2, point3, point1);
  50. // 质心加权
  51. let x = (retPoint1.x / (r1 + r2) + retPoint2.x / (r1 + r3) + retPoint3.x / (r2 + r3)) / (
  52. 1 / (r1 + r2) + 1 / (r1 + r3) + 1 / (r2 + r3)
  53. );
  54. let y = (retPoint1.y / (r1 + r2) + retPoint2.y / (r1 + r3) + retPoint3.y / (r2 + r3)) / (
  55. 1 / (r1 + r2) + 1 / (r1 + r3) + 1 / (r2 + r3)
  56. );
  57. // 绘制定位点
  58. let pos = this.game.add.graphics().generateCircle(0x1ae846, [x, y, 8]);
  59. this.addChild(pos);
  60. console.log('定位坐标 x: ' + x, 'y: ' + y);
  61. // 优化的三点质心加权
  62. let x1 = (retPoint1.x * (1 / r1 + 1 / Math.pow(r2, r1 / r2))
  63. + retPoint2.x * (1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  64. + retPoint3.x * (1 / r1 + 1 / Math.pow(r3, r1 / r3))) / (
  65. 2 * (1 / r1 + 1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  66. );
  67. let y1 = (retPoint1.y * (1 / r1 + 1 / Math.pow(r2, r1 / r2))
  68. + retPoint2.y * (1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  69. + retPoint3.y * (1 / r1 + 1 / Math.pow(r3, r1 / r3))) / (
  70. 2 * (1 / r1 + 1 / Math.pow(r2, r1 / r2) + 1 / Math.pow(r3, r1 / r3))
  71. );
  72. let pos1 = this.game.add.graphics().generateCircle(0xb51ae8, [x1, y1, 8]);
  73. this.addChild(pos1);
  74. console.log('定位坐标2 x: ' + x1, 'y: ' + y1);
  75. }
  76. computedPointer(r1, r2, p1, p2, rest) {
  77. let AB: number = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
  78. console.log(AB);
  79. if (AB < r1 + r2 && Math.abs(r1 - r2) < AB) {
  80. console.log('相交');
  81. let AE: number = (Math.pow(r2, 2) - Math.pow(r1, 2) - Math.pow(AB, 2)) / (-2 * AB);
  82. console.log(AE);
  83. let CE: number = Math.sqrt(Math.pow(r1, 2) - Math.pow(AE, 2));
  84. console.log(CE);
  85. let flag = p1.x - p2.x > 0 ? -1 : 1;
  86. let pointE = {
  87. x: p1.x + ((p2.x - p1.x) * AE) / AB,
  88. y: p1.y + ((p2.y - p1.y) * AE) / AB
  89. };
  90. console.log(pointE);
  91. let angle = Math.asin((p2.y - p1.y) / AB);
  92. console.log(angle);
  93. let pointC = {
  94. x: pointE.x + flag * Math.sin(angle) * CE,
  95. y: pointE.y - Math.cos(angle) * CE
  96. };
  97. console.log(pointC);
  98. let pointD = {
  99. x: pointE.x - flag * Math.sin(angle) * CE,
  100. y: pointE.y + Math.cos(angle) * CE
  101. };
  102. let restPointC = Math.abs(pointC.x - rest.x) + Math.abs(pointC.y - rest.y);
  103. let restPointD = Math.abs(pointD.x - rest.x) + Math.abs(pointD.y - rest.y);
  104. if (restPointC > restPointD) {
  105. let pd = this.game.add.graphics().generateCircle(0xffffff, [pointD.x, pointD.y, 6]);
  106. this.addChild(pd);
  107. return pointD;
  108. } else {
  109. // 绘制相交点
  110. let pc = this.game.add.graphics().generateCircle(0xffffff, [pointC.x, pointC.y, 6]);
  111. this.addChild(pc);
  112. return pointC;
  113. }
  114. } else if (AB == r1 + r2) {
  115. console.log('相切');
  116. let AE: number = (Math.pow(r2, 2) - Math.pow(r1, 2) - Math.pow(AB, 2)) / (-2 * AB);
  117. let pointE = {
  118. x: p1.x + ((p2.x - p1.x) * AE) / AB,
  119. y: p1.y + ((p2.y - p1.y) * AE) / AB
  120. };
  121. console.log(pointE);
  122. // 绘制相交点
  123. let pe = this.game.add.graphics().generateCircle(0xffffff, [pointE.x, pointE.y, 6]);
  124. this.addChild(pe);
  125. return pointE;
  126. } else if (AB > r1 + r2) {
  127. console.log('相离');
  128. let disX = p2.x - p1.x;
  129. let disY = p2.y - p1.y;
  130. let pointE = {
  131. x: p1.x + (AB + r1 - r2) * 0.5 / AB * disX,
  132. y: p1.y + (AB + r1 - r2) * 0.5 / AB * disY
  133. };
  134. console.log(pointE);
  135. // 绘制相交点
  136. let pe = this.game.add.graphics().generateCircle(0xffffff, [pointE.x, pointE.y, 6]);
  137. this.addChild(pe);
  138. return pointE;
  139. } else {
  140. console.log('内含');
  141. let disX = p2.x - p1.x;
  142. let disY = p2.y - p1.y;
  143. let pointE = {
  144. x: p1.x + (r1 + r2 + AB) * 0.5 * disX / AB,
  145. y: p1.y + (r1 + r2 + AB) * 0.5 * disY / AB
  146. };
  147. console.log(pointE);
  148. // 绘制相交点
  149. let pe = this.game.add.graphics().generateCircle(0xffffff, [pointE.x, pointE.y, 6]);
  150. this.addChild(pe);
  151. return pointE;
  152. }
  153. }
  154. addBackground() {
  155. const bg = this.game.add.image('wsjBg', this);
  156. bg.width = this.game.config.width;
  157. bg.height = this.game.config.height;
  158. }
  159. reset() {
  160. if (this.children.length > 1) {
  161. // 清除group子对象
  162. this.removeChildren(1, this.children.length);
  163. }
  164. }
  165. }
  166. export default Triangulation;

3. 仿真三点定位效果

白色为计算出的三角参考点,绿色为普通加权的结果坐标,紫色为优化后加权的坐标:

image.png

image.png

image.png


结尾:三点定位的深入点在于加权因子的不断优化,实际上距离完整成熟的技术方案落地,还需要考虑进一步优化算法,同时降低计算成本等多方面因素,本文仅供研究参考。