2

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.

Łukasz Fijas
  • 113
  • 1
  • 8
  • If you e.g. use FastestWeighting or ShortestWeighting you'll get in both cases time&distance. If you take traffic data into account via changing speed then you do not need to change something more - it should already work. If you e.g. have the traffic data as separate time offset stored per edge you'll need to do the final calculation on your own (just loop over all edges) or wait for these issues being fixed: https://github.com/graphhopper/graphhopper/issues/439 & https://github.com/graphhopper/graphhopper/pull/730 – Karussell Jun 28 '16 at 20:33
  • Thanks @Karussell for your reply. So, I think I must change my approach a bit. Is it good idea to set custom speed for an edge using method setAdditionalField( int value ) from EdgeIteratorState? – Łukasz Fijas Jun 29 '16 at 14:32
  • setting speed is done e.g. via edge.setProperties or use an EncodedValue in your FlagEncoder to store additional values – Karussell Jun 29 '16 at 19:02

1 Answers1

2

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);
}
Łukasz Fijas
  • 113
  • 1
  • 8