/** * Processor for Voice Hints * * @author ab */ package btools.router; import java.util.ArrayList; import java.util.List; public final class VoiceHintProcessor { 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<VoiceHint> inputs, int offset ) { double distance = 0.; float angle = 0.f; while( offset >= 0 && distance < catchingRange ) { VoiceHint input = inputs.get( offset-- ); if ( input.turnAngleConsumed ) { break; } angle += input.goodWay.turnangle; distance += input.goodWay.linkdist; input.turnAngleConsumed = true; } return angle; } /** * 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<VoiceHint> process( List<VoiceHint> inputs ) { List<VoiceHint> results = new ArrayList<VoiceHint>(); double distance = 0.; float roundAboutTurnAngle = 0.f; // sums up angles in roundabout int roundaboutExit = 0; for ( int hintIdx = 0; hintIdx < inputs.size(); hintIdx++ ) { VoiceHint input = inputs.get( hintIdx ); float turnAngle = input.goodWay.turnangle; distance += input.goodWay.linkdist; int currentPrio = input.goodWay.getPrio(); int oldPrio = input.oldWay.getPrio(); int minPrio = Math.min( oldPrio, currentPrio ); boolean isLink2Highway = input.oldWay.isLinktType() && !input.goodWay.isLinktType(); if ( input.oldWay.isRoundabout() ) { roundAboutTurnAngle += sumNonConsumedWithinCatchingRange( inputs, hintIdx ); boolean isExit = roundaboutExit == 0; // exit point is always exit if ( input.badWays != null ) { for ( MessageData badWay : input.badWays ) { if ( !badWay.isBadOneway() && badWay.isGoodForCars() && Math.abs( badWay.turnangle ) < 120. ) { isExit = true; } } } if ( isExit ) { roundaboutExit++; } continue; } if ( roundaboutExit > 0 ) { roundAboutTurnAngle += sumNonConsumedWithinCatchingRange( inputs, hintIdx ); input.angle = roundAboutTurnAngle; input.distanceToNext = distance; input.roundaboutExit = turnAngle < 0 ? -roundaboutExit : roundaboutExit; distance = 0.; results.add( input ); roundAboutTurnAngle = 0.f; roundaboutExit = 0; continue; } int maxPrioAll = -1; // max prio of all detours int maxPrioCandidates = -1; // max prio of real candidates 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(); float badTurn = badWay.turnangle; 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 ( badWay.isBadOneway() ) { continue; // ignore wrong oneways } if ( Math.abs( badTurn ) - Math.abs( turnAngle ) > 80.f ) { continue; // ways from the back should not trigger a slight turn } if ( badPrio > maxPrioCandidates ) { maxPrioCandidates = badPrio; } if ( badTurn > maxAngle ) { maxAngle = badTurn; } if ( badTurn < minAngle ) { minAngle = badTurn; } } } 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 = 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 boolean conditionalTrigger = maxPrioCandidates >= minPrio; if ( unconditionalTrigger || conditionalTrigger ) { input.angle = turnAngle; input.calcCommand(); boolean isStraight = input.cmd == VoiceHint.C; input.needsRealTurn = (!unconditionalTrigger) && isStraight; // check for KR/KL if ( maxAngle < turnAngle && maxAngle > turnAngle - 45.f - (turnAngle > 0.f ? turnAngle : 0.f ) ) { input.cmd = VoiceHint.KR; } if ( minAngle > turnAngle && minAngle < turnAngle + 45.f - (turnAngle < 0.f ? turnAngle : 0.f ) ) { input.cmd = VoiceHint.KL; } input.angle = sumNonConsumedWithinCatchingRange( inputs, hintIdx ); input.distanceToNext = distance; distance = 0.; results.add( input ); } if ( results.size() > 0 && distance < catchingRange ) { results.get( results.size()-1 ).angle += sumNonConsumedWithinCatchingRange( inputs, hintIdx ); } } // go through the hint list again in reverse order (=travel direction) // and filter out non-signficant hints and hints too close to it's predecessor List<VoiceHint> results2 = new ArrayList<VoiceHint>(); int i = results.size(); while( i > 0 ) { VoiceHint hint = results.get(--i); if ( hint.cmd == 0 ) { hint.calcCommand(); } if ( ! ( hint.needsRealTurn && hint.cmd == VoiceHint.C ) ) { double dist = hint.distanceToNext; // 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; hint.distanceToNext+= dist; hint.angle += h2.angle; i--; if ( h2.isRoundabout() ) // if we hit a roundabout, use that as the trigger { h2.angle = hint.angle; hint = h2; break; } } if ( !explicitRoundabouts ) { hint.roundaboutExit = 0; // use an angular hint instead } hint.calcCommand(); results2.add( hint ); } } return results2; } }