CheckAreaHandler.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <?php
  2. namespace App\Handlers;
  3. class CheckAreaHandler
  4. {
  5. // 一个表示区域的三维数组
  6. protected $config = null;
  7. // 包含每个区域的四边形
  8. protected $rectangles = null;
  9. // 每个区域(多边形)的所有边
  10. protected $lines = null;
  11. // 要判断的点的x, y坐标
  12. protected $_x = null;
  13. protected $_y = null;
  14. public function __construct($config)
  15. {
  16. $this->config = $config;
  17. $this->initRectangles();
  18. $this->initLines();
  19. }
  20. /*
  21. 获取包含每个配送区域的四边形
  22. */
  23. private function initRectangles()
  24. {
  25. foreach ($this->config as $k => $v) {
  26. $this->rectangles[$k]['minX'] = $this->getMinXInEachConfig($k);
  27. $this->rectangles[$k]['minY'] = $this->getMinYInEachConfig($k);
  28. $this->rectangles[$k]['maxX'] = $this->getMaxXInEachConfig($k);
  29. $this->rectangles[$k]['maxY'] = $this->getMaxYInEachConfig($k);
  30. }
  31. }
  32. /*
  33. 初始化每个区域(多边形)的边(线段:直线的一部分【限制x或者y坐标范围】)
  34. n 个顶点构成的多边形,有 n-1 条边
  35. */
  36. private function initLines()
  37. {
  38. foreach ($this->config as $k => $v) {
  39. $pointNum = count($v); // 区域的顶点个数
  40. $lineNum = $pointNum - 1; // 区域的边条数
  41. for ($i = 0; $i < $lineNum; $i++) {
  42. // y=kx+b : k
  43. if ($this->config[$k][$i]['longitude'] - $this->config[$k][$i + 1]['longitude'] == 0) $this->lines[$k][$i]['k'] = 0;
  44. else $this->lines[$k][$i]['k'] =
  45. ($this->config[$k][$i]['latitude'] - $this->config[$k][$i + 1]['latitude']) / ($this->config[$k][$i]['longitude'] - $this->config[$k][$i + 1]['longitude']);
  46. // y=kx+b : b
  47. $this->lines[$k][$i]['b'] = $this->config[$k][$i + 1]['latitude'] - $this->lines[$k][$i]['k'] * $this->config[$k][$i + 1]['longitude'];
  48. $this->lines[$k][$i]['lx'] = min($this->config[$k][$i]['longitude'], $this->config[$k][$i + 1]['longitude']);
  49. $this->lines[$k][$i]['rx'] = max($this->config[$k][$i]['longitude'], $this->config[$k][$i + 1]['longitude']);
  50. }
  51. $pointNum -= 1;
  52. if ($this->config[$k][$pointNum]['longitude'] - $this->config[$k][0]['longitude'] == 0) $this->lines[$k][$pointNum]['k'] = 0;
  53. else $this->lines[$k][$pointNum]['k'] =
  54. ($this->config[$k][$pointNum]['latitude'] - $this->config[$k][0]['latitude']) / ($this->config[$k][$pointNum]['longitude'] - $this->config[$k][0]['longitude']);
  55. // y=kx+b : b
  56. $this->lines[$k][$pointNum]['b'] = $this->config[$k][0]['latitude'] - $this->lines[$k][$pointNum]['k'] * $this->config[$k][0]['longitude'];
  57. $this->lines[$k][$pointNum]['lx'] = min($this->config[$k][$pointNum]['longitude'], $this->config[$k][0]['longitude']);
  58. $this->lines[$k][$pointNum]['rx'] = max($this->config[$k][$pointNum]['longitude'], $this->config[$k][0]['longitude']);
  59. }
  60. }
  61. /*
  62. 获取一组坐标中,x坐标最小值
  63. */
  64. private function getMinXInEachConfig($index)
  65. {
  66. $minX = 200;
  67. foreach ($this->config[$index] as $k => $v) {
  68. if ($v['longitude'] < $minX) {
  69. $minX = $v['longitude'];
  70. }
  71. }
  72. return $minX;
  73. }
  74. /*
  75. 获取一组坐标中,y坐标最小值
  76. */
  77. private function getMinYInEachConfig($index)
  78. {
  79. $minY = 200;
  80. foreach ($this->config[$index] as $k => $v) {
  81. if ($v['latitude'] < $minY) {
  82. $minY = $v['latitude'];
  83. }
  84. }
  85. return $minY;
  86. }
  87. /*
  88. 获取一组坐标中,x坐标最大值
  89. */
  90. public function getMaxXInEachConfig($index)
  91. {
  92. $maxX = 0;
  93. foreach ($this->config[$index] as $k => $v) {
  94. if ($v['longitude'] > $maxX) {
  95. $maxX = $v['longitude'];
  96. }
  97. }
  98. return $maxX;
  99. }
  100. /*
  101. 获取一组坐标中,y坐标最大值
  102. */
  103. public function getMaxYInEachConfig($index)
  104. {
  105. $maxY = 0;
  106. foreach ($this->config[$index] as $k => $v) {
  107. if ($v['latitude'] > $maxY) {
  108. $maxY = $v['latitude'];
  109. }
  110. }
  111. return $maxY;
  112. }
  113. /*
  114. 获取 y=y0 与特定区域的所有边的交点,并去除和顶点重复的,再将交点分为左和右两部分
  115. */
  116. private function getCrossPointInCertainConfig($index)
  117. {
  118. $crossPoint = ['left' => [], 'right' => []];
  119. foreach ($this->lines[$index] as $k => $v) {
  120. if ($v['k'] == 0) return true;
  121. $x0 = ($this->_y - $v['b']) / $v['k']; // 交点x坐标
  122. if ($x0 == $this->_x) return true; // 点在边上
  123. if ($x0 > $v['lx'] && $x0 < $v['rx']) {
  124. if ($x0 < $this->_x) $crossPoint['left'][] = $x0;
  125. if ($x0 > $this->_x) $crossPoint['right'][] = $x0;
  126. }
  127. }
  128. return $crossPoint;
  129. }
  130. /*
  131. 检测一个点,是否在区域内
  132. 返回结果:
  133. return === false : 点不在区域内
  134. return 0, 1, 2, 3 ... 点所在的区域编号(配置文件中的区域编号。)
  135. */
  136. public function checkPoint($lng, $lat)
  137. {
  138. $this->_x = $lng;
  139. $this->_y = $lat;
  140. $contain = null;
  141. foreach ($this->rectangles as $k => $v) {
  142. if ($lng > $v['maxX'] || $lng < $v['minX'] || $lat > $v['maxY'] || $lat < $v['minY']) {
  143. continue;
  144. } else {
  145. $contain = $k;
  146. break;
  147. }
  148. }
  149. if ($contain === null) return false;
  150. $crossPoint = $this->getCrossPointInCertainConfig($contain);
  151. if ($crossPoint === true) return $contain;
  152. if (count($crossPoint['left']) % 2 == 1 && count($crossPoint['right']) % 2 == 1) return $contain;
  153. return false;
  154. }
  155. }