diff --git a/core/src/main/java/apoc/algo/PathFinding.java b/core/src/main/java/apoc/algo/PathFinding.java index 7e187e0ff0..21adf48b1c 100644 --- a/core/src/main/java/apoc/algo/PathFinding.java +++ b/core/src/main/java/apoc/algo/PathFinding.java @@ -74,24 +74,6 @@ private double[] getCoordinates(Node node) { @Context public Transaction tx; - @Procedure - @Description("apoc.algo.aStarWithPoint(startNode, endNode, 'relTypesAndDirs', 'distance','pointProp') - " + - "equivalent to apoc.algo.aStar but accept a Point type as a pointProperty instead of Number types as latitude and longitude properties") - public Stream aStarWithPoint( - @Name("startNode") Node startNode, - @Name("endNode") Node endNode, - @Name("relationshipTypesAndDirections") String relTypesAndDirs, - @Name("weightPropertyName") String weightPropertyName, - @Name("pointPropertyName") String pointPropertyName) { - - PathFinder algo = GraphAlgoFactory.aStar( - new BasicEvaluationContext(tx, db), - buildPathExpander(relTypesAndDirs), - CommonEvaluators.doubleCostEvaluator(weightPropertyName), - new GeoEstimateEvaluatorPointCustom(pointPropertyName)); - return WeightedPathResult.streamWeightedPathResult(startNode, endNode, algo); - } - @Procedure @Description("apoc.algo.aStar(startNode, endNode, 'KNOWS|', 'distance','lat','lon') " + "YIELD path, weight - run A* with relationship property name as cost function") @@ -198,7 +180,7 @@ public Stream dijkstraWithDefaultWeight( return WeightedPathResult.streamWeightedPathResult(startNode, endNode, algo); } - private PathExpander buildPathExpander(String relationshipsAndDirections) { + public static PathExpander buildPathExpander(String relationshipsAndDirections) { PathExpanderBuilder builder = PathExpanderBuilder.empty(); for (Pair pair : RelationshipTypeAndDirections .parse(relationshipsAndDirections)) { diff --git a/core/src/test/java/apoc/algo/PathFindingTest.java b/core/src/test/java/apoc/algo/PathFindingTest.java index fb45f21ce3..492b0b8c81 100644 --- a/core/src/test/java/apoc/algo/PathFindingTest.java +++ b/core/src/test/java/apoc/algo/PathFindingTest.java @@ -15,6 +15,8 @@ import java.util.List; import java.util.Map; +import static apoc.algo.AlgoUtil.SETUP_GEO; +import static apoc.algo.AlgoUtil.assertAStarResult; import static apoc.util.TestUtil.testCall; import static apoc.util.TestUtil.testResult; import static apoc.util.Util.map; @@ -46,15 +48,6 @@ public class PathFindingTest { "(b)-[:ROAD {d:20}]->(c), " + "(c)-[:ROAD {d:30}]->(d), " + "(a)-[:ROAD {d:20}]->(c) "; - private static final String SETUP_GEO = "CREATE (b:City {name:'Berlin', coords: point({latitude:52.52464,longitude:13.40514}), lat:52.52464,lon:13.40514})\n" + - "CREATE (m:City {name:'München', coords: point({latitude:48.1374,longitude:11.5755, height: 1}), lat:48.1374,lon:11.5755})\n" + - "CREATE (f:City {name:'Frankfurt',coords: point({latitude:50.1167,longitude:8.68333, height: 1}), lat:50.1167,lon:8.68333})\n" + - "CREATE (h:City {name:'Hamburg', coords: point({latitude:53.554423,longitude:9.994583, height: 1}), lat:53.554423,lon:9.994583})\n" + - "CREATE (b)-[:DIRECT {dist:255.64*1000}]->(h)\n" + - "CREATE (b)-[:DIRECT {dist:504.47*1000}]->(m)\n" + - "CREATE (b)-[:DIRECT {dist:424.12*1000}]->(f)\n" + - "CREATE (f)-[:DIRECT {dist:304.28*1000}]->(m)\n" + - "CREATE (f)-[:DIRECT {dist:393.15*1000}]->(h)"; @Rule public DbmsRule db = new ImpermanentDbmsRule(); @@ -76,17 +69,6 @@ public void testAStar() { ); } - @Test - public void testAStarWithPoint() { - db.executeTransactionally(SETUP_GEO); - testResult(db, - "MATCH (from:City {name:'München'}), (to:City {name:'Hamburg'}) " + - "CALL apoc.algo.aStarWithPoint(from, to, 'DIRECT', 'dist', 'coords') yield path, weight " + - "RETURN path, weight" , - this::assertAStarResult - ); - } - @Test public void testAStarConfig() { db.executeTransactionally(SETUP_GEO); @@ -105,23 +87,10 @@ public void testAStarConfigWithPoint() { "MATCH (from:City {name:'München'}), (to:City {name:'Hamburg'}) " + "CALL apoc.algo.aStarConfig(from, to, 'DIRECT', {pointPropName:'coords', weight:'dist', default:100}) yield path, weight " + "RETURN path, weight" , - this::assertAStarResult + AlgoUtil::assertAStarResult ); } - private void assertAStarResult(Result r) { - assertEquals(true, r.hasNext()); - Map row = r.next(); - assertEquals(697, ((Number)row.get("weight")).intValue()/1000) ; - Path path = (Path) row.get("path"); - assertEquals(2, path.length()) ; // 3nodes, 2 rels - List nodes = Iterables.asList(path.nodes()); - assertEquals("München", nodes.get(0).getProperty("name")) ; - assertEquals("Frankfurt", nodes.get(1).getProperty("name")) ; - assertEquals("Hamburg", nodes.get(2).getProperty("name")) ; - assertEquals(false,r.hasNext()); - } - @Test public void testDijkstra() { db.executeTransactionally(SETUP_SIMPLE); diff --git a/docs/asciidoc/modules/ROOT/pages/algorithms/path-finding-procedures.adoc b/docs/asciidoc/modules/ROOT/pages/algorithms/path-finding-procedures.adoc index 9a1eac053a..80e43a3534 100644 --- a/docs/asciidoc/modules/ROOT/pages/algorithms/path-finding-procedures.adoc +++ b/docs/asciidoc/modules/ROOT/pages/algorithms/path-finding-procedures.adoc @@ -11,7 +11,7 @@ APOC exposes some built in path-finding functions that Neo4j brings along. | apoc.algo.dijkstra(startNode, endNode, 'KNOWS\|', 'distance') YIELD path, weight | run dijkstra with relationship property name as cost function | apoc.algo.dijkstraWithDefaultWeight(startNode, endNode, 'KNOWS\|', 'distance', 10) YIELD path, weight | run dijkstra with relationship property name as cost function and a default weight if the property does not exist | apoc.algo.aStar(startNode, endNode, 'KNOWS\|', 'distance','lat','lon') YIELD path, weight | run A* with relationship property name as cost function -| apoc.algo.aStarWithPoint(startNode, endNode, 'relTypesAndDirs', 'weightPropertyName','pointPropertyName') - equivalent to apoc.algo.aStar but accept a Point type as a pointProperty instead of Number types as latitude and longitude properties +| label:apoc-full[] apoc.algo.aStarWithPoint(startNode, endNode, 'relTypesAndDirs', 'weightPropertyName','pointPropertyName') - equivalent to apoc.algo.aStar but accept a Point type as a pointProperty instead of Number types as latitude and longitude properties | apoc.algo.aStarConfig(startNode, endNode, 'KNOWS|', {weight:'dist',default:10, x:'lon',y:'lat',pointPropName:'point'}) YIELD path, weight - run A* with relationship property name as cost function | apoc.algo.allSimplePaths(startNode, endNode, 'KNOWS\|', 5) YIELD path, weight | run allSimplePaths with relationships given and maxNodes | apoc.stats.degrees(relTypesDirections) yield type, direction, total, min, max, mean, p50, p75, p90, p95, p99, p999 | compute degree distribution in parallel diff --git a/full/src/main/java/apoc/algo/PathFindingFull.java b/full/src/main/java/apoc/algo/PathFindingFull.java new file mode 100644 index 0000000000..da109b3f37 --- /dev/null +++ b/full/src/main/java/apoc/algo/PathFindingFull.java @@ -0,0 +1,47 @@ +package apoc.algo; + +import apoc.result.WeightedPathResult; +import org.neo4j.graphalgo.BasicEvaluationContext; +import org.neo4j.graphalgo.CommonEvaluators; +import org.neo4j.graphalgo.GraphAlgoFactory; +import org.neo4j.graphalgo.PathFinder; +import org.neo4j.graphalgo.WeightedPath; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.procedure.Context; +import org.neo4j.procedure.Description; +import org.neo4j.procedure.Name; +import org.neo4j.procedure.Procedure; + +import java.util.stream.Stream; + +import static apoc.algo.PathFinding.buildPathExpander; + +public class PathFindingFull { + + @Context + public GraphDatabaseService db; + + @Context + public Transaction tx; + + @Procedure + @Description("apoc.algo.aStarWithPoint(startNode, endNode, 'relTypesAndDirs', 'distance','pointProp') - " + + "equivalent to apoc.algo.aStar but accept a Point type as a pointProperty instead of Number types as latitude and longitude properties") + public Stream aStarWithPoint( + @Name("startNode") Node startNode, + @Name("endNode") Node endNode, + @Name("relationshipTypesAndDirections") String relTypesAndDirs, + @Name("weightPropertyName") String weightPropertyName, + @Name("pointPropertyName") String pointPropertyName) { + + PathFinder algo = GraphAlgoFactory.aStar( + new BasicEvaluationContext(tx, db), + buildPathExpander(relTypesAndDirs), + CommonEvaluators.doubleCostEvaluator(weightPropertyName), + new PathFinding.GeoEstimateEvaluatorPointCustom(pointPropertyName)); + return WeightedPathResult.streamWeightedPathResult(startNode, endNode, algo); + } + +} diff --git a/full/src/test/java/apoc/algo/PathFindingFullTest.java b/full/src/test/java/apoc/algo/PathFindingFullTest.java new file mode 100644 index 0000000000..8eeed5a985 --- /dev/null +++ b/full/src/test/java/apoc/algo/PathFindingFullTest.java @@ -0,0 +1,34 @@ +package apoc.algo; + +import apoc.util.TestUtil; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.neo4j.test.rule.DbmsRule; +import org.neo4j.test.rule.ImpermanentDbmsRule; + +import static apoc.algo.AlgoUtil.SETUP_GEO; +import static apoc.util.TestUtil.testResult; + +public class PathFindingFullTest { + + @ClassRule + public static DbmsRule db = new ImpermanentDbmsRule(); + + + @BeforeClass + public static void setUp() throws Exception { + TestUtil.registerProcedure(db, PathFindingFull.class); + } + + @Test + public void testAStarWithPoint() { + db.executeTransactionally(SETUP_GEO); + testResult(db, + "MATCH (from:City {name:'München'}), (to:City {name:'Hamburg'}) " + + "CALL apoc.algo.aStarWithPoint(from, to, 'DIRECT', 'dist', 'coords') yield path, weight " + + "RETURN path, weight" , + AlgoUtil::assertAStarResult + ); + } +} diff --git a/test-utils/src/main/java/apoc/algo/AlgoUtil.java b/test-utils/src/main/java/apoc/algo/AlgoUtil.java new file mode 100644 index 0000000000..9669929ffa --- /dev/null +++ b/test-utils/src/main/java/apoc/algo/AlgoUtil.java @@ -0,0 +1,37 @@ +package apoc.algo; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; +import org.neo4j.graphdb.Result; +import org.neo4j.internal.helpers.collection.Iterables; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class AlgoUtil { + public static final String SETUP_GEO = "CREATE (b:City {name:'Berlin', coords: point({latitude:52.52464,longitude:13.40514}), lat:52.52464,lon:13.40514})\n" + + "CREATE (m:City {name:'München', coords: point({latitude:48.1374,longitude:11.5755, height: 1}), lat:48.1374,lon:11.5755})\n" + + "CREATE (f:City {name:'Frankfurt',coords: point({latitude:50.1167,longitude:8.68333, height: 1}), lat:50.1167,lon:8.68333})\n" + + "CREATE (h:City {name:'Hamburg', coords: point({latitude:53.554423,longitude:9.994583, height: 1}), lat:53.554423,lon:9.994583})\n" + + "CREATE (b)-[:DIRECT {dist:255.64*1000}]->(h)\n" + + "CREATE (b)-[:DIRECT {dist:504.47*1000}]->(m)\n" + + "CREATE (b)-[:DIRECT {dist:424.12*1000}]->(f)\n" + + "CREATE (f)-[:DIRECT {dist:304.28*1000}]->(m)\n" + + "CREATE (f)-[:DIRECT {dist:393.15*1000}]->(h)"; + + + public static void assertAStarResult(Result r) { + assertEquals(true, r.hasNext()); + Map row = r.next(); + assertEquals(697, ((Number)row.get("weight")).intValue()/1000) ; + Path path = (Path) row.get("path"); + assertEquals(2, path.length()) ; // 3nodes, 2 rels + List nodes = Iterables.asList(path.nodes()); + assertEquals("München", nodes.get(0).getProperty("name")) ; + assertEquals("Frankfurt", nodes.get(1).getProperty("name")) ; + assertEquals("Hamburg", nodes.get(2).getProperty("name")) ; + assertEquals(false,r.hasNext()); + } +}