java – How can I calculate Shortest Path through Neo4j in my Spring Boot through localhost and Docker?

[*]

I have a problem about calculating the shortest path through Neo4j in my Spring Boot example.

After adding some cities with its route, I want to calculate their shortest path in terms of their connection and their duration. However, these two methods which defined in ShortestPathController cannot work.

Here are the issues shown below.

1 ) I get empty destinationCity as null value when I try to add a route

2) getShortestPath throws this kind of error message

Neo.ClientNotification.Statement.UnboundedVariableLengthPatternWarning: The provided pattern is unbounded, consider adding an upper limit to the number of node hops.
    MATCH p=shortestPath((a:City {name:$from})-[*]->(b:City {name:$to})) RETURN p
                                              ^
Using shortest path with an unbounded pattern will likely result in long execution times. It is recommended to use an upper limit to the number of node hops in your pattern.

3 ) getShortestPathInTime throws this kind of error message shown below.

org.neo4j.driver.exceptions.value.LossyCoercion: Cannot coerce FLOAT to Java int without losing precision

4) I cannot run all test methods in AppApplicationTests.java

5 ) I have no idea as I cannot run all test methods what if there are many cities and its route like this resource (Link)

How can I fix it?

Here is my GitHub repository : Project Link

Here is my Controller class

public class ShortestPathController {

    private final ShortestPathService shortestPathService;

    public ShortestPathController(ShortestPathService shortestPathService) {
        this.shortestPathService = shortestPathService;
    }

    /* Sample Output (Not working)
    {
      "arrivalCity": "A",
      "departureCity": "B",
      "totalConnections": 1
    }
     */
    @GetMapping("/shortest-path")
    public Mono<PathShortestConnectionResponse> getShortestPath(@RequestBody PathRequest pathRequest) {

        return shortestPathService.getShortestPath(pathRequest.getFrom(), pathRequest.getDestination())
                .map(PathShortestConnectionResponse::new)
                .switchIfEmpty(Mono.error(new IllegalArgumentException("Error")));
    }

    /* Sample Output (Not working)
    {
      "arrivalCity": "A",
      "departureCity": "B",
      "totalHours": 5
    }
     */
    @GetMapping("/shortest-path-in-time")
public Mono<PathShortestTimeResponse>  getShortestPathInTime(@RequestBody PathRequest pathRequest) {

    LOGGER.info("ShortestPathController | getShortestPathInTime is started");
    LOGGER.info("ShortestPathController | getShortestPathInTime | pathRequest from : " + pathRequest.getFrom());
    LOGGER.info("ShortestPathController | getShortestPathInTime | pathRequest destination : " + pathRequest.getDestination());


    return shortestPathService.getShortestPathInTime(pathRequest.getFrom(), pathRequest.getDestination())
            .map(PathShortestTimeResponse::new)
            .switchIfEmpty(Mono.error(new IllegalArgumentException("Error")));
}

    @ResponseStatus(
            value = HttpStatus.NOT_FOUND,
            reason = "Illegal arguments")
    @ExceptionHandler(IllegalArgumentException.class)
    public void illegalArgumentHandler() {

    }
}

Here is my Service class

public class ShortestPathServiceImpl implements ShortestPathService {

    private final ShortestPathRepository shortestPathRepository;

    @Override
    public Mono<PathShortestConnectionResponse> getShortestPath(String from, String to) {

        final Flux<PathValue> rows = shortestPathRepository.shortestPath(from, to);
        return rows
                .map(it -> this.convert(it.asPath()))
                .take(1)
                .next()
                .switchIfEmpty(Mono.empty());

    }

    @Override
    public Mono<PathShortestTimeResponse> getShortestPathInTime(String from, String to) {

        final Flux<PathValue> rows = shortestPathRepository.shortestPathInTime(from, to);
        return rows
                .map(it -> this.convertTimePath(it.asPath()))
                .take(1)
                .next()
                .switchIfEmpty(Mono.empty());
    }


    private PathShortestConnectionResponse convert(org.neo4j.driver.types.Path connection) {

        String departureCity = connection.start().get("name").asString();
        String arriveCity = connection.end().get("name").asString();
        int length = connection.length();

        return new PathShortestConnectionResponse(departureCity, arriveCity, length);
    }



private PathShortestTimeResponse convertTimePath(org.neo4j.driver.types.Path connection) {

    String departureCity = connection.start().get("name").asString();
    String arriveCity = connection.end().get("name").asString();
    Double totalInTime = StreamSupport.stream(connection.nodes().spliterator(), false)
            .filter(node -> node.hasLabel("Route"))
            .mapToDouble(route -> route.get("duration").asDouble()).sum();

    return new PathShortestTimeResponse(departureCity, arriveCity, totalInTime);
}

Here is my Repository class

public interface ShortestPathRepository extends ReactiveNeo4jRepository<City, UUID> {

    @Query("MATCH p=shortestPath((a:City {name:$from})-[*]->(b:City {name:$to})) RETURN p")
    Flux<PathValue> shortestPath(@Param("from") String from, @Param("to") String to);

    @Query("MATCH (a:City {name: $from})n"
            + "MATCH (b:City {name: $to})n"
            + "CALL apoc.algo.dijkstra(a, b, 'ROUTES', 'duration')n"
            + "YIELD path, weightn"
            + "RETURN pathn"
            + "ORDER BY weight ASC LIMIT 1")
    Flux<PathValue> shortestPathInTime(@Param("from") String from, @Param("to") String to);
}

Here is my docker-compose.yml shown below.

version: '3'

services:
  neo4j-db:
    image: neo4j:4.1
    container_name: app-neo4j-db
    ports:
      - 7474:7474
      - 7687:7687
    volumes:
      - $HOME/neo4j/data:/data
      - $HOME/neo4j/logs:/logs
      - $HOME/neo4j/import:/import
      - $HOME/neo4j/plugins:/plugins
    environment:
      NEO4J_AUTH: neo4j/123456
      NEO4J_dbms_security_procedures_unrestricted: apoc.\*,gds.\*
      dbms_connector_bolt_listen__address: neo4j-db:7687
      dbms_connector_bolt_advertised__address: neo4j-db:7687
    healthcheck:
      test: cypher-shell --username neo4j --password 123456 'MATCH (n) RETURN COUNT(n);' # Checks if neo4j server is up and running
      interval: 10s
      timeout: 10s
      retries: 5

  app:
    image: 'springbootneo4jshortestpath:latest'
    build:
      context: .
      dockerfile: Dockerfile
    container_name: SpringBootNeo4jShortestPath
    depends_on:
      neo4j-db:
        condition: service_healthy # Wait for neo4j to be ready
    links:
      - neo4j-db
      environment:
        NEO4J_URI: bolt://neo4j-db:7687
        NEO4J_PASSWORD: 123456

volumes:
  app-neo4j-db:

Leave a Comment