# include "selfdrive/ui/navd/route_engine.h"
# include <QDebug>
# include "selfdrive/ui/qt/maps/map.h"
# include "selfdrive/ui/qt/maps/map_helpers.h"
# include "selfdrive/ui/qt/api.h"
# include "common/params.h"
const qreal REROUTE_DISTANCE = 25 ;
const float MANEUVER_TRANSITION_THRESHOLD = 10 ;
static float get_time_typical ( const QGeoRouteSegment & segment ) {
auto maneuver = segment . maneuver ( ) ;
auto attrs = maneuver . extendedAttributes ( ) ;
return attrs . contains ( " mapbox.duration_typical " ) ? attrs [ " mapbox.duration_typical " ] . toDouble ( ) : segment . travelTime ( ) ;
}
static cereal : : NavInstruction : : Direction string_to_direction ( QString d ) {
if ( d . contains ( " left " ) ) {
return cereal : : NavInstruction : : Direction : : LEFT ;
} else if ( d . contains ( " right " ) ) {
return cereal : : NavInstruction : : Direction : : RIGHT ;
} else if ( d . contains ( " straight " ) ) {
return cereal : : NavInstruction : : Direction : : STRAIGHT ;
}
return cereal : : NavInstruction : : Direction : : NONE ;
}
static void parse_banner ( cereal : : NavInstruction : : Builder & instruction , const QMap < QString , QVariant > & banner , bool full ) {
QString primary_str , secondary_str ;
auto p = banner [ " primary " ] . toMap ( ) ;
primary_str + = p [ " text " ] . toString ( ) ;
instruction . setShowFull ( full ) ;
if ( p . contains ( " type " ) ) {
instruction . setManeuverType ( p [ " type " ] . toString ( ) . toStdString ( ) ) ;
}
if ( p . contains ( " modifier " ) ) {
instruction . setManeuverModifier ( p [ " modifier " ] . toString ( ) . toStdString ( ) ) ;
}
if ( banner . contains ( " secondary " ) ) {
auto s = banner [ " secondary " ] . toMap ( ) ;
secondary_str + = s [ " text " ] . toString ( ) ;
}
instruction . setManeuverPrimaryText ( primary_str . toStdString ( ) ) ;
instruction . setManeuverSecondaryText ( secondary_str . toStdString ( ) ) ;
if ( banner . contains ( " sub " ) ) {
auto s = banner [ " sub " ] . toMap ( ) ;
auto components = s [ " components " ] . toList ( ) ;
size_t num_lanes = 0 ;
for ( auto & c : components ) {
auto cc = c . toMap ( ) ;
if ( cc [ " type " ] . toString ( ) = = " lane " ) {
num_lanes + = 1 ;
}
}
auto lanes = instruction . initLanes ( num_lanes ) ;
size_t i = 0 ;
for ( auto & c : components ) {
auto cc = c . toMap ( ) ;
if ( cc [ " type " ] . toString ( ) = = " lane " ) {
auto lane = lanes [ i ] ;
lane . setActive ( cc [ " active " ] . toBool ( ) ) ;
if ( cc . contains ( " active_direction " ) ) {
lane . setActiveDirection ( string_to_direction ( cc [ " active_direction " ] . toString ( ) ) ) ;
}
auto directions = lane . initDirections ( cc [ " directions " ] . toList ( ) . size ( ) ) ;
size_t j = 0 ;
for ( auto & dir : cc [ " directions " ] . toList ( ) ) {
directions . set ( j , string_to_direction ( dir . toString ( ) ) ) ;
j + + ;
}
i + + ;
}
}
}
}
RouteEngine : : RouteEngine ( ) {
sm = new SubMaster ( { " liveLocationKalman " , " managerState " } ) ;
pm = new PubMaster ( { " navInstruction " , " navRoute " } ) ;
// Timers
route_timer = new QTimer ( this ) ;
QObject : : connect ( route_timer , SIGNAL ( timeout ( ) ) , this , SLOT ( routeUpdate ( ) ) ) ;
route_timer - > start ( 1000 ) ;
msg_timer = new QTimer ( this ) ;
QObject : : connect ( msg_timer , SIGNAL ( timeout ( ) ) , this , SLOT ( msgUpdate ( ) ) ) ;
msg_timer - > start ( 50 ) ;
// Build routing engine
QVariantMap parameters ;
parameters [ " mapbox.access_token " ] = get_mapbox_token ( ) ;
parameters [ " mapbox.directions_api_url " ] = MAPS_HOST + " /directions/v5/mapbox/ " ;
geoservice_provider = new QGeoServiceProvider ( " mapbox " , parameters ) ;
routing_manager = geoservice_provider - > routingManager ( ) ;
if ( routing_manager = = nullptr ) {
qWarning ( ) < < geoservice_provider - > errorString ( ) ;
assert ( routing_manager ) ;
}
QObject : : connect ( routing_manager , & QGeoRoutingManager : : finished , this , & RouteEngine : : routeCalculated ) ;
// Get last gps position from params
auto last_gps_position = coordinate_from_param ( " LastGPSPosition " ) ;
if ( last_gps_position ) {
last_position = * last_gps_position ;
}
}
void RouteEngine : : msgUpdate ( ) {
sm - > update ( 1000 ) ;
if ( ! sm - > updated ( " liveLocationKalman " ) ) {
active = false ;
return ;
}
if ( sm - > updated ( " managerState " ) ) {
for ( auto const & p : ( * sm ) [ " managerState " ] . getManagerState ( ) . getProcesses ( ) ) {
if ( p . getName ( ) = = " ui " & & p . getRunning ( ) ) {
if ( ui_pid & & * ui_pid ! = p . getPid ( ) ) {
qWarning ( ) < < " UI restarting, sending route " ;
QTimer : : singleShot ( 5000 , this , & RouteEngine : : sendRoute ) ;
}
ui_pid = p . getPid ( ) ;
}
}
}
auto location = ( * sm ) [ " liveLocationKalman " ] . getLiveLocationKalman ( ) ;
auto pos = location . getPositionGeodetic ( ) ;
auto orientation = location . getCalibratedOrientationNED ( ) ;
gps_ok = location . getGpsOK ( ) ;
localizer_valid = ( location . getStatus ( ) = = cereal : : LiveLocationKalman : : Status : : VALID ) & & pos . getValid ( ) ;
if ( localizer_valid ) {
last_bearing = RAD2DEG ( orientation . getValue ( ) [ 2 ] ) ;
last_position = QMapbox : : Coordinate ( pos . getValue ( ) [ 0 ] , pos . getValue ( ) [ 1 ] ) ;
emit positionUpdated ( * last_position , * last_bearing ) ;
}
active = true ;
}
void RouteEngine : : routeUpdate ( ) {
if ( ! active ) {
return ;
}
recomputeRoute ( ) ;
MessageBuilder msg ;
cereal : : Event : : Builder evt = msg . initEvent ( segment . isValid ( ) ) ;
cereal : : NavInstruction : : Builder instruction = evt . initNavInstruction ( ) ;
// Show route instructions
if ( segment . isValid ( ) ) {
auto cur_maneuver = segment . maneuver ( ) ;
auto attrs = cur_maneuver . extendedAttributes ( ) ;
if ( cur_maneuver . isValid ( ) & & attrs . contains ( " mapbox.banner_instructions " ) ) {
float along_geometry = distance_along_geometry ( segment . path ( ) , to_QGeoCoordinate ( * last_position ) ) ;
float distance_to_maneuver_along_geometry = segment . distance ( ) - along_geometry ;
auto banners = attrs [ " mapbox.banner_instructions " ] . toList ( ) ;
if ( banners . size ( ) ) {
auto banner = banners [ 0 ] . toMap ( ) ;
for ( auto & b : banners ) {
auto bb = b . toMap ( ) ;
if ( distance_to_maneuver_along_geometry < bb [ " distance_along_geometry " ] . toDouble ( ) ) {
banner = bb ;
}
}
instruction . setManeuverDistance ( distance_to_maneuver_along_geometry ) ;
parse_banner ( instruction , banner , distance_to_maneuver_along_geometry < banner [ " distance_along_geometry " ] . toDouble ( ) ) ;
// ETA
float progress = distance_along_geometry ( segment . path ( ) , to_QGeoCoordinate ( * last_position ) ) / segment . distance ( ) ;
float total_distance = segment . distance ( ) * ( 1.0 - progress ) ;
float total_time = segment . travelTime ( ) * ( 1.0 - progress ) ;
float total_time_typical = get_time_typical ( segment ) * ( 1.0 - progress ) ;
auto s = segment . nextRouteSegment ( ) ;
while ( s . isValid ( ) ) {
total_distance + = s . distance ( ) ;
total_time + = s . travelTime ( ) ;
total_time_typical + = get_time_typical ( s ) ;
s = s . nextRouteSegment ( ) ;
}
instruction . setTimeRemaining ( total_time ) ;
instruction . setTimeRemainingTypical ( total_time_typical ) ;
instruction . setDistanceRemaining ( total_distance ) ;
}
// Transition to next route segment
if ( distance_to_maneuver_along_geometry < - MANEUVER_TRANSITION_THRESHOLD ) {
auto next_segment = segment . nextRouteSegment ( ) ;
if ( next_segment . isValid ( ) ) {
segment = next_segment ;
recompute_backoff = 0 ;
recompute_countdown = 0 ;
} else {
qWarning ( ) < < " Destination reached " ;
Params ( ) . remove ( " NavDestination " ) ;
// Clear route if driving away from destination
float d = segment . maneuver ( ) . position ( ) . distanceTo ( to_QGeoCoordinate ( * last_position ) ) ;
if ( d > REROUTE_DISTANCE ) {
clearRoute ( ) ;
}
}
}
}
}
pm - > send ( " navInstruction " , msg ) ;
}
void RouteEngine : : clearRoute ( ) {
route = QGeoRoute ( ) ;
segment = QGeoRouteSegment ( ) ;
nav_destination = QMapbox : : Coordinate ( ) ;
}
bool RouteEngine : : shouldRecompute ( ) {
if ( ! segment . isValid ( ) ) {
return true ;
}
// Don't recompute in last segment, assume destination is reached
if ( ! segment . nextRouteSegment ( ) . isValid ( ) ) {
return false ;
}
// Compute closest distance to all line segments in the current path
float min_d = REROUTE_DISTANCE + 1 ;
auto path = segment . path ( ) ;
auto cur = to_QGeoCoordinate ( * last_position ) ;
for ( size_t i = 0 ; i < path . size ( ) - 1 ; i + + ) {
auto a = path [ i ] ;
auto b = path [ i + 1 ] ;
if ( a . distanceTo ( b ) < 1.0 ) {
continue ;
}
min_d = std : : min ( min_d , minimum_distance ( a , b , cur ) ) ;
}
return min_d > REROUTE_DISTANCE ;
// TODO: Check for going wrong way in segment
}
void RouteEngine : : recomputeRoute ( ) {
if ( ! last_position ) {
return ;
}
auto new_destination = coordinate_from_param ( " NavDestination " ) ;
if ( ! new_destination ) {
clearRoute ( ) ;
return ;
}
bool should_recompute = shouldRecompute ( ) ;
if ( * new_destination ! = nav_destination ) {
qWarning ( ) < < " Got new destination from NavDestination param " < < * new_destination ;
should_recompute = true ;
}
if ( ! gps_ok & & segment . isValid ( ) ) return ; // Don't recompute when gps drifts in tunnels
if ( recompute_countdown = = 0 & & should_recompute ) {
recompute_countdown = std : : pow ( 2 , recompute_backoff ) ;
recompute_backoff = std : : min ( 6 , recompute_backoff + 1 ) ;
calculateRoute ( * new_destination ) ;
} else {
recompute_countdown = std : : max ( 0 , recompute_countdown - 1 ) ;
}
}
void RouteEngine : : calculateRoute ( QMapbox : : Coordinate destination ) {
qWarning ( ) < < " Calculating route " < < * last_position < < " -> " < < destination ;
nav_destination = destination ;
QGeoRouteRequest request ( to_QGeoCoordinate ( * last_position ) , to_QGeoCoordinate ( destination ) ) ;
request . setFeatureWeight ( QGeoRouteRequest : : TrafficFeature , QGeoRouteRequest : : AvoidFeatureWeight ) ;
if ( last_bearing ) {
QVariantMap params ;
int bearing = ( ( int ) ( * last_bearing ) + 360 ) % 360 ;
params [ " bearing " ] = bearing ;
request . setWaypointsMetadata ( { params } ) ;
}
routing_manager - > calculateRoute ( request ) ;
}
void RouteEngine : : routeCalculated ( QGeoRouteReply * reply ) {
if ( reply - > error ( ) = = QGeoRouteReply : : NoError ) {
if ( reply - > routes ( ) . size ( ) ! = 0 ) {
qWarning ( ) < < " Got route response " ;
route = reply - > routes ( ) . at ( 0 ) ;
segment = route . firstRouteSegment ( ) ;
auto path = route . path ( ) ;
emit routeUpdated ( path ) ;
} else {
qWarning ( ) < < " Got empty route response " ;
}
} else {
qWarning ( ) < < " Got error in route reply " < < reply - > errorString ( ) ;
}
sendRoute ( ) ;
reply - > deleteLater ( ) ;
}
void RouteEngine : : sendRoute ( ) {
MessageBuilder msg ;
cereal : : Event : : Builder evt = msg . initEvent ( ) ;
cereal : : NavRoute : : Builder nav_route = evt . initNavRoute ( ) ;
auto path = route . path ( ) ;
auto coordinates = nav_route . initCoordinates ( path . size ( ) ) ;
size_t i = 0 ;
for ( auto const & c : route . path ( ) ) {
coordinates [ i ] . setLatitude ( c . latitude ( ) ) ;
coordinates [ i ] . setLongitude ( c . longitude ( ) ) ;
i + + ;
}
pm - > send ( " navRoute " , msg ) ;
}