diff --git a/java/bundles/org.eclipse.set.application.test/src/org/eclipse/set/application/graph/TopologicalGraphServiceTest.java b/java/bundles/org.eclipse.set.application.test/src/org/eclipse/set/application/graph/TopologicalGraphServiceTest.java index bc08f684f0..b34ada8c3a 100644 --- a/java/bundles/org.eclipse.set.application.test/src/org/eclipse/set/application/graph/TopologicalGraphServiceTest.java +++ b/java/bundles/org.eclipse.set.application.test/src/org/eclipse/set/application/graph/TopologicalGraphServiceTest.java @@ -24,6 +24,7 @@ import org.eclipse.set.application.cacheservice.CacheServiceImpl; import org.eclipse.set.basis.constants.ContainerType; import org.eclipse.set.basis.files.ToolboxFileRole; +import org.eclipse.set.basis.graph.TopPath; import org.eclipse.set.basis.graph.TopPoint; import org.eclipse.set.core.services.Services; import org.eclipse.set.core.services.cache.CacheService; @@ -127,7 +128,40 @@ void testFindShortedDistanceInDirection() throws Exception { true); // It given't relevant path from 60AA -> 60P1 assertFalse(testDistance3.isPresent()); + } + } + @Test + void testFindShortesRelevantPath() throws Exception { + try (MockedStatic mockServices = Mockito + .mockStatic(Services.class)) { + setupTest(mockServices); + // 60L107X -> 60L106X + // it give no relevant path from 60L107X to 60L106X + final Optional testPath1 = testee + .findTopologicalShortesPath( + getTopPoint("2B347074-8B80-48F8-960D-DF2F8DC0244F"), + getTopPoint("78BB7C0E-A90D-4A6E-9F60-0A855A010B6B") // + ); + + assertTrue(testPath1.isEmpty()); + + // 60L107X -> 60L123Y + final Optional testPath2 = testee + .findTopologicalShortesPath( + getTopPoint("2B347074-8B80-48F8-960D-DF2F8DC0244F"), + getTopPoint("3941E450-42FE-4F9A-9370-262C50E2BC9F") // + ); + + assertTrue(testPath2.isPresent()); + + final Optional shortestDistance = testee + .findShortestDistance( + getTopPoint("2B347074-8B80-48F8-960D-DF2F8DC0244F"), + getTopPoint("3941E450-42FE-4F9A-9370-262C50E2BC9F") // + ); + + assertEquals(shortestDistance.get(), testPath2.get().length()); } } diff --git a/java/bundles/org.eclipse.set.application/src/org/eclipse/set/application/graph/TopologicalGraphServiceImpl.java b/java/bundles/org.eclipse.set.application/src/org/eclipse/set/application/graph/TopologicalGraphServiceImpl.java index c160413d09..30f9cbc8d4 100644 --- a/java/bundles/org.eclipse.set.application/src/org/eclipse/set/application/graph/TopologicalGraphServiceImpl.java +++ b/java/bundles/org.eclipse.set.application/src/org/eclipse/set/application/graph/TopologicalGraphServiceImpl.java @@ -43,6 +43,8 @@ import org.eclipse.set.utils.graph.AsSplitTopGraph.Node; import org.jgrapht.GraphPath; import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.alg.shortestpath.PathValidator; +import org.jgrapht.alg.shortestpath.YenKShortestPath; import org.jgrapht.graph.WeightedPseudograph; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -64,6 +66,11 @@ public class TopologicalGraphServiceImpl // Tolerance value by find path with topological direction private final int TOLERANT_DISTANCE_TO_FIND_DIRECTION_PATH = 500; + private record InputData(AsSplitTopGraph graphView, Node fromNode, + Node toNode) { + + } + @Reference EventAdmin eventAdmin; @Reference @@ -164,6 +171,43 @@ public TopPath findPathBetween(final TopPoint from, final TopPoint to, from, to, limit, condition); } + @Override + public Optional findTopologicalShortesPath(final TopPoint from, + final TopPoint to) { + if (from.equalLocation(to)) { + return Optional.of( + new TopPath(List.of(from.edge()), BigDecimal.ZERO, from)); + } + final PathValidator validator = getTopologicalPathValidator(); + + final InputData inputData = getInputData(from, to, Optional.empty()); + + final YenKShortestPath yenKShortestPath = new YenKShortestPath<>( + inputData.graphView, validator); + final List> paths = yenKShortestPath + .getPaths(inputData.fromNode, inputData.toNode, 1); + return paths.stream() + .map(p -> new TopPath(p.getEdgeList() + .stream() + .map(Edge::edge) + .distinct() + .toList(), getPathWeight(p), from)) + .findFirst(); + + } + + private static PathValidator getTopologicalPathValidator() { + final PathValidator validator = ( + path, edge) -> { + if (path.getEdgeList().isEmpty()) { + return true; + } + return TopKanteExtensions.isRoute(edge.edge(), + path.getEdgeList().getLast().edge()); + }; + return validator; + } + @Override public Optional findShortestDistanceInDirection( final TopPoint from, final TopPoint to, @@ -179,18 +223,10 @@ public Optional findShortestPath(final TopPoint from, return Optional.of( new TopPath(List.of(from.edge()), BigDecimal.ZERO, from)); } - final MultiContainer_AttributeGroup container = getContainer( - from.edge()); - final PlanPro_Schnittstelle planProSchnittstelle = getPlanProSchnittstelle( - container); - final AsSplitTopGraph graphView = new AsSplitTopGraph( - getTopGraphBase(planProSchnittstelle)); - - final Node fromNode = graphView.splitGraphAt(from); - final Node toNode = graphView.splitGraphAt(to); - + final InputData inputData = getInputData(from, to, Optional.empty()); return Optional.ofNullable( // - findPathBetween(graphView, fromNode, toNode)) + findPathBetween(inputData.graphView, inputData.fromNode, + inputData.toNode)) .map(p -> new TopPath(p.getEdgeList() .stream() .map(Edge::edge) @@ -209,15 +245,8 @@ public Optional findShortestPathInDirection(final TopPoint from, distance.abs(), distance.abs())) : Optional.empty(); } - final MultiContainer_AttributeGroup container = getContainer( - from.edge()); - final PlanPro_Schnittstelle planProSchnittstelle = getPlanProSchnittstelle( - container); - final AsSplitTopGraph graphView = new AsSplitTopGraph( - getTopGraphBase(planProSchnittstelle)); - final Node fromNode = graphView.splitGraphAt(from, - Boolean.valueOf(inTopDirection)); - final Node toNode = graphView.splitGraphAt(to); + final InputData inputData = getInputData(from, to, + Optional.of(Boolean.valueOf(inTopDirection))); final Optional shortestDistance = findShortestDistance(from, to); @@ -226,8 +255,10 @@ public Optional findShortestPathInDirection(final TopPoint from, } final TopPath path = AsDirectedTopGraph - .getPath(AsDirectedTopGraph.asDirectedTopGraph(graphView), - fromNode, toNode, + .getPath( + AsDirectedTopGraph + .asDirectedTopGraph(inputData.graphView), + inputData.fromNode, inputData.toNode, shortestDistance.get().intValue() + TOLERANT_DISTANCE_TO_FIND_DIRECTION_PATH, topPath -> { @@ -254,7 +285,6 @@ private static GraphPath findPathBet try { return DijkstraShortestPath.findPathBetween(graphView, fromNode, toNode); - } catch (final IllegalArgumentException ex) { if (ex.getMessage().equals("Negative edge weight not allowed")) { //$NON-NLS-1$ throw new IllegalArgumentException("Invalid spot location", ex); //$NON-NLS-1$ @@ -280,26 +310,20 @@ public Optional findShortestDistance(final TopPoint from, @Override public Optional findClosestPoint(final TopPoint from, final List points, final boolean searchInTopDirection) { - final MultiContainer_AttributeGroup container = getContainer( - from.edge()); - final PlanPro_Schnittstelle planProSchnittstelle = getPlanProSchnittstelle( - container); - final AsSplitTopGraph graphView = new AsSplitTopGraph( - getTopGraphBase(planProSchnittstelle)); - final Node fromNode = graphView.splitGraphAt(from, - Boolean.valueOf(searchInTopDirection)); + final InputData inputData = getInputData(from, null, + Optional.of(Boolean.valueOf(searchInTopDirection))); BigDecimal minWeight = BigDecimal.valueOf(1000000); Optional minPoint = Optional.empty(); for (final TopPoint point : points) { Node toNode = null; try { - toNode = graphView.splitGraphAt(point); + toNode = inputData.graphView.splitGraphAt(point); } catch (final IllegalArgumentException e) { continue; } - final GraphPath path = findPathBetween(graphView, - fromNode, toNode); + final GraphPath path = findPathBetween( + inputData.graphView, inputData.fromNode, toNode); if (path == null) { continue; } @@ -313,4 +337,22 @@ public Optional findClosestPoint(final TopPoint from, return minPoint; } + private InputData getInputData(final TopPoint from, final TopPoint to, + final Optional inTopDirection) { + final MultiContainer_AttributeGroup container = getContainer( + from.edge()); + final PlanPro_Schnittstelle planProSchnittstelle = getPlanProSchnittstelle( + container); + final AsSplitTopGraph graphView = new AsSplitTopGraph( + getTopGraphBase(planProSchnittstelle)); + + final Node fromNode = inTopDirection.isPresent() + ? graphView.splitGraphAt(from, inTopDirection.get()) + : graphView.splitGraphAt(from); + if (to == null) { + return new InputData(graphView, fromNode, null); + } + final Node toNode = graphView.splitGraphAt(to); + return new InputData(graphView, fromNode, toNode); + } } diff --git a/java/bundles/org.eclipse.set.core.services/src/org/eclipse/set/core/services/graph/TopologicalGraphService.java b/java/bundles/org.eclipse.set.core.services/src/org/eclipse/set/core/services/graph/TopologicalGraphService.java index 010d761eec..8f63cad211 100644 --- a/java/bundles/org.eclipse.set.core.services/src/org/eclipse/set/core/services/graph/TopologicalGraphService.java +++ b/java/bundles/org.eclipse.set.core.services/src/org/eclipse/set/core/services/graph/TopologicalGraphService.java @@ -112,4 +112,15 @@ Optional findShortestPathInDirection(TopPoint from, TopPoint to, */ Optional findClosestPoint(final TopPoint from, final List points, final boolean searchInTopDirection); + + /** + * Finds the shortest path, which have plausible branch + * + * @param from + * starting point to search from + * @param to + * end point + * @return the optional of {@link TopPath} + */ + Optional findTopologicalShortesPath(TopPoint from, TopPoint to); } diff --git a/java/bundles/org.eclipse.set.feature.table.pt1/src/org/eclipse/set/feature/table/pt1/sszs/SszsTransformator.xtend b/java/bundles/org.eclipse.set.feature.table.pt1/src/org/eclipse/set/feature/table/pt1/sszs/SszsTransformator.xtend index 715c1ff50e..8d6db59db9 100644 --- a/java/bundles/org.eclipse.set.feature.table.pt1/src/org/eclipse/set/feature/table/pt1/sszs/SszsTransformator.xtend +++ b/java/bundles/org.eclipse.set.feature.table.pt1/src/org/eclipse/set/feature/table/pt1/sszs/SszsTransformator.xtend @@ -807,11 +807,12 @@ class SszsTransformator extends AbstractPlanPro2TableModelTransformator { val signal = etcsSignal.IDSignal?.value if (signal !== null && po !== null) { val signalTopPoint = new TopPoint(signal) - val topPoint = new TopPoint(po) - val distance = topGraphService.findShortestDistance(signalTopPoint, - topPoint) - if (distance.present) { - return distance.get + val distances = po.singlePoints.map [ + topGraphService.findTopologicalShortesPath(signalTopPoint, + new TopPoint(it)) + ].filter[isPresent].map[get.length] + if (!distances.nullOrEmpty) { + return distances.min } } diff --git a/java/bundles/org.eclipse.set.ppmodel.extensions/src/org/eclipse/set/ppmodel/extensions/geometry/GEOKanteGeometryExtensions.java b/java/bundles/org.eclipse.set.ppmodel.extensions/src/org/eclipse/set/ppmodel/extensions/geometry/GEOKanteGeometryExtensions.java index 9100b29187..288a923827 100644 --- a/java/bundles/org.eclipse.set.ppmodel.extensions/src/org/eclipse/set/ppmodel/extensions/geometry/GEOKanteGeometryExtensions.java +++ b/java/bundles/org.eclipse.set.ppmodel.extensions/src/org/eclipse/set/ppmodel/extensions/geometry/GEOKanteGeometryExtensions.java @@ -216,27 +216,36 @@ private static LineString blosscurve(final GEO_Kante edge, .getWert() .doubleValue(); if (radiusA != 0 && radiusB != 0) { - // Bloss curve connecting two straight tracks - logger.warn("Form Bloss between straight tracks not supported."); //$NON-NLS-1$ - return getGeometryFactory().createLineString(getCoordinates(edge)); + // Bloss curve connecting two curve tracks + // logger.warn("Form Bloss between straight tracks not supported."); + // //$NON-NLS-1$ + final Bloss bloss = new Bloss(radiusA, radiusB, length, + geometryOptions.precision()); + + return blosscurve(bloss, coordinateA, coordinateB, radiusA, length, + geometryOptions); } else if (radiusA == 0) { // Curve from node B to node A - return blosscurve(coordinateB, coordinateA, radiusB, length, + final Bloss bloss = new Bloss(Math.abs(radiusB), length, + geometryOptions.precision()); + return blosscurve(bloss, coordinateB, coordinateA, radiusB, length, geometryOptions); } // Curve from node A to node B // Invert the radius, as left-right is also inverted due to the // drawing direction - return blosscurve(coordinateA, coordinateB, -radiusA, length, + final Bloss bloss = new Bloss(Math.abs(radiusA), length, + geometryOptions.precision()); + return blosscurve(bloss, coordinateA, coordinateB, -radiusA, length, geometryOptions); } - private static LineString blosscurve(final Coordinate fromCoordinate, - final Coordinate toCoordinate, final double radius, - final double length, final GeometryOptions geometryOptions) { - final Bloss bloss = new Bloss(Math.abs(radius), length, - geometryOptions.precision()); + private static LineString blosscurve(final Bloss bloss, + final Coordinate fromCoordinate, final Coordinate toCoordinate, + final double radiusA, final double length, + final GeometryOptions geometryOptions) { + final int segmentCount = (int) Math.max( length / geometryOptions.stepSize(), geometryOptions.minSegmentCount()); @@ -245,7 +254,7 @@ private static LineString blosscurve(final Coordinate fromCoordinate, .map(coor -> { final Coordinate coordinate = new Coordinate(coor[0], coor[1], 0); - return radius < 0 ? mirrorY(coordinate) : coordinate; + return radiusA < 0 ? mirrorY(coordinate) : coordinate; }) .toList(); diff --git a/java/bundles/org.eclipse.set.utils/src/org/eclipse/set/utils/math/Bloss.java b/java/bundles/org.eclipse.set.utils/src/org/eclipse/set/utils/math/Bloss.java index e019edce00..b4b20ad6ff 100644 --- a/java/bundles/org.eclipse.set.utils/src/org/eclipse/set/utils/math/Bloss.java +++ b/java/bundles/org.eclipse.set.utils/src/org/eclipse/set/utils/math/Bloss.java @@ -11,8 +11,6 @@ import java.util.ArrayList; import java.util.List; -import com.google.common.math.BigIntegerMath; - /** * Determines points on a Bloss curve with a zero target curvature * @@ -23,38 +21,47 @@ * */ public class Bloss { - private final double[][] amnCache; - private final double curvature; + private final double startCurvature; + private final double endCurvature; + + private final double firstLength; private final int maxIterations; + private final boolean isInflectionCurve; + private final double totalLength; /** + * 7 point closed Newton-Cotes rule + */ + private static final double[] NC_WEIGHTS = { 41, 216, 27, 272, 27, 216, + 41 }; + + /** + * Simple bloss curve from RadiusA to a straight line. + * * @param radius * radius of the curve - * @param totalLength + * @param arcLength * total length of the curve * @param iterations * number of iterations to perform during calculation */ - public Bloss(final double radius, final double totalLength, + public Bloss(final double radius, final double arcLength, final int iterations) { - this.totalLength = totalLength; - this.maxIterations = iterations; - this.curvature = radius != 0 ? 1 / radius : 0; - - // Prepare the cache for a_m,n - // n is at most 4m - final int nMax = 4 * iterations; - amnCache = new double[iterations][nMax]; - for (int i = 0; i < iterations; ++i) { - amnCache[i] = new double[nMax]; - for (int j = 0; j < nMax; ++j) { - // Set all values to NaN - amnCache[i][j] = Double.NaN; - } - } + this(radius, 0, arcLength, iterations); + } + public Bloss(final double radiusA, final double radiusB, + final double arcLength, final int iterations) { + this.totalLength = arcLength; + this.maxIterations = iterations; + startCurvature = radiusA == 0 ? 0 : 1 / radiusA; + endCurvature = radiusB == 0 ? 0 : 1 / radiusB; + isInflectionCurve = startCurvature * endCurvature < 0 && arcLength > 0; + firstLength = isInflectionCurve + ? arcLength * startCurvature / (startCurvature - endCurvature) + : arcLength; } /** @@ -82,121 +89,89 @@ public List calculate(final int segmentCount) { * @return a xy-position on the curve */ public double[] calculatePoint(final double length) { - return new double[] { x(length), y(length) }; - } - - /** - * Calculates the term a_m,n using a cache - * - * @param m - * value for m - * @param n - * value for n - * @return a_m,n - */ - private double amn(final int m, final int n) { - // Check if the value is not yet present - // For the empty state we use Double.NaN and NaN != NaN - if (amnCache[m - 1][n - 1] != amnCache[m - 1][n - 1]) { - amnCache[m - 1][n - 1] = amnUncached(m, n); + if (isInflectionCurve && length > firstLength) { + final double[] firstPart = integrate(0, firstLength); + final double[] secondPart = integrate(firstLength, totalLength); + return new double[] { firstPart[0] + secondPart[0], + firstPart[1] + secondPart[1] }; } - return amnCache[m - 1][n - 1]; + return integrate(0, length); } /** - * Calculates the term a_m,n * - * @param m - * value for m - * @param n - * value for n - * @return a_m,n + * @param from + * @param to + * @return */ - private double amnUncached(final int m, final int n) { - if (m == 1) { - return an(n); - } - - double result = 0.0; - // First sum term - if (n == m || n == m + 1 || n == m + 2) { - - for (int j = 1; j <= n - m + 1; ++j) { - result += amn(m - 1, n - j) * an(j); - } + private double[] integrate(final double from, final double to) { + final double[] coor = new double[2]; + if (from == to) { + return coor; } - // Second sum term - else if (n >= m + 3 && n <= 4 * m - 3) { - for (int j = 1; j <= 4; ++j) { - result += amn(m - 1, n - j) * an(j); + final double factor = (to - from) / (840 * maxIterations); + final double delta = (to - from) / maxIterations; + final double h = delta / 6; + for (int i = 0; i < maxIterations; i++) { + final double x = from + i * delta; + for (int k = 0; k < NC_WEIGHTS.length; k++) { + final double angle = directionAngle(x + k * h); + coor[0] += NC_WEIGHTS[k] * Math.cos(angle); + coor[1] += NC_WEIGHTS[k] * Math.sin(angle); } } - // Third sum term - else if (n == 4 * m || n == 4 * m - 1 || n == 4 * m - 2) { - for (int j = n - 4 * m + 4; j <= 4; ++j) { - result += amn(m - 1, n - j) * an(j); - } - } else { - // This should be unreachable - throw new RuntimeException("Invalid value for n in amn"); //$NON-NLS-1$ - } - return result; + coor[0] *= factor; + coor[1] *= factor; + return coor; } /** - * Definition of a_n + * Calculate direction angle of the tangent, measured from the +N axis, + * clockwise, in radians. * - * @param n - * index - * @return a_n + * @param length + * Arc length along the curve, measured from the element start + * point. 0 ≤ l ≤ L. + * @return */ - private double an(final int n) { - if (n == 1) { - return curvature; - } - if (n == 2) { - return 0; - } - if (n == 3) { - return -curvature / Math.pow(totalLength, 2); + private double directionAngle(final double length) { + if (!isInflectionCurve) { + return startCurvature * length + (endCurvature - startCurvature) + * totalLength * hermitIntegral(length / totalLength); } - if (n == 4) { - return curvature / (2 * Math.pow(totalLength, 3)); - } - - // This should be unreachable - throw new RuntimeException("Invalid value for n in an"); //$NON-NLS-1$ - } - private double x(final double l) { - double result = l; - for (int m = 2; m <= maxIterations; m += 2) { - final double sign = Math.pow(-1, m / 2f); - final double fact = BigIntegerMath.factorial(m).doubleValue(); + if (length < firstLength) { + return startCurvature * firstLength + * rampIntegral(length / firstLength); + } - double innerSum = 0.0; - for (int n = m; n <= 4 * m; ++n) { - innerSum += amn(m, n) * Math.pow(l, n + 1f) / (n + 1); - } + final double secondLength = totalLength - firstLength; + final double t = (totalLength - length) / secondLength; + return 5 / 8 * startCurvature * firstLength + + endCurvature * secondLength * (5 / 8 * rampIntegral(t)); - result += sign / fact * innerSum; - } - return result; } - private double y(final double l) { - double result = 0.0; - for (int m = 1; m <= maxIterations; m += 2) { - final double sign = Math.pow(-1, (m - 1) / 2f); - final double fact = BigIntegerMath.factorial(m).doubleValue(); + /** + * Hermit Integral with s = segmentLength / totalLength + * + * @param s + * segmentLength / totalLength + * @return + */ + private static double hermitIntegral(final double s) { + return Math.pow(s, 3) - Math.pow(s, 4) / 2; + } - double innerSum = 0.0; - for (int n = m; n <= 4 * m; ++n) { - innerSum += amn(m, n) * Math.pow(l, n + 1f) / (n + 1); - } + /** + * Bloss ramp Integral with t = segmentLength / firstLength + * + * @param t + * segmentLength / firstLength + * @return + */ - result += sign / fact * innerSum; - } - return result; + private static double rampIntegral(final double t) { + return t - Math.pow(t, 3) / 2 + Math.pow(t, 4) / 8; } }