router.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import { FunctionExt } from '@antv/x6-common';
  2. import { Point, Rectangle } from '@antv/x6-geometry';
  3. import { SortedSet } from './sorted-set';
  4. import { ObstacleMap } from './obstacle-map';
  5. import * as util from './util';
  6. import { resolveOptions, } from './options';
  7. /**
  8. * Finds the route between two points (`from`, `to`).
  9. */
  10. function findRoute(edgeView, from, to, map, options) {
  11. const precision = options.precision;
  12. let sourceEndpoint;
  13. let targetEndpoint;
  14. if (Rectangle.isRectangle(from)) {
  15. sourceEndpoint = util.round(util.getSourceEndpoint(edgeView, options).clone(), precision);
  16. }
  17. else {
  18. sourceEndpoint = util.round(from.clone(), precision);
  19. }
  20. if (Rectangle.isRectangle(to)) {
  21. targetEndpoint = util.round(util.getTargetEndpoint(edgeView, options).clone(), precision);
  22. }
  23. else {
  24. targetEndpoint = util.round(to.clone(), precision);
  25. }
  26. // Get grid for this route.
  27. const grid = util.getGrid(options.step, sourceEndpoint, targetEndpoint);
  28. // Get pathfinding points.
  29. // -----------------------
  30. const startPoint = sourceEndpoint;
  31. const endPoint = targetEndpoint;
  32. let startPoints;
  33. let endPoints;
  34. if (Rectangle.isRectangle(from)) {
  35. startPoints = util.getRectPoints(startPoint, from, options.startDirections, grid, options);
  36. }
  37. else {
  38. startPoints = [startPoint];
  39. }
  40. if (Rectangle.isRectangle(to)) {
  41. endPoints = util.getRectPoints(targetEndpoint, to, options.endDirections, grid, options);
  42. }
  43. else {
  44. endPoints = [endPoint];
  45. }
  46. // take into account only accessible rect points (those not under obstacles)
  47. startPoints = startPoints.filter((p) => map.isAccessible(p));
  48. endPoints = endPoints.filter((p) => map.isAccessible(p));
  49. // There is an accessible route point on both sides.
  50. if (startPoints.length > 0 && endPoints.length > 0) {
  51. const openSet = new SortedSet();
  52. // Keeps the actual points for given nodes of the open set.
  53. const points = {};
  54. // Keeps the point that is immediate predecessor of given element.
  55. const parents = {};
  56. // Cost from start to a point along best known path.
  57. const costs = {};
  58. for (let i = 0, n = startPoints.length; i < n; i += 1) {
  59. // startPoint is assumed to be aligned already
  60. const startPoint = startPoints[i];
  61. const key = util.getKey(startPoint);
  62. openSet.add(key, util.getCost(startPoint, endPoints));
  63. points[key] = startPoint;
  64. costs[key] = 0;
  65. }
  66. const previousRouteDirectionAngle = options.previousDirectionAngle;
  67. // undefined for first route
  68. const isPathBeginning = previousRouteDirectionAngle === undefined;
  69. // directions
  70. let direction;
  71. let directionChange;
  72. const directions = util.getGridOffsets(grid, options);
  73. const numDirections = directions.length;
  74. const endPointsKeys = endPoints.reduce((res, endPoint) => {
  75. const key = util.getKey(endPoint);
  76. res.push(key);
  77. return res;
  78. }, []);
  79. // main route finding loop
  80. const sameStartEndPoints = Point.equalPoints(startPoints, endPoints);
  81. let loopsRemaining = options.maxLoopCount;
  82. while (!openSet.isEmpty() && loopsRemaining > 0) {
  83. // Get the closest item and mark it CLOSED
  84. const currentKey = openSet.pop();
  85. const currentPoint = points[currentKey];
  86. const currentParent = parents[currentKey];
  87. const currentCost = costs[currentKey];
  88. const isStartPoint = currentPoint.equals(startPoint);
  89. const isRouteBeginning = currentParent == null;
  90. let previousDirectionAngle;
  91. if (!isRouteBeginning) {
  92. previousDirectionAngle = util.getDirectionAngle(currentParent, currentPoint, numDirections, grid, options);
  93. }
  94. else if (!isPathBeginning) {
  95. // a vertex on the route
  96. previousDirectionAngle = previousRouteDirectionAngle;
  97. }
  98. else if (!isStartPoint) {
  99. // beginning of route on the path
  100. previousDirectionAngle = util.getDirectionAngle(startPoint, currentPoint, numDirections, grid, options);
  101. }
  102. else {
  103. previousDirectionAngle = null;
  104. }
  105. // Check if we reached any endpoint
  106. const skipEndCheck = isRouteBeginning && sameStartEndPoints;
  107. if (!skipEndCheck && endPointsKeys.indexOf(currentKey) >= 0) {
  108. options.previousDirectionAngle = previousDirectionAngle;
  109. return util.reconstructRoute(parents, points, currentPoint, startPoint, endPoint);
  110. }
  111. // Go over all possible directions and find neighbors
  112. for (let i = 0; i < numDirections; i += 1) {
  113. direction = directions[i];
  114. const directionAngle = direction.angle;
  115. directionChange = util.getDirectionChange(previousDirectionAngle, directionAngle);
  116. // Don't use the point changed rapidly.
  117. if (!(isPathBeginning && isStartPoint) &&
  118. directionChange > options.maxDirectionChange) {
  119. continue;
  120. }
  121. const neighborPoint = util.align(currentPoint
  122. .clone()
  123. .translate(direction.gridOffsetX || 0, direction.gridOffsetY || 0), grid, precision);
  124. const neighborKey = util.getKey(neighborPoint);
  125. // Closed points were already evaluated.
  126. if (openSet.isClose(neighborKey) || !map.isAccessible(neighborPoint)) {
  127. continue;
  128. }
  129. // Neighbor is an end point.
  130. if (endPointsKeys.indexOf(neighborKey) >= 0) {
  131. const isEndPoint = neighborPoint.equals(endPoint);
  132. if (!isEndPoint) {
  133. const endDirectionAngle = util.getDirectionAngle(neighborPoint, endPoint, numDirections, grid, options);
  134. const endDirectionChange = util.getDirectionChange(directionAngle, endDirectionAngle);
  135. if (endDirectionChange > options.maxDirectionChange) {
  136. continue;
  137. }
  138. }
  139. }
  140. // The current direction is ok.
  141. // ----------------------------
  142. const neighborCost = direction.cost;
  143. const neighborPenalty = isStartPoint
  144. ? 0
  145. : options.penalties[directionChange];
  146. const costFromStart = currentCost + neighborCost + neighborPenalty;
  147. // Neighbor point has not been processed yet or the cost of
  148. // the path from start is lower than previously calculated.
  149. if (!openSet.isOpen(neighborKey) ||
  150. costFromStart < costs[neighborKey]) {
  151. points[neighborKey] = neighborPoint;
  152. parents[neighborKey] = currentPoint;
  153. costs[neighborKey] = costFromStart;
  154. openSet.add(neighborKey, costFromStart + util.getCost(neighborPoint, endPoints));
  155. }
  156. }
  157. loopsRemaining -= 1;
  158. }
  159. }
  160. if (options.fallbackRoute) {
  161. return FunctionExt.call(options.fallbackRoute, this, startPoint, endPoint, options);
  162. }
  163. return null;
  164. }
  165. function snap(vertices, gridSize = 10) {
  166. if (vertices.length <= 1) {
  167. return vertices;
  168. }
  169. for (let i = 0, len = vertices.length; i < len - 1; i += 1) {
  170. const first = vertices[i];
  171. const second = vertices[i + 1];
  172. if (first.x === second.x) {
  173. const x = gridSize * Math.round(first.x / gridSize);
  174. if (first.x !== x) {
  175. first.x = x;
  176. second.x = x;
  177. }
  178. }
  179. else if (first.y === second.y) {
  180. const y = gridSize * Math.round(first.y / gridSize);
  181. if (first.y !== y) {
  182. first.y = y;
  183. second.y = y;
  184. }
  185. }
  186. }
  187. return vertices;
  188. }
  189. export const router = function (vertices, optionsRaw, edgeView) {
  190. const options = resolveOptions(optionsRaw);
  191. const sourceBBox = util.getSourceBBox(edgeView, options);
  192. const targetBBox = util.getTargetBBox(edgeView, options);
  193. const sourceEndpoint = util.getSourceEndpoint(edgeView, options);
  194. // pathfinding
  195. const map = new ObstacleMap(options).build(edgeView.graph.model, edgeView.cell);
  196. const oldVertices = vertices.map((p) => Point.create(p));
  197. const newVertices = [];
  198. // The origin of first route's grid, does not need snapping
  199. let tailPoint = sourceEndpoint;
  200. let from;
  201. let to;
  202. for (let i = 0, len = oldVertices.length; i <= len; i += 1) {
  203. let partialRoute = null;
  204. from = to || sourceBBox;
  205. to = oldVertices[i];
  206. // This is the last iteration
  207. if (to == null) {
  208. to = targetBBox;
  209. // If the target is a point, we should use dragging route
  210. // instead of main routing method if it has been provided.
  211. const edge = edgeView.cell;
  212. const isEndingAtPoint = edge.getSourceCellId() == null || edge.getTargetCellId() == null;
  213. if (isEndingAtPoint && typeof options.draggingRouter === 'function') {
  214. const dragFrom = from === sourceBBox ? sourceEndpoint : from;
  215. const dragTo = to.getOrigin();
  216. partialRoute = FunctionExt.call(options.draggingRouter, edgeView, dragFrom, dragTo, options);
  217. }
  218. }
  219. // Find the partial route
  220. if (partialRoute == null) {
  221. partialRoute = findRoute(edgeView, from, to, map, options);
  222. }
  223. // Cannot found the partial route.
  224. if (partialRoute === null) {
  225. // eslint-next-line
  226. console.warn(`Unable to execute manhattan algorithm, use orth instead`);
  227. return FunctionExt.call(options.fallbackRouter, this, vertices, options, edgeView);
  228. }
  229. // Remove the first point if the previous partial route has
  230. // the same point as last.
  231. const leadPoint = partialRoute[0];
  232. if (leadPoint && leadPoint.equals(tailPoint)) {
  233. partialRoute.shift();
  234. }
  235. // Save tailPoint for next iteration
  236. tailPoint = partialRoute[partialRoute.length - 1] || tailPoint;
  237. newVertices.push(...partialRoute);
  238. }
  239. if (options.snapToGrid) {
  240. return snap(newVertices, edgeView.graph.grid.getGridSize());
  241. }
  242. return newVertices;
  243. };
  244. //# sourceMappingURL=router.js.map