# include "selfdrive/ui/qt/maps/map.h"
# include <cmath>
# include <QDebug>
# include <QPainterPath>
# include "selfdrive/common/swaglog.h"
# include "selfdrive/ui/ui.h"
# include "selfdrive/ui/qt/util.h"
# include "selfdrive/ui/qt/maps/map_helpers.h"
# include "selfdrive/ui/qt/request_repeater.h"
const int PAN_TIMEOUT = 100 ;
const float MANEUVER_TRANSITION_THRESHOLD = 10 ;
const float MAX_ZOOM = 17 ;
const float MIN_ZOOM = 14 ;
const float MAX_PITCH = 50 ;
const float MIN_PITCH = 0 ;
const float MAP_SCALE = 2 ;
MapWindow : : MapWindow ( const QMapboxGLSettings & settings ) :
m_settings ( settings ) , velocity_filter ( 0 , 10 , 0.1 ) {
sm = new SubMaster ( { " liveLocationKalman " , " navInstruction " , " navRoute " } ) ;
timer = new QTimer ( this ) ;
QObject : : connect ( timer , SIGNAL ( timeout ( ) ) , this , SLOT ( timerUpdate ( ) ) ) ;
timer - > start ( 100 ) ;
// Instructions
map_instructions = new MapInstructions ( this ) ;
QObject : : connect ( this , & MapWindow : : instructionsChanged , map_instructions , & MapInstructions : : updateInstructions ) ;
QObject : : connect ( this , & MapWindow : : distanceChanged , map_instructions , & MapInstructions : : updateDistance ) ;
map_instructions - > setFixedWidth ( width ( ) ) ;
map_instructions - > setVisible ( false ) ;
map_eta = new MapETA ( this ) ;
QObject : : connect ( this , & MapWindow : : ETAChanged , map_eta , & MapETA : : updateETA ) ;
const int h = 120 ;
map_eta - > setFixedHeight ( h ) ;
map_eta - > move ( 25 , 1080 - h - bdr_s * 2 ) ;
map_eta - > setVisible ( false ) ;
auto last_gps_position = coordinate_from_param ( " LastGPSPosition " ) ;
if ( last_gps_position ) {
last_position = * last_gps_position ;
}
grabGesture ( Qt : : GestureType : : PinchGesture ) ;
}
MapWindow : : ~ MapWindow ( ) {
makeCurrent ( ) ;
}
void MapWindow : : initLayers ( ) {
// This doesn't work from initializeGL
if ( ! m_map - > layerExists ( " modelPathLayer " ) ) {
qDebug ( ) < < " Initializing modelPathLayer " ;
QVariantMap modelPath ;
modelPath [ " id " ] = " modelPathLayer " ;
modelPath [ " type " ] = " line " ;
modelPath [ " source " ] = " modelPathSource " ;
m_map - > addLayer ( modelPath ) ;
m_map - > setPaintProperty ( " modelPathLayer " , " line-color " , QColor ( " red " ) ) ;
m_map - > setPaintProperty ( " modelPathLayer " , " line-width " , 5.0 ) ;
m_map - > setLayoutProperty ( " modelPathLayer " , " line-cap " , " round " ) ;
}
if ( ! m_map - > layerExists ( " navLayer " ) ) {
qDebug ( ) < < " Initializing navLayer " ;
QVariantMap nav ;
nav [ " id " ] = " navLayer " ;
nav [ " type " ] = " line " ;
nav [ " source " ] = " navSource " ;
m_map - > addLayer ( nav , " road-intersection " ) ;
m_map - > setPaintProperty ( " navLayer " , " line-color " , QColor ( " #31a1ee " ) ) ;
m_map - > setPaintProperty ( " navLayer " , " line-width " , 7.5 ) ;
m_map - > setLayoutProperty ( " navLayer " , " line-cap " , " round " ) ;
}
if ( ! m_map - > layerExists ( " carPosLayer " ) ) {
qDebug ( ) < < " Initializing carPosLayer " ;
m_map - > addImage ( " label-arrow " , QImage ( " ../assets/images/triangle.svg " ) ) ;
QVariantMap carPos ;
carPos [ " id " ] = " carPosLayer " ;
carPos [ " type " ] = " symbol " ;
carPos [ " source " ] = " carPosSource " ;
m_map - > addLayer ( carPos ) ;
m_map - > setLayoutProperty ( " carPosLayer " , " icon-pitch-alignment " , " map " ) ;
m_map - > setLayoutProperty ( " carPosLayer " , " icon-image " , " label-arrow " ) ;
m_map - > setLayoutProperty ( " carPosLayer " , " icon-size " , 0.5 ) ;
m_map - > setLayoutProperty ( " carPosLayer " , " icon-ignore-placement " , true ) ;
m_map - > setLayoutProperty ( " carPosLayer " , " icon-allow-overlap " , true ) ;
m_map - > setLayoutProperty ( " carPosLayer " , " symbol-sort-key " , 0 ) ;
}
}
void MapWindow : : timerUpdate ( ) {
if ( ! QUIState : : ui_state . scene . started ) {
return ;
}
update ( ) ;
if ( m_map . isNull ( ) ) {
return ;
}
sm - > update ( 0 ) ;
if ( sm - > updated ( " liveLocationKalman " ) ) {
auto location = ( * sm ) [ " liveLocationKalman " ] . getLiveLocationKalman ( ) ;
localizer_valid = location . getStatus ( ) = = cereal : : LiveLocationKalman : : Status : : VALID ;
if ( localizer_valid ) {
auto pos = location . getPositionGeodetic ( ) ;
auto orientation = location . getCalibratedOrientationNED ( ) ;
float velocity = location . getVelocityCalibrated ( ) . getValue ( ) [ 0 ] ;
float bearing = RAD2DEG ( orientation . getValue ( ) [ 2 ] ) ;
auto coordinate = QMapbox : : Coordinate ( pos . getValue ( ) [ 0 ] , pos . getValue ( ) [ 1 ] ) ;
last_position = coordinate ;
last_bearing = bearing ;
velocity_filter . update ( velocity ) ;
}
}
loaded_once = loaded_once | | m_map - > isFullyLoaded ( ) ;
if ( ! loaded_once ) {
map_instructions - > showError ( " Map Loading " ) ;
return ;
}
initLayers ( ) ;
if ( ! localizer_valid ) {
map_instructions - > showError ( " Waiting for GPS " ) ;
} else {
map_instructions - > noError ( ) ;
// Update current location marker
auto point = coordinate_to_collection ( * last_position ) ;
QMapbox : : Feature feature1 ( QMapbox : : Feature : : PointType , point , { } , { } ) ;
QVariantMap carPosSource ;
carPosSource [ " type " ] = " geojson " ;
carPosSource [ " data " ] = QVariant : : fromValue < QMapbox : : Feature > ( feature1 ) ;
m_map - > updateSource ( " carPosSource " , carPosSource ) ;
}
if ( pan_counter = = 0 ) {
if ( last_position ) m_map - > setCoordinate ( * last_position ) ;
if ( last_bearing ) m_map - > setBearing ( * last_bearing ) ;
} else {
pan_counter - - ;
}
if ( zoom_counter = = 0 ) {
m_map - > setZoom ( util : : map_val < float > ( velocity_filter . x ( ) , 0 , 30 , MAX_ZOOM , MIN_ZOOM ) ) ;
} else {
zoom_counter - - ;
}
if ( sm - > updated ( " navInstruction " ) ) {
if ( sm - > valid ( " navInstruction " ) ) {
auto i = ( * sm ) [ " navInstruction " ] . getNavInstruction ( ) ;
emit ETAChanged ( i . getTimeRemaining ( ) , i . getTimeRemainingTypical ( ) , i . getDistanceRemaining ( ) ) ;
if ( localizer_valid ) {
m_map - > setPitch ( MAX_PITCH ) ; // TODO: smooth pitching based on maneuver distance
emit distanceChanged ( i . getManeuverDistance ( ) ) ; // TODO: combine with instructionsChanged
emit instructionsChanged ( i ) ;
}
} else {
m_map - > setPitch ( MIN_PITCH ) ;
clearRoute ( ) ;
}
}
if ( sm - > updated ( " navRoute " ) ) {
auto route = ( * sm ) [ " navRoute " ] . getNavRoute ( ) ;
auto route_points = capnp_coordinate_list_to_collection ( route . getCoordinates ( ) ) ;
QMapbox : : Feature feature ( QMapbox : : Feature : : LineStringType , route_points , { } , { } ) ;
QVariantMap navSource ;
navSource [ " type " ] = " geojson " ;
navSource [ " data " ] = QVariant : : fromValue < QMapbox : : Feature > ( feature ) ;
m_map - > updateSource ( " navSource " , navSource ) ;
m_map - > setLayoutProperty ( " navLayer " , " visibility " , " visible " ) ;
// Only open the map on setting destination the first time
if ( allow_open ) {
setVisible ( true ) ; // Show map on destination set/change
allow_open = false ;
}
}
}
void MapWindow : : resizeGL ( int w , int h ) {
m_map - > resize ( size ( ) / MAP_SCALE ) ;
map_instructions - > setFixedWidth ( width ( ) ) ;
}
void MapWindow : : initializeGL ( ) {
m_map . reset ( new QMapboxGL ( this , m_settings , size ( ) , 1 ) ) ;
if ( last_position ) {
m_map - > setCoordinateZoom ( * last_position , MAX_ZOOM ) ;
} else {
m_map - > setCoordinateZoom ( QMapbox : : Coordinate ( 64.31990695292795 , - 149.79038934046247 ) , MIN_ZOOM ) ;
}
m_map - > setMargins ( { 0 , 350 , 0 , 50 } ) ;
m_map - > setPitch ( MIN_PITCH ) ;
m_map - > setStyleUrl ( " mapbox://styles/commaai/ckr64tlwp0azb17nqvr9fj13s " ) ;
QObject : : connect ( m_map . data ( ) , & QMapboxGL : : mapChanged , [ = ] ( QMapboxGL : : MapChange change ) {
if ( change = = QMapboxGL : : MapChange : : MapChangeDidFinishLoadingMap ) {
loaded_once = true ;
}
} ) ;
}
void MapWindow : : paintGL ( ) {
if ( ! isVisible ( ) | | m_map . isNull ( ) ) return ;
m_map - > render ( ) ;
}
void MapWindow : : clearRoute ( ) {
if ( ! m_map . isNull ( ) ) {
m_map - > setLayoutProperty ( " navLayer " , " visibility " , " none " ) ;
m_map - > setPitch ( MIN_PITCH ) ;
}
map_instructions - > hideIfNoError ( ) ;
map_eta - > setVisible ( false ) ;
allow_open = true ;
}
void MapWindow : : mousePressEvent ( QMouseEvent * ev ) {
m_lastPos = ev - > localPos ( ) ;
ev - > accept ( ) ;
}
void MapWindow : : mouseDoubleClickEvent ( QMouseEvent * ev ) {
if ( last_position ) m_map - > setCoordinate ( * last_position ) ;
if ( last_bearing ) m_map - > setBearing ( * last_bearing ) ;
m_map - > setZoom ( util : : map_val < float > ( velocity_filter . x ( ) , 0 , 30 , MAX_ZOOM , MIN_ZOOM ) ) ;
update ( ) ;
pan_counter = 0 ;
zoom_counter = 0 ;
}
void MapWindow : : mouseMoveEvent ( QMouseEvent * ev ) {
QPointF delta = ev - > localPos ( ) - m_lastPos ;
if ( ! delta . isNull ( ) ) {
pan_counter = PAN_TIMEOUT ;
m_map - > moveBy ( delta / MAP_SCALE ) ;
update ( ) ;
}
m_lastPos = ev - > localPos ( ) ;
ev - > accept ( ) ;
}
void MapWindow : : wheelEvent ( QWheelEvent * ev ) {
if ( ev - > orientation ( ) = = Qt : : Horizontal ) {
return ;
}
float factor = ev - > delta ( ) / 1200. ;
if ( ev - > delta ( ) < 0 ) {
factor = factor > - 1 ? factor : 1 / factor ;
}
m_map - > scaleBy ( 1 + factor , ev - > pos ( ) / MAP_SCALE ) ;
update ( ) ;
zoom_counter = PAN_TIMEOUT ;
ev - > accept ( ) ;
}
bool MapWindow : : event ( QEvent * event ) {
if ( event - > type ( ) = = QEvent : : Gesture ) {
return gestureEvent ( static_cast < QGestureEvent * > ( event ) ) ;
}
return QWidget : : event ( event ) ;
}
bool MapWindow : : gestureEvent ( QGestureEvent * event ) {
if ( QGesture * pinch = event - > gesture ( Qt : : PinchGesture ) ) {
pinchTriggered ( static_cast < QPinchGesture * > ( pinch ) ) ;
}
return true ;
}
void MapWindow : : pinchTriggered ( QPinchGesture * gesture ) {
QPinchGesture : : ChangeFlags changeFlags = gesture - > changeFlags ( ) ;
if ( changeFlags & QPinchGesture : : ScaleFactorChanged ) {
// TODO: figure out why gesture centerPoint doesn't work
m_map - > scaleBy ( gesture - > scaleFactor ( ) , { width ( ) / 2.0 / MAP_SCALE , height ( ) / 2.0 / MAP_SCALE } ) ;
update ( ) ;
zoom_counter = PAN_TIMEOUT ;
}
}
void MapWindow : : offroadTransition ( bool offroad ) {
if ( offroad ) {
clearRoute ( ) ;
} else {
auto dest = coordinate_from_param ( " NavDestination " ) ;
setVisible ( dest . has_value ( ) ) ;
}
last_bearing = { } ;
}
MapInstructions : : MapInstructions ( QWidget * parent ) : QWidget ( parent ) {
QHBoxLayout * main_layout = new QHBoxLayout ( this ) ;
main_layout - > setContentsMargins ( 11 , 50 , 11 , 11 ) ;
{
QVBoxLayout * layout = new QVBoxLayout ;
icon_01 = new QLabel ;
layout - > addWidget ( icon_01 ) ;
layout - > addStretch ( ) ;
main_layout - > addLayout ( layout ) ;
}
{
QVBoxLayout * layout = new QVBoxLayout ;
distance = new QLabel ;
distance - > setStyleSheet ( R " (font-size: 90px;) " ) ;
layout - > addWidget ( distance ) ;
primary = new QLabel ;
primary - > setStyleSheet ( R " (font-size: 60px;) " ) ;
primary - > setWordWrap ( true ) ;
layout - > addWidget ( primary ) ;
secondary = new QLabel ;
secondary - > setStyleSheet ( R " (font-size: 50px;) " ) ;
secondary - > setWordWrap ( true ) ;
layout - > addWidget ( secondary ) ;
lane_widget = new QWidget ;
lane_widget - > setFixedHeight ( 125 ) ;
lane_layout = new QHBoxLayout ( lane_widget ) ;
layout - > addWidget ( lane_widget ) ;
main_layout - > addLayout ( layout ) ;
}
setStyleSheet ( R " (
* {
color : white ;
font - family : " Inter " ;
}
) " );
QPalette pal = palette ( ) ;
pal . setColor ( QPalette : : Background , QColor ( 0 , 0 , 0 , 150 ) ) ;
setAutoFillBackground ( true ) ;
setPalette ( pal ) ;
}
void MapInstructions : : updateDistance ( float d ) {
d = std : : max ( d , 0.0f ) ;
QString distance_str ;
if ( QUIState : : ui_state . scene . is_metric ) {
if ( d > 500 ) {
distance_str . setNum ( d / 1000 , ' f ' , 1 ) ;
distance_str + = " km " ;
} else {
distance_str . setNum ( 50 * int ( d / 50 ) ) ;
distance_str + = " m " ;
}
} else {
float miles = d * METER_2_MILE ;
float feet = d * METER_2_FOOT ;
if ( feet > 500 ) {
distance_str . setNum ( miles , ' f ' , 1 ) ;
distance_str + = " mi " ;
} else {
distance_str . setNum ( 50 * int ( feet / 50 ) ) ;
distance_str + = " ft " ;
}
}
distance - > setAlignment ( Qt : : AlignLeft ) ;
distance - > setText ( distance_str ) ;
}
void MapInstructions : : showError ( QString error_text ) {
primary - > setText ( " " ) ;
distance - > setText ( error_text ) ;
distance - > setAlignment ( Qt : : AlignCenter ) ;
secondary - > setVisible ( false ) ;
icon_01 - > setVisible ( false ) ;
this - > error = true ;
lane_widget - > setVisible ( false ) ;
setVisible ( true ) ;
}
void MapInstructions : : noError ( ) {
error = false ;
}
void MapInstructions : : updateInstructions ( cereal : : NavInstruction : : Reader instruction ) {
// Word wrap widgets need fixed width
primary - > setFixedWidth ( width ( ) - 250 ) ;
secondary - > setFixedWidth ( width ( ) - 250 ) ;
// Show instruction text
QString primary_str = QString : : fromStdString ( instruction . getManeuverPrimaryText ( ) ) ;
QString secondary_str = QString : : fromStdString ( instruction . getManeuverSecondaryText ( ) ) ;
primary - > setText ( primary_str ) ;
secondary - > setVisible ( secondary_str . length ( ) > 0 ) ;
secondary - > setText ( secondary_str ) ;
// Show arrow with direction
QString type = QString : : fromStdString ( instruction . getManeuverType ( ) ) ;
QString modifier = QString : : fromStdString ( instruction . getManeuverModifier ( ) ) ;
if ( ! type . isEmpty ( ) ) {
QString fn = " ../assets/navigation/direction_ " + type ;
if ( ! modifier . isEmpty ( ) ) {
fn + = " _ " + modifier ;
}
fn + = + " .png " ;
fn = fn . replace ( ' ' , ' _ ' ) ;
QPixmap pix ( fn ) ;
icon_01 - > setPixmap ( pix . scaledToWidth ( 200 , Qt : : SmoothTransformation ) ) ;
icon_01 - > setSizePolicy ( QSizePolicy ( QSizePolicy : : Fixed , QSizePolicy : : Fixed ) ) ;
icon_01 - > setVisible ( true ) ;
}
// Show lanes
bool has_lanes = false ;
clearLayout ( lane_layout ) ;
for ( auto const & lane : instruction . getLanes ( ) ) {
has_lanes = true ;
bool active = lane . getActive ( ) ;
// TODO: only use active direction if active
bool left = false , straight = false , right = false ;
for ( auto const & direction : lane . getDirections ( ) ) {
left | = direction = = cereal : : NavInstruction : : Direction : : LEFT ;
right | = direction = = cereal : : NavInstruction : : Direction : : RIGHT ;
straight | = direction = = cereal : : NavInstruction : : Direction : : STRAIGHT ;
}
// TODO: Make more images based on active direction and combined directions
QString fn = " ../assets/navigation/direction_ " ;
if ( left ) {
fn + = " turn_left " ;
} else if ( right ) {
fn + = " turn_right " ;
} else if ( straight ) {
fn + = " turn_straight " ;
}
QPixmap pix ( fn + " .png " ) ;
auto icon = new QLabel ;
int wh = active ? 125 : 75 ;
icon - > setPixmap ( pix . scaled ( wh , wh , Qt : : IgnoreAspectRatio , Qt : : SmoothTransformation ) ) ;
icon - > setSizePolicy ( QSizePolicy ( QSizePolicy : : Fixed , QSizePolicy : : Fixed ) ) ;
lane_layout - > addWidget ( icon ) ;
}
lane_widget - > setVisible ( has_lanes ) ;
show ( ) ;
resize ( sizeHint ( ) ) ;
}
void MapInstructions : : hideIfNoError ( ) {
if ( ! error ) {
hide ( ) ;
}
}
MapETA : : MapETA ( QWidget * parent ) : QWidget ( parent ) {
QHBoxLayout * main_layout = new QHBoxLayout ( this ) ;
main_layout - > setContentsMargins ( 40 , 25 , 40 , 25 ) ;
{
QHBoxLayout * layout = new QHBoxLayout ;
eta = new QLabel ;
eta - > setAlignment ( Qt : : AlignCenter ) ;
eta - > setStyleSheet ( " font-weight:600 " ) ;
eta_unit = new QLabel ;
eta_unit - > setAlignment ( Qt : : AlignCenter ) ;
layout - > addWidget ( eta ) ;
layout - > addWidget ( eta_unit ) ;
main_layout - > addLayout ( layout ) ;
}
main_layout - > addSpacing ( 40 ) ;
{
QHBoxLayout * layout = new QHBoxLayout ;
time = new QLabel ;
time - > setAlignment ( Qt : : AlignCenter ) ;
time_unit = new QLabel ;
time_unit - > setAlignment ( Qt : : AlignCenter ) ;
layout - > addWidget ( time ) ;
layout - > addWidget ( time_unit ) ;
main_layout - > addLayout ( layout ) ;
}
main_layout - > addSpacing ( 40 ) ;
{
QHBoxLayout * layout = new QHBoxLayout ;
distance = new QLabel ;
distance - > setAlignment ( Qt : : AlignCenter ) ;
distance - > setStyleSheet ( " font-weight:600 " ) ;
distance_unit = new QLabel ;
distance_unit - > setAlignment ( Qt : : AlignCenter ) ;
layout - > addWidget ( distance ) ;
layout - > addWidget ( distance_unit ) ;
main_layout - > addLayout ( layout ) ;
}
setStyleSheet ( R " (
* {
color : white ;
font - family : " Inter " ;
font - size : 70 px ;
}
) " );
QPalette pal = palette ( ) ;
pal . setColor ( QPalette : : Background , QColor ( 0 , 0 , 0 , 150 ) ) ;
setAutoFillBackground ( true ) ;
setPalette ( pal ) ;
}
void MapETA : : updateETA ( float s , float s_typical , float d ) {
if ( d < MANEUVER_TRANSITION_THRESHOLD ) {
hide ( ) ;
return ;
}
// ETA
auto eta_time = QDateTime : : currentDateTime ( ) . addSecs ( s ) . time ( ) ;
if ( params . getBool ( " NavSettingTime24h " ) ) {
eta - > setText ( eta_time . toString ( " HH:mm " ) ) ;
eta_unit - > setText ( " eta " ) ;
} else {
auto t = eta_time . toString ( " h:mm a " ) . split ( ' ' ) ;
eta - > setText ( t [ 0 ] ) ;
eta_unit - > setText ( t [ 1 ] ) ;
}
// Remaining time
if ( s < 3600 ) {
time - > setText ( QString : : number ( int ( s / 60 ) ) ) ;
time_unit - > setText ( " min " ) ;
} else {
int hours = int ( s ) / 3600 ;
time - > setText ( QString : : number ( hours ) + " : " + QString : : number ( int ( ( s - hours * 3600 ) / 60 ) ) . rightJustified ( 2 , ' 0 ' ) ) ;
time_unit - > setText ( " hr " ) ;
}
QString color ;
if ( s / s_typical > 1.5 ) {
color = " #DA3025 " ;
} else if ( s / s_typical > 1.2 ) {
color = " #DAA725 " ;
} else {
color = " #25DA6E " ;
}
time - > setStyleSheet ( QString ( R " (color: %1; font-weight:600;) " ) . arg ( color ) ) ;
time_unit - > setStyleSheet ( QString ( R " (color: %1;) " ) . arg ( color ) ) ;
// Distance
QString distance_str ;
float num = 0 ;
if ( QUIState : : ui_state . scene . is_metric ) {
num = d / 1000.0 ;
distance_unit - > setText ( " km " ) ;
} else {
num = d * METER_2_MILE ;
distance_unit - > setText ( " mi " ) ;
}
distance_str . setNum ( num , ' f ' , num < 100 ? 1 : 0 ) ;
distance - > setText ( distance_str ) ;
show ( ) ;
adjustSize ( ) ;
repaint ( ) ;
adjustSize ( ) ;
// Rounded corners
const int radius = 25 ;
const auto r = rect ( ) ;
// Top corners rounded
QPainterPath path ;
path . setFillRule ( Qt : : WindingFill ) ;
path . addRoundedRect ( r , radius , radius ) ;
// Bottom corners not rounded
path . addRect ( r . marginsRemoved ( QMargins ( 0 , radius , 0 , 0 ) ) ) ;
// Set clipping mask
QRegion mask = QRegion ( path . simplified ( ) . toFillPolygon ( ) . toPolygon ( ) ) ;
setMask ( mask ) ;
// Center
move ( static_cast < QWidget * > ( parent ( ) ) - > width ( ) / 2 - width ( ) / 2 , 1080 - height ( ) - bdr_s * 2 ) ;
}