Graphhopper - расчет времени в пути
Я разрабатываю проект с использованием ядра Graphhopper для расчета оптимальных маршрутов. Я включил некоторые реальные данные о трафике, изменив скорость, назначенную краям, и рассчитал оптимальные маршруты двумя способами: способом "по умолчанию" и способом, который учитывает трафик.
Теперь я попытаюсь сравнить эти маршруты и выяснить, как меняется время в пути. То, что я хотел бы сделать, это рассчитать время в пути по оптимальному маршруту, который был найден с использованием скорости по умолчанию, назначенной ребрам, но время в пути следует рассчитывать с использованием пользовательских значений скорости (тех, которые учитывают реальный трафик). Другими словами, возможно ли использовать Graphhopper для расчета времени в пути по конкретному маршруту (не оптимальному)?
Решение, которое мне пришло в голову, состоит в том, чтобы реализовать пользовательский FlagEncoder (как описано здесь), расширить класс Path и использовать их для расчета времени в пути, используя значения скорости, которые учитывают трафик. Однако, возможно, вы, ребята, знаете более простой способ добиться этого.
1 ответ
Мне наконец удалось решить проблему, и я поделился своим решением.
Для сохранения пользовательской скорости в качестве дополнительного значения я расширил класс 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;
}
}
Чтобы иметь возможность использовать собственный FlagEncoder, я создал CustomEncodingManager, который расширяет EncodingManager и обрабатывает 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;
}
}
Затем я установил пользовательский EncodingManager для объекта GraphHopper hopper.setEncodingManager(new CustomEncodingManager(CustomEncodingManager.CUSTOM_CAR));
Я назначаю пользовательскую скорость ребру как дополнительное значение edge.setFlags(customCarEncoder.setDouble(existingFlags, CustomCarFlagEncoder.CUSTOM_SPEED_KEY,
newSpeed));
Наконец, чтобы использовать пользовательскую скорость при расчете времени в пути, я немного изменил метод класса формы clacMillis. Path
из пакета 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);
}