test4.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. <?php
  2. class OptimizedFlightPathGenerator
  3. {
  4. private $startPoint;
  5. private $points;
  6. private $noFlyZone;
  7. private $highVoltage;
  8. // 地球半径(米)
  9. const EARTH_RADIUS = 6371000;
  10. public function __construct($flightInput)
  11. {
  12. $this->startPoint = $flightInput['start_point'];
  13. $this->points = $flightInput['points'];
  14. $this->noFlyZone = $flightInput['no_fly_zone'];
  15. $this->highVoltage = $flightInput['high_voltage'];
  16. }
  17. /**
  18. * 生成优化的航线路径
  19. */
  20. public function generateOptimizedFlightPath()
  21. {
  22. // 使用凸包算法找到外围点,然后填充内部点
  23. $optimizedPath = $this->generateEfficientPath();
  24. // 检查并处理禁飞区相交 - 使用新的避让算法
  25. $pathAfterNoFly = $this->handleNoFlyZoneIntersectionsNew($optimizedPath);
  26. // 检查并处理高压线相交
  27. $finalPath = $this->handleHighVoltageIntersections($pathAfterNoFly);
  28. return $finalPath;
  29. }
  30. /**
  31. * 新的禁飞区相交处理方法 - 生成中间点并向外移动
  32. */
  33. private function handleNoFlyZoneIntersectionsNew($path)
  34. {
  35. $maxIterations = 10; // 最大迭代次数,防止无限循环
  36. $iteration = 0;
  37. do {
  38. $hasIntersection = false;
  39. $newPath = [];
  40. // echo '--aaaaaaaa-'.PHP_EOL;
  41. for ($i = 0; $i < count($path) - 1; $i++) {
  42. $currentPoint = $path[$i];
  43. $nextPoint = $path[$i + 1];
  44. // 检查当前线段是否与禁飞区相交
  45. $intersections = $this->checkLineCircleIntersection(
  46. $currentPoint,
  47. $nextPoint,
  48. $this->noFlyZone['center'],
  49. $this->noFlyZone['radius']+50
  50. );
  51. // var_dump($intersections);
  52. if (!empty($intersections)) {
  53. // 发现相交,生成中间点并向外移动
  54. $hasIntersection = true;
  55. // 计算两个航点的中间点
  56. $midPoint = $this->calculateMidpoint($currentPoint, $nextPoint);
  57. // var_dump($intersections);
  58. // 将中间点向外移动到禁飞区外100米
  59. $safePoint = $this->movePointAwayFromNoFlyZone($midPoint, 150);
  60. // 添加安全点到路径
  61. $newPath[] = $currentPoint;
  62. $newPath[] = [
  63. 'name' => '禁飞区避让点',
  64. 'lng' => $safePoint['lng'],
  65. 'lat' => $safePoint['lat'],
  66. 'type' => 'no_fly_zone_avoidance'
  67. ];
  68. // 不添加nextPoint,因为它会在下一次循环中作为currentPoint处理
  69. } else {
  70. // 没有相交,保持原路径点
  71. $newPath[] = $currentPoint;
  72. // 如果是最后一个点,也要添加
  73. if ($i == count($path) - 2) {
  74. $newPath[] = $nextPoint;
  75. }
  76. }
  77. }
  78. $path = $newPath;
  79. $iteration++;
  80. } while ($hasIntersection && $iteration < $maxIterations);
  81. // if ($iteration >= $maxIterations) {
  82. // echo "警告: 达到最大迭代次数,可能仍有禁飞区相交\n";
  83. // }
  84. return $path;
  85. }
  86. /**
  87. * 计算两个点的中间点
  88. */
  89. private function calculateMidpoint($point1, $point2)
  90. {
  91. return [
  92. 'lng' => ($point1['lng'] + $point2['lng']) / 2,
  93. 'lat' => ($point1['lat'] + $point2['lat']) / 2
  94. ];
  95. }
  96. /**
  97. * 将点从禁飞区中心向外移动指定距离
  98. */
  99. private function movePointAwayFromNoFlyZone($point, $safeDistance)
  100. {
  101. $center = $this->noFlyZone['center'];
  102. $radius = $this->noFlyZone['radius'];
  103. // 计算点到禁飞区中心的距离
  104. $distanceToCenter = $this->calculateDistance($point, $center);
  105. if ($distanceToCenter <= $radius + $safeDistance) {
  106. // 计算从禁飞区中心指向点的方向向量
  107. $dx = $point['lng'] - $center['lng'];
  108. $dy = $point['lat'] - $center['lat'];
  109. // 计算方向向量的长度
  110. $vectorLength = sqrt($dx * $dx + $dy * $dy);
  111. if ($vectorLength > 0) {
  112. // 归一化方向向量
  113. $dx /= $vectorLength;
  114. $dy /= $vectorLength;
  115. // 计算需要移动的总距离(从当前位置移动到禁飞区外safeDistance米)
  116. $moveDistance = $radius + $safeDistance - $distanceToCenter;
  117. // 计算新的点位置
  118. $newLng = $point['lng'] + $dx * $moveDistance * 0.00001; // 调整缩放因子
  119. $newLat = $point['lat'] + $dy * $moveDistance * 0.00001;
  120. return [
  121. 'lng' => $newLng,
  122. 'lat' => $newLat
  123. ];
  124. }
  125. }
  126. // 如果已经在安全距离外,返回原點
  127. return $point;
  128. }
  129. // 以下是原有的方法,保持不变
  130. private function generateEfficientPath()
  131. {
  132. if (count($this->points) <= 2) {
  133. return $this->nearestNeighborPath();
  134. }
  135. $center = $this->calculateCenter($this->points);
  136. $sortedPoints = $this->sortPointsByAngle($this->points, $center);
  137. $path = $this->improvedNearestInsertion($sortedPoints);
  138. $fullPath = [$this->startPoint];
  139. foreach ($path as $point) {
  140. $fullPath[] = $point;
  141. }
  142. $fullPath[] = $this->startPoint;
  143. return $fullPath;
  144. }
  145. private function calculateCenter($points)
  146. {
  147. $totalLng = 0;
  148. $totalLat = 0;
  149. $count = count($points);
  150. foreach ($points as $point) {
  151. $totalLng += $point['lng'];
  152. $totalLat += $point['lat'];
  153. }
  154. return [
  155. 'lng' => $totalLng / $count,
  156. 'lat' => $totalLat / $count
  157. ];
  158. }
  159. private function sortPointsByAngle($points, $center)
  160. {
  161. $angles = [];
  162. foreach ($points as $index => $point) {
  163. $angle = atan2(
  164. $point['lat'] - $center['lat'],
  165. $point['lng'] - $center['lng']
  166. );
  167. $angles[$index] = $angle;
  168. }
  169. asort($angles);
  170. $sortedPoints = [];
  171. foreach ($angles as $index => $angle) {
  172. $sortedPoints[] = $points[$index];
  173. }
  174. return $sortedPoints;
  175. }
  176. private function improvedNearestInsertion($points)
  177. {
  178. if (empty($points)) {
  179. return [];
  180. }
  181. $firstPointIndex = $this->findNearestPoint($this->startPoint, $points);
  182. $path = [$points[$firstPointIndex]];
  183. $remainingPoints = $points;
  184. unset($remainingPoints[$firstPointIndex]);
  185. $remainingPoints = array_values($remainingPoints);
  186. while (!empty($remainingPoints)) {
  187. $bestPointIndex = null;
  188. $bestPosition = null;
  189. $minIncrease = PHP_FLOAT_MAX;
  190. foreach ($remainingPoints as $pointIndex => $point) {
  191. for ($i = 0; $i <= count($path); $i++) {
  192. $increase = $this->calculateInsertionCost($path, $point, $i);
  193. if ($increase < $minIncrease) {
  194. $minIncrease = $increase;
  195. $bestPointIndex = $pointIndex;
  196. $bestPosition = $i;
  197. }
  198. }
  199. }
  200. array_splice($path, $bestPosition, 0, [$remainingPoints[$bestPointIndex]]);
  201. unset($remainingPoints[$bestPointIndex]);
  202. $remainingPoints = array_values($remainingPoints);
  203. }
  204. return $path;
  205. }
  206. private function calculateInsertionCost($path, $point, $position)
  207. {
  208. if (empty($path)) {
  209. return 0;
  210. }
  211. if ($position == 0) {
  212. return $this->calculateDistance($this->startPoint, $point) +
  213. $this->calculateDistance($point, $path[0]) -
  214. $this->calculateDistance($this->startPoint, $path[0]);
  215. } elseif ($position == count($path)) {
  216. return $this->calculateDistance($path[count($path)-1], $point) +
  217. $this->calculateDistance($point, $this->startPoint) -
  218. $this->calculateDistance($path[count($path)-1], $this->startPoint);
  219. } else {
  220. return $this->calculateDistance($path[$position-1], $point) +
  221. $this->calculateDistance($point, $path[$position]) -
  222. $this->calculateDistance($path[$position-1], $path[$position]);
  223. }
  224. }
  225. private function nearestNeighborPath()
  226. {
  227. $unvisited = $this->points;
  228. $currentPoint = $this->startPoint;
  229. $path = [$currentPoint];
  230. while (!empty($unvisited)) {
  231. $nearestIndex = $this->findNearestPoint($currentPoint, $unvisited);
  232. $nearestPoint = $unvisited[$nearestIndex];
  233. $path[] = $nearestPoint;
  234. $currentPoint = $nearestPoint;
  235. array_splice($unvisited, $nearestIndex, 1);
  236. }
  237. $path[] = $this->startPoint;
  238. return $path;
  239. }
  240. private function findNearestPoint($currentPoint, $points)
  241. {
  242. $minDistance = PHP_FLOAT_MAX;
  243. $nearestIndex = 0;
  244. foreach ($points as $index => $point) {
  245. $distance = $this->calculateDistance($currentPoint, $point);
  246. if ($distance < $minDistance) {
  247. $minDistance = $distance;
  248. $nearestIndex = $index;
  249. }
  250. }
  251. return $nearestIndex;
  252. }
  253. /**
  254. * 原有的禁飞区处理方法(保留用于参考)
  255. */
  256. private function handleNoFlyZoneIntersections($path)
  257. {
  258. $finalPath = [];
  259. for ($i = 0; $i < count($path) - 1; $i++) {
  260. $currentPoint = $path[$i];
  261. $nextPoint = $path[$i + 1];
  262. $finalPath[] = $currentPoint;
  263. $intersections = $this->checkLineCircleIntersection(
  264. $currentPoint,
  265. $nextPoint,
  266. $this->noFlyZone['center'],
  267. $this->noFlyZone['radius']
  268. );
  269. if (!empty($intersections)) {
  270. foreach ($intersections as $intersection) {
  271. $finalPath[] = [
  272. 'name' => '禁飞区相交点',
  273. 'lng' => $intersection['lng'],
  274. 'lat' => $intersection['lat'],
  275. 'type' => 'no_fly_zone_intersection'
  276. ];
  277. }
  278. }
  279. }
  280. $finalPath[] = $path[count($path) - 1];
  281. return $finalPath;
  282. }
  283. private function handleHighVoltageIntersections($path)
  284. {
  285. $finalPath = [];
  286. for ($i = 0; $i < count($path) - 1; $i++) {
  287. $currentPoint = $path[$i];
  288. $nextPoint = $path[$i + 1];
  289. $finalPath[] = $currentPoint;
  290. $intersections = $this->checkHighVoltageIntersections(
  291. $currentPoint,
  292. $nextPoint
  293. );
  294. if (!empty($intersections)) {
  295. foreach ($intersections as $intersection) {
  296. $finalPath[] = [
  297. 'name' => '高压线相交点',
  298. 'lng' => $intersection['lng'],
  299. 'lat' => $intersection['lat'],
  300. 'type' => 'high_voltage_intersection',
  301. 'tower1' => $intersection['tower1'],
  302. 'tower2' => $intersection['tower2']
  303. ];
  304. }
  305. }
  306. }
  307. $finalPath[] = $path[count($path) - 1];
  308. return $finalPath;
  309. }
  310. private function checkHighVoltageIntersections($pointA, $pointB)
  311. {
  312. $intersections = [];
  313. $towers = $this->highVoltage['towers'];
  314. for ($i = 0; $i < count($towers) - 1; $i++) {
  315. $tower1 = $towers[$i];
  316. $tower2 = $towers[$i + 1];
  317. $intersection = $this->checkLineSegmentIntersection(
  318. $pointA, $pointB, $tower1, $tower2
  319. );
  320. if ($intersection !== null) {
  321. $intersections[] = [
  322. 'lng' => $intersection['lng'],
  323. 'lat' => $intersection['lat'],
  324. 'tower1' => $tower1,
  325. 'tower2' => $tower2
  326. ];
  327. }
  328. }
  329. return $intersections;
  330. }
  331. private function checkLineSegmentIntersection($line1A, $line1B, $line2A, $line2B)
  332. {
  333. $p1 = $this->latLngToMeters($line1A['lat'], $line1A['lng']);
  334. $p2 = $this->latLngToMeters($line1B['lat'], $line1B['lng']);
  335. $p3 = $this->latLngToMeters($line2A['lat'], $line2A['lng']);
  336. $p4 = $this->latLngToMeters($line2B['lat'], $line2B['lng']);
  337. $denominator = (($p4[1] - $p3[1]) * ($p2[0] - $p1[0]) - ($p4[0] - $p3[0]) * ($p2[1] - $p1[1]));
  338. if ($denominator == 0) {
  339. return null;
  340. }
  341. $ua = (($p4[0] - $p3[0]) * ($p1[1] - $p3[1]) - ($p4[1] - $p3[1]) * ($p1[0] - $p3[0])) / $denominator;
  342. $ub = (($p2[0] - $p1[0]) * ($p1[1] - $p3[1]) - ($p2[1] - $p1[1]) * ($p1[0] - $p3[0])) / $denominator;
  343. if ($ua >= 0 && $ua <= 1 && $ub >= 0 && $ub <= 1) {
  344. $x = $p1[0] + $ua * ($p2[0] - $p1[0]);
  345. $y = $p1[1] + $ua * ($p2[1] - $p1[1]);
  346. $latLng = $this->metersToLatLng($x, $y);
  347. return [
  348. 'lng' => $latLng['lng'],
  349. 'lat' => $latLng['lat']
  350. ];
  351. }
  352. return null;
  353. }
  354. /**
  355. * 检测线段与圆是否相交并返回交点坐标
  356. * @param array $pointA 线段端点A,包含lat和lng键
  357. * @param array $pointB 线段端点B,包含lat和lng键
  358. * @param array $circleCenter 圆心坐标,包含lat和lng键
  359. * @param float $radius 圆的半径(米)
  360. * @return array 交点坐标数组,每个元素包含lat和lng键
  361. */
  362. function checkLineCircleIntersection($pointA, $pointB, $circleCenter, $radius)
  363. {
  364. $intersections = [];
  365. $earthRadius = 6378137; // 地球半径(米)
  366. $epsilon = 1e-12; // 容差
  367. // 度转弧度
  368. function toRadians($deg) {
  369. return $deg * M_PI / 180;
  370. }
  371. // 弧度转度
  372. function toDegrees($rad) {
  373. return $rad * 180 / M_PI;
  374. }
  375. // Haversine公式计算两点间距离(米)[1,3,5](@ref)
  376. function getDistanceFromLatLng($lat1, $lng1, $lat2, $lng2) {
  377. global $earthRadius;
  378. $φ1 = toRadians($lat1);
  379. $φ2 = toRadians($lat2);
  380. $Δφ = toRadians($lat2 - $lat1);
  381. $Δλ = toRadians($lng2 - $lng1);
  382. $a = sin($Δφ/2) * sin($Δφ/2) +
  383. cos($φ1) * cos($φ2) *
  384. sin($Δλ/2) * sin($Δλ/2);
  385. $c = 2 * atan2(sqrt($a), sqrt(1-$a));
  386. return $earthRadius * $c;
  387. }
  388. // 经纬度转墨卡托米坐标
  389. function latLngToMeters($lat, $lng) {
  390. global $earthRadius;
  391. $x = $earthRadius * toRadians($lng);
  392. $y = $earthRadius * log(tan(M_PI/4 + toRadians($lat)/2));
  393. return [$x, $y];
  394. }
  395. // 墨卡托米坐标转经纬度
  396. function metersToLatLng($x, $y) {
  397. global $earthRadius;
  398. $lng = ($x / $earthRadius) * 180 / M_PI;
  399. $lat = (2 * atan(exp($y / $earthRadius)) - M_PI/2) * 180 / M_PI;
  400. return [$lat, $lng];
  401. }
  402. // 判断点是否在圆内(用实际距离)[1](@ref)
  403. function isPointInCircle($point) {
  404. global $circleCenter, $radius, $epsilon;
  405. $dist = getDistanceFromLatLng(
  406. $point['lat'], $point['lng'],
  407. $circleCenter['lat'], $circleCenter['lng']
  408. );
  409. return $dist <= $radius + $epsilon;
  410. }
  411. // 转换坐标为墨卡托米
  412. $A = latLngToMeters($pointA['lat'], $pointA['lng']);
  413. $B = latLngToMeters($pointB['lat'], $pointB['lng']);
  414. $C = latLngToMeters($circleCenter['lat'], $circleCenter['lng']);
  415. // 向量计算[2,7](@ref)
  416. $AB = [$B[0] - $A[0], $B[1] - $A[1]];
  417. $AC = [$C[0] - $A[0], $C[1] - $A[1]];
  418. $ab2 = $AB[0]**2 + $AB[1]**2;
  419. // 线段为点的情况
  420. if ($ab2 < $epsilon) {
  421. if (isPointInCircle($pointA)) {
  422. return [[
  423. 'lng' => $pointA['lng'],
  424. 'lat' => $pointA['lat']
  425. ]];
  426. }
  427. return [];
  428. }
  429. // 计算最近点参数t[2](@ref)
  430. $t = ($AC[0] * $AB[0] + $AC[1] * $AB[1]) / $ab2;
  431. $closest = [$A[0] + $t * $AB[0], $A[1] + $t * $AB[1]];
  432. $distToClosest = sqrt(($closest[0]-$C[0])**2 + ($closest[1]-$C[1])**2);
  433. // 判断跨圆场景[7,8](@ref)
  434. $isPointAIn = isPointInCircle($pointA);
  435. $isPointBIn = isPointInCircle($pointB);
  436. $isCrossing = $isPointAIn !== $isPointBIn;
  437. // 关键修复:跨圆场景强制计算交点,即使最近点在圆外
  438. if ($distToClosest <= $radius + $epsilon || $isCrossing) {
  439. $discriminant = $radius**2 - $distToClosest**2;
  440. $dt = $discriminant >= 0 ? sqrt($discriminant / $ab2) : 0;
  441. $t1 = $t - $dt;
  442. $t2 = $t + $dt;
  443. // 添加交点的辅助函数
  444. $addIntersection = function($tVal) use ($A, $AB, $pointA, $pointB, $circleCenter, $radius) {
  445. // 允许tVal在[-0.1, 1.1]范围内(补偿墨卡托误差)
  446. if ($tVal >= -0.1 && $tVal <= 1.1) {
  447. $tClamped = max(0, min(1, $tVal)); // 强制夹紧到线段上
  448. $x = $A[0] + $tClamped * $AB[0];
  449. $y = $A[1] + $tClamped * $AB[1];
  450. list($lat, $lng) = metersToLatLng($x, $y);
  451. // 额外验证:确保交点到圆心的实际距离≈半径(容错5米)
  452. $intersectionDist = getDistanceFromLatLng($lat, $lng, $circleCenter['lat'], $circleCenter['lng']);
  453. if (abs($intersectionDist - $radius) <= 5) {
  454. return [
  455. 'lat' => round($lat, 6),
  456. 'lng' => round($lng, 6)
  457. ];
  458. }
  459. }
  460. return null;
  461. };
  462. $p1 = $addIntersection($t1);
  463. $p2 = $addIntersection($t2);
  464. if ($p1) {
  465. $intersections[] = $p1;
  466. }
  467. if ($p2 && !in_array($p2, $intersections, true)) {
  468. $intersections[] = $p2;
  469. }
  470. // 跨圆场景但无交点时,手动计算线段与圆的交点(最终保障)[6](@ref)
  471. if ($isCrossing && empty($intersections)) {
  472. // 用参数方程暴力求解(t从0到1逐步计算)
  473. for ($tForce = 0; $tForce <= 1; $tForce += 0.001) {
  474. $x = $A[0] + $tForce * $AB[0];
  475. $y = $A[1] + $tForce * $AB[1];
  476. list($lat, $lng) = metersToLatLng($x, $y);
  477. $dist = getDistanceFromLatLng($lat, $lng, $circleCenter['lat'], $circleCenter['lng']);
  478. if (abs($dist - $radius) <= 1) { // 误差≤1米
  479. $intersection = [
  480. 'lat' => round($lat, 6),
  481. 'lng' => round($lng, 6)
  482. ];
  483. if (!in_array($intersection, $intersections, true)) {
  484. $intersections[] = $intersection;
  485. }
  486. break;
  487. }
  488. }
  489. }
  490. }
  491. return $intersections;
  492. }
  493. private function latLngToMeters($lat, $lng)
  494. {
  495. $x = $lng * (M_PI / 180) * self::EARTH_RADIUS * cos($lat * M_PI / 180);
  496. $y = $lat * (M_PI / 180) * self::EARTH_RADIUS;
  497. return [$x, $y];
  498. }
  499. private function metersToLatLng($x, $y)
  500. {
  501. $lng = $x / (self::EARTH_RADIUS * cos($y / self::EARTH_RADIUS));
  502. $lat = $y / self::EARTH_RADIUS;
  503. return [
  504. 'lng' => $lng * (180 / M_PI),
  505. 'lat' => $lat * (180 / M_PI)
  506. ];
  507. }
  508. private function calculateDistance($point1, $point2)
  509. {
  510. $lat1 = $point1['lat'];
  511. $lng1 = $point1['lng'];
  512. $lat2 = $point2['lat'];
  513. $lng2 = $point2['lng'];
  514. $dLat = deg2rad($lat2 - $lat1);
  515. $dLng = deg2rad($lng2 - $lng1);
  516. $a = sin($dLat/2) * sin($dLat/2) +
  517. cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
  518. sin($dLng/2) * sin($dLng/2);
  519. $c = 2 * atan2(sqrt($a), sqrt(1-$a));
  520. return self::EARTH_RADIUS * $c;
  521. }
  522. public function printPathInfo($path)
  523. {
  524. echo "生成的优化航线路径:\n";
  525. echo "总点数: " . count($path) . "\n\n";
  526. $totalDistance = 0;
  527. $pathSequence = [];
  528. $intersectionCount = 0;
  529. $avoidanceCount = 0;
  530. for ($i = 0; $i < count($path); $i++) {
  531. $point = $path[$i];
  532. $name = isset($point['name']) ? $point['name'] : '起点/终点';
  533. $type = isset($point['type']) ? " ({$point['type']})" : "";
  534. echo ($i + 1) . ". {$name}{$type}\n";
  535. echo " 经度: {$point['lng']}, 纬度: {$point['lat']}\n";
  536. }
  537. }
  538. private function generateRandomPath()
  539. {
  540. $points = $this->points;
  541. shuffle($points);
  542. $path = [$this->startPoint];
  543. foreach ($points as $point) {
  544. $path[] = $point;
  545. }
  546. $path[] = $this->startPoint;
  547. return $path;
  548. }
  549. private function calculatePathDistance($path)
  550. {
  551. $totalDistance = 0;
  552. for ($i = 0; $i < count($path) - 1; $i++) {
  553. $totalDistance += $this->calculateDistance($path[$i], $path[$i + 1]);
  554. }
  555. return $totalDistance;
  556. }
  557. public function visualizePath($path)
  558. {
  559. echo "\n路径可视化:\n";
  560. $lngs = array_map(function($point) { return $point['lng']; }, $path);
  561. $lats = array_map(function($point) { return $point['lat']; }, $path);
  562. $minLng = min($lngs);
  563. $maxLng = max($lngs);
  564. $minLat = min($lats);
  565. $maxLat = max($lats);
  566. $width = 60;
  567. $height = 20;
  568. $grid = array_fill(0, $height, array_fill(0, $width, ' '));
  569. foreach ($path as $index => $point) {
  570. $x = (int)(($point['lng'] - $minLng) / ($maxLng - $minLng) * ($width - 1));
  571. $y = (int)(($point['lat'] - $minLat) / ($maxLat - $minLat) * ($height - 1));
  572. $char = '•';
  573. if ($index === 0 || $index === count($path)-1) {
  574. $char = 'S';
  575. } elseif (isset($point['type'])) {
  576. if ($point['type'] === 'no_fly_zone_intersection') {
  577. $char = 'X';
  578. } elseif ($point['type'] === 'high_voltage_intersection') {
  579. $char = 'H';
  580. } elseif ($point['type'] === 'no_fly_zone_avoidance') {
  581. $char = 'A'; // 避让点
  582. }
  583. }
  584. $grid[$y][$x] = $char;
  585. }
  586. $cx = (int)(($this->noFlyZone['center']['lng'] - $minLng) / ($maxLng - $minLng) * ($width - 1));
  587. $cy = (int)(($this->noFlyZone['center']['lat'] - $minLat) / ($maxLat - $minLat) * ($height - 1));
  588. $radius = 3;
  589. for ($a = 0; $a < 2 * M_PI; $a += 0.1) {
  590. $dx = (int)($radius * cos($a));
  591. $dy = (int)($radius * sin($a));
  592. if ($cx+$dx >= 0 && $cx+$dx < $width && $cy+$dy >= 0 && $cy+$dy < $height) {
  593. $grid[$cy+$dy][$cx+$dx] = 'O';
  594. }
  595. }
  596. $towers = $this->highVoltage['towers'];
  597. for ($i = 0; $i < count($towers) - 1; $i++) {
  598. $tower1 = $towers[$i];
  599. $tower2 = $towers[$i + 1];
  600. $x1 = (int)(($tower1['lng'] - $minLng) / ($maxLng - $minLng) * ($width - 1));
  601. $y1 = (int)(($tower1['lat'] - $minLat) / ($maxLat - $minLat) * ($height - 1));
  602. $x2 = (int)(($tower2['lng'] - $minLng) / ($maxLng - $minLng) * ($width - 1));
  603. $y2 = (int)(($tower2['lat'] - $minLat) / ($maxLat - $minLat) * ($height - 1));
  604. $grid[$y1][$x1] = 'T';
  605. $grid[$y2][$x2] = 'T';
  606. $this->drawLine($grid, $x1, $y1, $x2, $y2, '=');
  607. }
  608. for ($y = 0; $y < $height; $y++) {
  609. for ($x = 0; $x < $width; $x++) {
  610. echo $grid[$y][$x];
  611. }
  612. echo "\n";
  613. }
  614. echo "\n图例: S=起点/终点, •=航点, A=禁飞区避让点, X=禁飞区相交点, H=高压线相交点, O=禁飞区边界, T=高压线塔, ===高压线\n";
  615. }
  616. private function drawLine(&$grid, $x1, $y1, $x2, $y2, $char)
  617. {
  618. $dx = abs($x2 - $x1);
  619. $dy = abs($y2 - $y1);
  620. $sx = ($x1 < $x2) ? 1 : -1;
  621. $sy = ($y1 < $y2) ? 1 : -1;
  622. $err = $dx - $dy;
  623. while (true) {
  624. if ($grid[$y1][$x1] === ' ' || $grid[$y1][$x1] === '=') {
  625. $grid[$y1][$x1] = $char;
  626. }
  627. if ($x1 == $x2 && $y1 == $y2) break;
  628. $e2 = 2 * $err;
  629. if ($e2 > -$dy) {
  630. $err -= $dy;
  631. $x1 += $sx;
  632. }
  633. if ($e2 < $dx) {
  634. $err += $dx;
  635. $y1 += $sy;
  636. }
  637. }
  638. }
  639. }
  640. // 使用示例
  641. $flightInput = [
  642. "start_point" => ["lng" => 119.728918245, "lat" => 29.158205373],
  643. "points" => [
  644. ["name" => "航点1", "lng" => 119.731844828, "lat" => 29.155170432],
  645. ["name" => "航点2", "lng" => 119.734061703, "lat" => 29.157567795],
  646. ["name" => "航点3", "lng" => 119.731450602, "lat" => 29.163489386],
  647. ["name" => "航点4", "lng" => 119.727492742, "lat" => 29.165032019],
  648. ["name" => "航点5", "lng" => 119.727926162, "lat" => 29.162911848]
  649. ],
  650. "no_fly_zone" => [
  651. "center" => ["lng" => 119.729029043, "lat" => 29.161302197],
  652. "radius" => 200
  653. ],
  654. "high_voltage" => [
  655. "towers" => [
  656. ["lng" => 119.728687943, "lat" => 29.156864388],
  657. ["lng" => 119.735076462, "lat" => 29.160592962]
  658. ]
  659. ]
  660. ];
  661. // 创建航线生成器实例
  662. $flightGenerator = new OptimizedFlightPathGenerator($flightInput);
  663. // 生成优化航线
  664. $flightPath = $flightGenerator->generateOptimizedFlightPath();
  665. // 输出航线信息
  666. $flightGenerator->printPathInfo($flightPath);
  667. ?>