/**
* 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;
}
}