From 23968b2a5b6484582eda696224e38cf962671151 Mon Sep 17 00:00:00 2001 From: Arndt Date: Thu, 5 May 2016 12:46:27 +0200 Subject: [PATCH] voice hints update --- .../main/java/btools/router/MessageData.java | 28 ++++++- .../src/main/java/btools/router/OsmPath.java | 3 +- .../src/main/java/btools/router/OsmTrack.java | 5 +- .../java/btools/router/RoutingContext.java | 4 + .../btools/router/VoiceHintProcessor.java | 79 ++++++++++++++----- .../java/btools/expressions/BExpression.java | 11 +++ .../expressions/BExpressionContextWay.java | 5 +- 7 files changed, 103 insertions(+), 32 deletions(-) diff --git a/brouter-core/src/main/java/btools/router/MessageData.java b/brouter-core/src/main/java/btools/router/MessageData.java index ce6265d..abe93fb 100644 --- a/brouter-core/src/main/java/btools/router/MessageData.java +++ b/brouter-core/src/main/java/btools/router/MessageData.java @@ -17,9 +17,8 @@ final class MessageData implements Cloneable float costfactor; int priorityclassifier; + int classifiermask; float turnangle; - int onwaydirection; - int roundaboutdirection; String wayKeyValues; String nodeKeyValues; @@ -77,11 +76,32 @@ final class MessageData implements Cloneable public int getPrio() { - return Math.abs( priorityclassifier ); + return priorityclassifier; + } + + public boolean isBadOneway() + { + return ( classifiermask & 1 ) != 0; + } + + public boolean isGoodOneway() + { + return ( classifiermask & 2 ) != 0; + } + + public boolean isRoundabout() + { + return ( classifiermask & 4 ) != 0; + } + + public boolean isLinktType() + { + return ( classifiermask & 8 ) != 0; } public boolean isGoodForCars() { - return priorityclassifier > 0; + return ( classifiermask & 16 ) != 0; } + } diff --git a/brouter-core/src/main/java/btools/router/OsmPath.java b/brouter-core/src/main/java/btools/router/OsmPath.java index a4afef2..d5bb08d 100644 --- a/brouter-core/src/main/java/btools/router/OsmPath.java +++ b/brouter-core/src/main/java/btools/router/OsmPath.java @@ -338,8 +338,7 @@ final class OsmPath implements OsmLinkHolder { msgData.costfactor = costfactor; msgData.priorityclassifier = (int)rc.expctxWay.getPriorityClassifier(); - msgData.onwaydirection = (int)rc.expctxWay.getOnewayDirection(); - msgData.roundaboutdirection = (int)rc.expctxWay.getRoundaboutDirection(); + msgData.classifiermask = (int)rc.expctxWay.getClassifierMask(); msgData.lon = lon2; msgData.lat = lat2; msgData.ele = ele2; diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 91015ae..fed48ca 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -657,7 +657,7 @@ public final class OsmTrack input.ilon = node.origin.getILon(); input.indexInTrack = --nodeNr; input.goodWay = node.message; - input.oldWay = node.origin.message; + input.oldWay = node.origin.message == null ? node.message : node.origin.message; OsmPathElementHolder detours = detourMap.get( node.origin.getIdFromPos() ); if ( detours != null ) @@ -674,7 +674,8 @@ public final class OsmTrack node = node.origin; } - List results = VoiceHintProcessor.process( inputs ); + VoiceHintProcessor vproc = new VoiceHintProcessor( rc.turnInstructionCatchingRange, rc.turnInstructionRoundabouts ); + List results = vproc.process( inputs ); for( VoiceHint hint : results ) { voiceHints.list.add( hint ); diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index 7b3c3e2..05851b8 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -101,6 +101,8 @@ public final class RoutingContext implements DistanceChecker { turnInstructionMode = tiMode; } + turnInstructionCatchingRange = expctxGlobal.getVariableValue( "trafficSourceMinDist", 40.f ); + turnInstructionRoundabouts = expctxGlobal.getVariableValue( "turnInstructionRoundabouts", 1.f ) != 0.f; } public RoutingMessageHandler messageHandler = new RoutingMessageHandler(); @@ -130,6 +132,8 @@ public final class RoutingContext implements DistanceChecker public double trafficSourceMinDist; public int turnInstructionMode; // 0=none, 1=auto, 2=locus, 3=osmand, 4=comment-style, 5=gpsies-style + public double turnInstructionCatchingRange; + public boolean turnInstructionRoundabouts; public static void prepareNogoPoints( List nogos ) { diff --git a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java index e937e15..247c840 100644 --- a/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java +++ b/brouter-core/src/main/java/btools/router/VoiceHintProcessor.java @@ -10,11 +10,20 @@ import java.util.List; public final class VoiceHintProcessor { - private static float sumNonConsumedWithin40( List inputs, int offset ) + private double catchingRange; // range to catch angles and merge turns + private boolean explicitRoundabouts; + + public VoiceHintProcessor( double catchingRange, boolean explicitRoundabouts ) + { + this.catchingRange = catchingRange; + this.explicitRoundabouts = explicitRoundabouts; + } + + private float sumNonConsumedWithinCatchingRange( List inputs, int offset ) { double distance = 0.; float angle = 0.f; - while( offset >= 0 && distance < 40. ) + while( offset >= 0 && distance < catchingRange ) { VoiceHint input = inputs.get( offset-- ); if ( input.turnAngleConsumed ) @@ -29,7 +38,24 @@ public final class VoiceHintProcessor } - public static List process( List inputs ) + /** + * process voice hints. Uses VoiceHint objects + * for both input and output. Input is in reverse + * order (from target to start), but output is + * returned in travel-direction and only for + * those nodes that trigger a voice hint. + * + * Input objects are expected for every segment + * of the track, also for those without a junction + * + * VoiceHint objects in the output list are enriched + * by the voice-command, the total angle and the distance + * to the next hint + * + * @param inputs tracknodes, un reverse order + * @return voice hints, in forward order + */ + public List process( List inputs ) { List results = new ArrayList(); double distance = 0.; @@ -44,22 +70,20 @@ public final class VoiceHintProcessor float turnAngle = input.goodWay.turnangle; distance += input.goodWay.linkdist; int currentPrio = input.goodWay.getPrio(); - int oldPrio = input.oldWay == null ? currentPrio : input.oldWay.getPrio(); + int oldPrio = input.oldWay.getPrio(); int minPrio = Math.min( oldPrio, currentPrio ); - // odd priorities are link-types - boolean isLink2Highway = ( ( oldPrio & 1 ) == 1 ) && ( ( currentPrio & 1 ) == 0 ); + boolean isLink2Highway = input.oldWay.isLinktType() && !input.goodWay.isLinktType(); - boolean inRoundabout = input.oldWay != null && input.oldWay.roundaboutdirection != 0; - if ( inRoundabout ) + if ( input.oldWay.isRoundabout() ) { - roundAboutTurnAngle += sumNonConsumedWithin40( inputs, hintIdx ); + roundAboutTurnAngle += sumNonConsumedWithinCatchingRange( inputs, hintIdx ); boolean isExit = roundaboutExit == 0; // exit point is always exit if ( input.badWays != null ) { for ( MessageData badWay : input.badWays ) { - if ( badWay.onwaydirection >= 0 && badWay.isGoodForCars() && Math.abs( badWay.turnangle ) < 120. ) + if ( !badWay.isBadOneway() && badWay.isGoodForCars() && Math.abs( badWay.turnangle ) < 120. ) { isExit = true; } @@ -73,7 +97,7 @@ public final class VoiceHintProcessor } if ( roundaboutExit > 0 ) { - roundAboutTurnAngle += sumNonConsumedWithin40( inputs, hintIdx ); + roundAboutTurnAngle += sumNonConsumedWithinCatchingRange( inputs, hintIdx ); input.angle = roundAboutTurnAngle; input.distanceToNext = distance; input.roundaboutExit = turnAngle < 0 ? -roundaboutExit : roundaboutExit; @@ -88,31 +112,37 @@ public final class VoiceHintProcessor float maxAngle = -180.f; float minAngle = 180.f; + float minAbsAngeRaw = 180.f; + if ( input.badWays != null ) { for ( MessageData badWay : input.badWays ) { int badPrio = badWay.getPrio(); - boolean badOneway = badWay.onwaydirection < 0; + float badTurn = badWay.turnangle; - boolean isHighway2Link = ( ( badPrio & 1 ) == 1 ) && ( ( currentPrio & 1 ) == 0 ); + boolean isHighway2Link = !input.oldWay.isLinktType() && badWay.isLinktType(); if ( badPrio > maxPrioAll && !isHighway2Link ) { maxPrioAll = badPrio; } + if ( badWay.costfactor < 20.f && Math.abs( badTurn ) < minAbsAngeRaw ) + { + minAbsAngeRaw = Math.abs( badTurn ); + } + if ( badPrio < minPrio ) { continue; // ignore low prio ways } - if ( badOneway ) + if ( badWay.isBadOneway() ) { continue; // ignore wrong oneways } - float badTurn = badWay.turnangle; if ( Math.abs( badTurn ) - Math.abs( turnAngle ) > 80.f ) { continue; // ways from the back should not trigger a slight turn @@ -133,10 +163,12 @@ public final class VoiceHintProcessor } } + boolean hasSomethingMoreStraight = Math.abs( turnAngle ) - minAbsAngeRaw > 20.; + // unconditional triggers are all junctions with // - higher detour prios than the minimum route prio (except link->highway junctions) // - or candidate detours with higher prio then the route exit leg - boolean unconditionalTrigger = ( maxPrioAll > minPrio && !isLink2Highway ) || ( maxPrioCandidates > currentPrio ); + boolean unconditionalTrigger = hasSomethingMoreStraight || ( maxPrioAll > minPrio && !isLink2Highway ) || ( maxPrioCandidates > currentPrio ); // conditional triggers (=real turning angle required) are junctions // with candidate detours equal in priority than the route exit leg @@ -159,14 +191,14 @@ public final class VoiceHintProcessor input.cmd = VoiceHint.KL; } - input.angle = sumNonConsumedWithin40( inputs, hintIdx ); + input.angle = sumNonConsumedWithinCatchingRange( inputs, hintIdx ); input.distanceToNext = distance; distance = 0.; results.add( input ); } - if ( results.size() > 0 && distance < 40. ) + if ( results.size() > 0 && distance < catchingRange ) { - results.get( results.size()-1 ).angle += sumNonConsumedWithin40( inputs, hintIdx ); + results.get( results.size()-1 ).angle += sumNonConsumedWithinCatchingRange( inputs, hintIdx ); } } @@ -185,8 +217,8 @@ public final class VoiceHintProcessor if ( ! ( hint.needsRealTurn && hint.cmd == VoiceHint.C ) ) { double dist = hint.distanceToNext; - // sum up other hints within 40m - while( dist < 40. && i > 0 ) + // sum up other hints within the catching range (e.g. 40m) + while( dist < catchingRange && i > 0 ) { VoiceHint h2 = results.get(i-1); dist = h2.distanceToNext; @@ -200,6 +232,11 @@ public final class VoiceHintProcessor break; } } + + if ( !explicitRoundabouts ) + { + hint.roundaboutExit = 0; // use an angular hint instead + } hint.calcCommand(); results2.add( hint ); } diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpression.java b/brouter-expressions/src/main/java/btools/expressions/BExpression.java index beb9b53..a9957b4 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpression.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpression.java @@ -13,6 +13,7 @@ final class BExpression private static final int MAX_EXP = 22; private static final int EQUAL_EXP = 23; private static final int GREATER_EXP = 24; + private static final int MIN_EXP = 25; private static final int SWITCH_EXP = 30; private static final int ASSIGN_EXP = 31; @@ -102,6 +103,10 @@ final class BExpression { exp.typ = MAX_EXP; } + else if ( "min".equals( operator ) ) + { + exp.typ = MIN_EXP; + } else if ( "equal".equals( operator ) ) { exp.typ = EQUAL_EXP; @@ -230,6 +235,7 @@ final class BExpression case ADD_EXP: return op1.evaluate(ctx) + op2.evaluate(ctx); case MULTIPLY_EXP: return op1.evaluate(ctx) * op2.evaluate(ctx); case MAX_EXP: return max( op1.evaluate(ctx), op2.evaluate(ctx) ); + case MIN_EXP: return min( op1.evaluate(ctx), op2.evaluate(ctx) ); case EQUAL_EXP: return op1.evaluate(ctx) == op2.evaluate(ctx) ? 1.f : 0.f; case GREATER_EXP: return op1.evaluate(ctx) > op2.evaluate(ctx) ? 1.f : 0.f; case SWITCH_EXP: return op1.evaluate(ctx) != 0.f ? op2.evaluate(ctx) : op3.evaluate(ctx); @@ -247,4 +253,9 @@ final class BExpression { return v1 > v2 ? v1 : v2; } + + private float min( float v1, float v2 ) + { + return v1 < v2 ? v1 : v2; + } } diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java index 7d9cbd9..a2fb7a5 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java @@ -13,7 +13,7 @@ import btools.codec.TagValueValidator; public final class BExpressionContextWay extends BExpressionContext implements TagValueValidator { private static String[] buildInVariables = - { "costfactor", "turncost", "uphillcostfactor", "downhillcostfactor", "initialcost", "nodeaccessgranted", "initialclassifier", "trafficsourcedensity", "istrafficbackbone", "priorityclassifier", "onewaydirection", "roundaboutdirection"}; + { "costfactor", "turncost", "uphillcostfactor", "downhillcostfactor", "initialcost", "nodeaccessgranted", "initialclassifier", "trafficsourcedensity", "istrafficbackbone", "priorityclassifier", "classifiermask" }; protected String[] getBuildInVariableNames() { @@ -30,8 +30,7 @@ public final class BExpressionContextWay extends BExpressionContext implements T public float getTrafficSourceDensity() { return getBuildInVariable(7); } public float getIsTrafficBackbone() { return getBuildInVariable(8); } public float getPriorityClassifier() { return getBuildInVariable(9); } - public float getOnewayDirection() { return getBuildInVariable(10); } - public float getRoundaboutDirection() { return getBuildInVariable(11); } + public float getClassifierMask() { return getBuildInVariable(10); } public BExpressionContextWay( BExpressionMetaData meta ) {