index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. // index.ts
  2. import { bbox } from "@turf/bbox";
  3. import { booleanPointInPolygon } from "@turf/boolean-point-in-polygon";
  4. import { distance } from "@turf/distance";
  5. import { transformScale as scale } from "@turf/transform-scale";
  6. import { cleanCoords } from "@turf/clean-coords";
  7. import { bboxPolygon } from "@turf/bbox-polygon";
  8. import { getCoord, getGeom } from "@turf/invariant";
  9. import {
  10. point,
  11. isNumber,
  12. lineString,
  13. isObject,
  14. featureCollection,
  15. feature
  16. } from "@turf/helpers";
  17. // lib/javascript-astar.js
  18. function pathTo(node) {
  19. var curr = node, path = [];
  20. while (curr.parent) {
  21. path.unshift(curr);
  22. curr = curr.parent;
  23. }
  24. return path;
  25. }
  26. function getHeap() {
  27. return new BinaryHeap(function(node) {
  28. return node.f;
  29. });
  30. }
  31. var astar = {
  32. /**
  33. * Perform an A* Search on a graph given a start and end node.
  34. *
  35. * @private
  36. * @memberof astar
  37. * @param {Graph} graph Graph
  38. * @param {GridNode} start Start
  39. * @param {GridNode} end End
  40. * @param {Object} [options] Options
  41. * @param {bool} [options.closest] Specifies whether to return the path to the closest node if the target is unreachable.
  42. * @param {Function} [options.heuristic] Heuristic function (see astar.heuristics).
  43. * @returns {Object} Search
  44. */
  45. search: function(graph, start, end, options) {
  46. var _a;
  47. graph.cleanDirty();
  48. options = options || {};
  49. var heuristic = options.heuristic || astar.heuristics.manhattan, closest = (_a = options.closest) != null ? _a : false;
  50. var openHeap = getHeap(), closestNode = start;
  51. start.h = heuristic(start, end);
  52. openHeap.push(start);
  53. while (openHeap.size() > 0) {
  54. var currentNode = openHeap.pop();
  55. if (currentNode === end) {
  56. return pathTo(currentNode);
  57. }
  58. currentNode.closed = true;
  59. var neighbors = graph.neighbors(currentNode);
  60. for (var i = 0, il = neighbors.length; i < il; ++i) {
  61. var neighbor = neighbors[i];
  62. if (neighbor.closed || neighbor.isWall()) {
  63. continue;
  64. }
  65. var gScore = currentNode.g + neighbor.getCost(currentNode), beenVisited = neighbor.visited;
  66. if (!beenVisited || gScore < neighbor.g) {
  67. neighbor.visited = true;
  68. neighbor.parent = currentNode;
  69. neighbor.h = neighbor.h || heuristic(neighbor, end);
  70. neighbor.g = gScore;
  71. neighbor.f = neighbor.g + neighbor.h;
  72. graph.markDirty(neighbor);
  73. if (closest) {
  74. if (neighbor.h < closestNode.h || neighbor.h === closestNode.h && neighbor.g < closestNode.g) {
  75. closestNode = neighbor;
  76. }
  77. }
  78. if (!beenVisited) {
  79. openHeap.push(neighbor);
  80. } else {
  81. openHeap.rescoreElement(neighbor);
  82. }
  83. }
  84. }
  85. }
  86. if (closest) {
  87. return pathTo(closestNode);
  88. }
  89. return [];
  90. },
  91. // See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
  92. heuristics: {
  93. manhattan: function(pos0, pos1) {
  94. var d1 = Math.abs(pos1.x - pos0.x);
  95. var d2 = Math.abs(pos1.y - pos0.y);
  96. return d1 + d2;
  97. },
  98. diagonal: function(pos0, pos1) {
  99. var D = 1;
  100. var D2 = Math.sqrt(2);
  101. var d1 = Math.abs(pos1.x - pos0.x);
  102. var d2 = Math.abs(pos1.y - pos0.y);
  103. return D * (d1 + d2) + (D2 - 2 * D) * Math.min(d1, d2);
  104. }
  105. },
  106. cleanNode: function(node) {
  107. node.f = 0;
  108. node.g = 0;
  109. node.h = 0;
  110. node.visited = false;
  111. node.closed = false;
  112. node.parent = null;
  113. }
  114. };
  115. function Graph(gridIn, options) {
  116. options = options || {};
  117. this.nodes = [];
  118. this.diagonal = !!options.diagonal;
  119. this.grid = [];
  120. for (var x = 0; x < gridIn.length; x++) {
  121. this.grid[x] = [];
  122. for (var y = 0, row = gridIn[x]; y < row.length; y++) {
  123. var node = new GridNode(x, y, row[y]);
  124. this.grid[x][y] = node;
  125. this.nodes.push(node);
  126. }
  127. }
  128. this.init();
  129. }
  130. Graph.prototype.init = function() {
  131. this.dirtyNodes = [];
  132. for (var i = 0; i < this.nodes.length; i++) {
  133. astar.cleanNode(this.nodes[i]);
  134. }
  135. };
  136. Graph.prototype.cleanDirty = function() {
  137. for (var i = 0; i < this.dirtyNodes.length; i++) {
  138. astar.cleanNode(this.dirtyNodes[i]);
  139. }
  140. this.dirtyNodes = [];
  141. };
  142. Graph.prototype.markDirty = function(node) {
  143. this.dirtyNodes.push(node);
  144. };
  145. Graph.prototype.neighbors = function(node) {
  146. var ret = [], x = node.x, y = node.y, grid = this.grid;
  147. if (grid[x - 1] && grid[x - 1][y]) {
  148. ret.push(grid[x - 1][y]);
  149. }
  150. if (grid[x + 1] && grid[x + 1][y]) {
  151. ret.push(grid[x + 1][y]);
  152. }
  153. if (grid[x] && grid[x][y - 1]) {
  154. ret.push(grid[x][y - 1]);
  155. }
  156. if (grid[x] && grid[x][y + 1]) {
  157. ret.push(grid[x][y + 1]);
  158. }
  159. if (this.diagonal) {
  160. if (grid[x - 1] && grid[x - 1][y - 1]) {
  161. ret.push(grid[x - 1][y - 1]);
  162. }
  163. if (grid[x + 1] && grid[x + 1][y - 1]) {
  164. ret.push(grid[x + 1][y - 1]);
  165. }
  166. if (grid[x - 1] && grid[x - 1][y + 1]) {
  167. ret.push(grid[x - 1][y + 1]);
  168. }
  169. if (grid[x + 1] && grid[x + 1][y + 1]) {
  170. ret.push(grid[x + 1][y + 1]);
  171. }
  172. }
  173. return ret;
  174. };
  175. Graph.prototype.toString = function() {
  176. var graphString = [], nodes = this.grid, rowDebug, row, y, l;
  177. for (var x = 0, len = nodes.length; x < len; x++) {
  178. rowDebug = [];
  179. row = nodes[x];
  180. for (y = 0, l = row.length; y < l; y++) {
  181. rowDebug.push(row[y].weight);
  182. }
  183. graphString.push(rowDebug.join(" "));
  184. }
  185. return graphString.join("\n");
  186. };
  187. function GridNode(x, y, weight) {
  188. this.x = x;
  189. this.y = y;
  190. this.weight = weight;
  191. }
  192. GridNode.prototype.toString = function() {
  193. return "[" + this.x + " " + this.y + "]";
  194. };
  195. GridNode.prototype.getCost = function(fromNeighbor) {
  196. if (fromNeighbor && fromNeighbor.x !== this.x && fromNeighbor.y !== this.y) {
  197. return this.weight * 1.41421;
  198. }
  199. return this.weight;
  200. };
  201. GridNode.prototype.isWall = function() {
  202. return this.weight === 0;
  203. };
  204. function BinaryHeap(scoreFunction) {
  205. this.content = [];
  206. this.scoreFunction = scoreFunction;
  207. }
  208. BinaryHeap.prototype = {
  209. push: function(element) {
  210. this.content.push(element);
  211. this.sinkDown(this.content.length - 1);
  212. },
  213. pop: function() {
  214. var result = this.content[0];
  215. var end = this.content.pop();
  216. if (this.content.length > 0) {
  217. this.content[0] = end;
  218. this.bubbleUp(0);
  219. }
  220. return result;
  221. },
  222. remove: function(node) {
  223. var i = this.content.indexOf(node);
  224. var end = this.content.pop();
  225. if (i !== this.content.length - 1) {
  226. this.content[i] = end;
  227. if (this.scoreFunction(end) < this.scoreFunction(node)) {
  228. this.sinkDown(i);
  229. } else {
  230. this.bubbleUp(i);
  231. }
  232. }
  233. },
  234. size: function() {
  235. return this.content.length;
  236. },
  237. rescoreElement: function(node) {
  238. this.sinkDown(this.content.indexOf(node));
  239. },
  240. sinkDown: function(n) {
  241. var element = this.content[n];
  242. while (n > 0) {
  243. var parentN = (n + 1 >> 1) - 1, parent = this.content[parentN];
  244. if (this.scoreFunction(element) < this.scoreFunction(parent)) {
  245. this.content[parentN] = element;
  246. this.content[n] = parent;
  247. n = parentN;
  248. } else {
  249. break;
  250. }
  251. }
  252. },
  253. bubbleUp: function(n) {
  254. var length = this.content.length, element = this.content[n], elemScore = this.scoreFunction(element);
  255. while (true) {
  256. var child2N = n + 1 << 1, child1N = child2N - 1;
  257. var swap = null, child1Score;
  258. if (child1N < length) {
  259. var child1 = this.content[child1N];
  260. child1Score = this.scoreFunction(child1);
  261. if (child1Score < elemScore) {
  262. swap = child1N;
  263. }
  264. }
  265. if (child2N < length) {
  266. var child2 = this.content[child2N], child2Score = this.scoreFunction(child2);
  267. if (child2Score < (swap === null ? elemScore : child1Score)) {
  268. swap = child2N;
  269. }
  270. }
  271. if (swap !== null) {
  272. this.content[n] = this.content[swap];
  273. this.content[swap] = element;
  274. n = swap;
  275. } else {
  276. break;
  277. }
  278. }
  279. }
  280. };
  281. // index.ts
  282. function shortestPath(start, end, options = {}) {
  283. options = options || {};
  284. if (!isObject(options)) throw new Error("options is invalid");
  285. let obstacles = options.obstacles || featureCollection([]);
  286. let resolution = options.resolution || 100;
  287. if (!start) throw new Error("start is required");
  288. if (!end) throw new Error("end is required");
  289. if (resolution && (!isNumber(resolution) || resolution <= 0))
  290. throw new Error("options.resolution must be a number, greater than 0");
  291. const startCoord = getCoord(start);
  292. const endCoord = getCoord(end);
  293. start = point(startCoord);
  294. end = point(endCoord);
  295. if (obstacles.type === "FeatureCollection") {
  296. if (obstacles.features.length === 0) {
  297. return lineString([startCoord, endCoord]);
  298. }
  299. } else if (obstacles.type === "Polygon") {
  300. obstacles = featureCollection([feature(getGeom(obstacles))]);
  301. } else {
  302. throw new Error("invalid obstacles");
  303. }
  304. const collection = obstacles;
  305. collection.features.push(start);
  306. collection.features.push(end);
  307. const box = bbox(scale(bboxPolygon(bbox(collection)), 1.15));
  308. const [west, south, east, north] = box;
  309. const width = distance([west, south], [east, south], options);
  310. const division = width / resolution;
  311. collection.features.pop();
  312. collection.features.pop();
  313. const xFraction = division / distance([west, south], [east, south], options);
  314. const cellWidth = xFraction * (east - west);
  315. const yFraction = division / distance([west, south], [west, north], options);
  316. const cellHeight = yFraction * (north - south);
  317. const bboxHorizontalSide = east - west;
  318. const bboxVerticalSide = north - south;
  319. const columns = Math.floor(bboxHorizontalSide / cellWidth);
  320. const rows = Math.floor(bboxVerticalSide / cellHeight);
  321. const deltaX = (bboxHorizontalSide - columns * cellWidth) / 2;
  322. const deltaY = (bboxVerticalSide - rows * cellHeight) / 2;
  323. const pointMatrix = [];
  324. const matrix = [];
  325. let closestToStart;
  326. let closestToEnd;
  327. let minDistStart = Infinity;
  328. let minDistEnd = Infinity;
  329. let currentY = north - deltaY;
  330. let r = 0;
  331. while (currentY >= south) {
  332. const matrixRow = [];
  333. const pointMatrixRow = [];
  334. let currentX = west + deltaX;
  335. let c = 0;
  336. while (currentX <= east) {
  337. const pt = point([currentX, currentY]);
  338. const isInsideObstacle = isInside(pt, obstacles);
  339. matrixRow.push(isInsideObstacle ? 0 : 1);
  340. pointMatrixRow.push(currentX + "|" + currentY);
  341. const distStart = distance(pt, start);
  342. if (!isInsideObstacle && distStart < minDistStart) {
  343. minDistStart = distStart;
  344. closestToStart = { x: c, y: r };
  345. }
  346. const distEnd = distance(pt, end);
  347. if (!isInsideObstacle && distEnd < minDistEnd) {
  348. minDistEnd = distEnd;
  349. closestToEnd = { x: c, y: r };
  350. }
  351. currentX += cellWidth;
  352. c++;
  353. }
  354. matrix.push(matrixRow);
  355. pointMatrix.push(pointMatrixRow);
  356. currentY -= cellHeight;
  357. r++;
  358. }
  359. const graph = new Graph(matrix, { diagonal: true });
  360. const startOnMatrix = graph.grid[closestToStart.y][closestToStart.x];
  361. const endOnMatrix = graph.grid[closestToEnd.y][closestToEnd.x];
  362. const result = astar.search(graph, startOnMatrix, endOnMatrix);
  363. const path = [startCoord];
  364. result.forEach(function(coord) {
  365. const coords = pointMatrix[coord.x][coord.y].split("|");
  366. path.push([+coords[0], +coords[1]]);
  367. });
  368. path.push(endCoord);
  369. return cleanCoords(lineString(path));
  370. }
  371. function isInside(pt, polygons) {
  372. for (let i = 0; i < polygons.features.length; i++) {
  373. if (booleanPointInPolygon(pt, polygons.features[i])) {
  374. return true;
  375. }
  376. }
  377. return false;
  378. }
  379. var turf_shortest_path_default = shortestPath;
  380. export {
  381. turf_shortest_path_default as default,
  382. shortestPath
  383. };
  384. //# sourceMappingURL=index.js.map