Search code examples
graphhopper

Graphhopper - calculating travel time


I'm developing a project using Graphhopper core to calculate optimal routes. I incorporated some real traffic data by modifying speed assigned to edges and calculated optimal routes in two ways: the "default" way and the way, which considers traffic.

Now, I try to compare those routes and investigate how travel time changes. What I would like to do is to calculate travel time on the optimal route, which was found using default speed assigned to edges, but travel time should be calculated using custom speed values (those, which take into account real traffic). In other words, is it possible to use Graphhopper to calculate travel time on a specific route (not optimal one)?

A solution, which came to my mind, is to implement custom FlagEncoder (as described here), extend Path class and use them to calculate travel time using speed values, which considers traffic. However, maybe you, guys, know simpler way to achieve this.


Solution

  • I finally managed to solve the problem so I share my solution.

    To store custom speed as an extra value I extended class CarFlagEncoder.

    public class CustomCarFlagEncoder extends CarFlagEncoder {
    
        public static final int CUSTOM_SPEED_KEY = 12345;
    
        private EncodedDoubleValue customSpeedEncoder;
    
        public CustomCarFlagEncoder() {
            super();
        }
    
        public CustomCarFlagEncoder(PMap properties) {
            super(properties);
        }
    
        public CustomCarFlagEncoder(String propertiesStr) {
            super(propertiesStr);
        }
    
        public CustomCarFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) {
            super(speedBits, speedFactor, maxTurnCosts);
        }
    
        @Override
        public int defineWayBits(int index, int shift) {
    
            shift = super.defineWayBits(index, shift);
            customSpeedEncoder = new EncodedDoubleValue("Custom speed", shift, speedBits, speedFactor,
                    defaultSpeedMap.get("secondary"), maxPossibleSpeed);
            shift += customSpeedEncoder.getBits();
    
            return shift;
        }
    
        @Override
        public double getDouble(long flags, int key) {
            switch (key) {
                case CUSTOM_SPEED_KEY:
                    return customSpeedEncoder.getDoubleValue(flags);
                default:
                    return super.getDouble(flags, key);
            }
        }
    
        @Override
        public long setDouble(long flags, int key, double value) {
            switch (key) {
                case CUSTOM_SPEED_KEY:
                    if (value < 0 || Double.isNaN(value))
                        throw new IllegalArgumentException("Speed cannot be negative or NaN: " + value
                                + ", flags:" + BitUtil.LITTLE.toBitString(flags));
    
                    if (value > getMaxSpeed())
                        value = getMaxSpeed();
                    return customSpeedEncoder.setDoubleValue(flags, value);
                default:
                    return super.setDouble(flags, key, value);
            }
        }
    
        @Override
        public String toString() {
            return CustomEncodingManager.CUSTOM_CAR;
        }
    }
    

    In order to be able to use custom FlagEncoder, I created CustomEncodingManager, which extends EncodingManager and handles CustomCarFlagEncoder.

    public class CustomEncodingManager extends EncodingManager {
    
        public static final String CUSTOM_CAR = "custom_car";
    
        public CustomEncodingManager(String flagEncodersStr) {
            this(flagEncodersStr, 4);
        }
    
        public CustomEncodingManager(String flagEncodersStr, int bytesForFlags )
        {
            this(parseEncoderString(flagEncodersStr), bytesForFlags);
        }
    
        public CustomEncodingManager(FlagEncoder... flagEncoders) {
            super(flagEncoders);
        }
    
        public CustomEncodingManager(List<? extends FlagEncoder> flagEncoders) {
            super(flagEncoders);
        }
    
        public CustomEncodingManager(List<? extends FlagEncoder> flagEncoders, int bytesForEdgeFlags) {
            super(flagEncoders, bytesForEdgeFlags);
        }
    
        static List<FlagEncoder> parseEncoderString(String encoderList )
        {
            if (encoderList.contains(":"))
                throw new IllegalArgumentException("EncodingManager does no longer use reflection instantiate encoders directly.");
    
            String[] entries = encoderList.split(",");
            List<FlagEncoder> resultEncoders = new ArrayList<FlagEncoder>();
    
            for (String entry : entries)
            {
                entry = entry.trim().toLowerCase();
                if (entry.isEmpty())
                    continue;
    
                String entryVal = "";
                if (entry.contains("|"))
                {
                    entryVal = entry;
                    entry = entry.split("\\|")[0];
                }
                PMap configuration = new PMap(entryVal);
    
                AbstractFlagEncoder fe;
                if (entry.equals(CAR))
                    fe = new CarFlagEncoder(configuration);
    
                else if (entry.equals(BIKE))
                    fe = new BikeFlagEncoder(configuration);
    
                else if (entry.equals(BIKE2))
                    fe = new Bike2WeightFlagEncoder(configuration);
    
                else if (entry.equals(RACINGBIKE))
                    fe = new RacingBikeFlagEncoder(configuration);
    
                else if (entry.equals(MOUNTAINBIKE))
                    fe = new MountainBikeFlagEncoder(configuration);
    
                else if (entry.equals(FOOT))
                    fe = new FootFlagEncoder(configuration);
    
                else if (entry.equals(MOTORCYCLE))
                    fe = new MotorcycleFlagEncoder(configuration);
    
                else if (entry.equals(CUSTOM_CAR)) {
                    fe = new CustomCarFlagEncoder(configuration);
                }
    
                else
                    throw new IllegalArgumentException("entry in encoder list not supported " + entry);
    
                if (configuration.has("version"))
                {
                    if (fe.getVersion() != configuration.getInt("version", -1))
                    {
                        throw new IllegalArgumentException("Encoder " + entry + " was used in version "
                                + configuration.getLong("version", -1) + ", but current version is " + fe.getVersion());
                    }
                }
    
                resultEncoders.add(fe);
            }
            return resultEncoders;
        }
    
    }
    

    Then, I set the custom EncodingManager to GraphHopper object hopper.setEncodingManager(new CustomEncodingManager(CustomEncodingManager.CUSTOM_CAR));

    I assign custom speed to an edge as an extra value edge.setFlags(customCarEncoder.setDouble(existingFlags, CustomCarFlagEncoder.CUSTOM_SPEED_KEY, newSpeed));

    Finally, to use custom speed while calculating travel time, I slightly modified method clacMillis form class Path from package com.graphhoper.routing.

    protected long calcMillis( double distance, long flags, boolean revert )
    {
        if (revert && !encoder.isBackward(flags)
                || !revert && !encoder.isForward(flags))
            throw new IllegalStateException("Calculating time should not require to read speed from edge in wrong direction. "
                    + "Reverse:" + revert + ", fwd:" + encoder.isForward(flags) + ", bwd:" + encoder.isBackward(flags));
    
        double speed = revert ? encoder.getReverseSpeed(flags) : encoder.getSpeed(flags);
        double customSpeed = encoder.getDouble(flags, 12345);
        if (customSpeed > 0) {
            speed = customSpeed;
        }
        if (Double.isInfinite(speed) || Double.isNaN(speed) || speed < 0)
            throw new IllegalStateException("Invalid speed stored in edge! " + speed);
    
        if (speed == 0)
            throw new IllegalStateException("Speed cannot be 0 for unblocked edge, use access properties to mark edge blocked! Should only occur for shortest path calculation. See #242.");
    
        return (long) (distance * 3600 / speed);
    }