ui/map: move `MapInstructions` & `MapETA` to separate files (#28976)

old-commit-hash: 942a2f9760
beeps
Dean Lee 2 years ago committed by GitHub
parent b120aec2f5
commit 7d5d1b1e70
  1. 3
      selfdrive/ui/SConscript
  2. 187
      selfdrive/ui/qt/maps/map.cc
  3. 41
      selfdrive/ui/qt/maps/map.h
  4. 55
      selfdrive/ui/qt/maps/map_eta.cc
  5. 23
      selfdrive/ui/qt/maps/map_eta.h
  6. 146
      selfdrive/ui/qt/maps/map_instructions.cc
  7. 27
      selfdrive/ui/qt/maps/map_instructions.h

@ -29,7 +29,8 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/
qt_env['CPPDEFINES'] = []
if maps:
base_libs += ['qmapboxgl']
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc"]
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc",
"qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"]
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)

@ -3,7 +3,6 @@
#include <eigen3/Eigen/Dense>
#include <QDebug>
#include <QDir>
#include "common/transformations/coordinates.hpp"
#include "selfdrive/ui/qt/maps/map_helpers.h"
@ -12,7 +11,6 @@
const int PAN_TIMEOUT = 100;
const float MANEUVER_TRANSITION_THRESHOLD = 10;
const float MAX_ZOOM = 17;
const float MIN_ZOOM = 14;
@ -20,8 +18,6 @@ const float MAX_PITCH = 50;
const float MIN_PITCH = 0;
const float MAP_SCALE = 2;
const QString ICON_SUFFIX = ".png";
MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05) {
QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState);
@ -428,186 +424,3 @@ void MapWindow::updateDestinationMarker() {
m_map->setPaintProperty("pinLayer", "icon-opacity", 1);
}
}
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
is_rhd = Params().getBool("IsRhdDetected");
QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11);
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
QWidget *right_container = new QWidget(this);
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout(right_container);
layout->addWidget(distance = new QLabel);
distance->setStyleSheet(R"(font-size: 90px;)");
layout->addWidget(primary = new QLabel);
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
layout->addWidget(secondary = new QLabel);
secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
layout->addLayout(lane_layout = new QHBoxLayout);
main_layout->addWidget(right_container);
setStyleSheet("color:white");
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
setAutoFillBackground(true);
setPalette(pal);
buildPixmapCache();
}
void MapInstructions::buildPixmapCache() {
QDir dir("../assets/navigation");
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) {
QPixmap pm(dir.filePath(fn));
QString key = fn.left(fn.size() - ICON_SUFFIX.length());
pm = pm.scaledToWidth(200, Qt::SmoothTransformation);
// Maneuver icons
pixmap_cache[key] = pm;
// lane direction icons
if (key.contains("turn_")) {
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
// for rhd, reflect direction and then flip
if (key.contains("_left")) {
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1));
} else if (key.contains("_right")) {
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1));
}
}
}
QString MapInstructions::getDistance(float d) {
d = std::max(d, 0.0f);
if (uiState()->scene.is_metric) {
return (d > 500) ? QString::number(d / 1000, 'f', 1) + tr(" km")
: QString::number(50 * int(d / 50)) + tr(" m");
} else {
float feet = d * METER_TO_FOOT;
return (feet > 500) ? QString::number(d * METER_TO_MILE, 'f', 1) + tr(" mi")
: QString::number(50 * int(feet / 50)) + tr(" ft");
}
}
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
setUpdatesEnabled(false);
// 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);
distance->setText(getDistance(instruction.getManeuverDistance()));
// Show arrow with direction
QString type = QString::fromStdString(instruction.getManeuverType());
QString modifier = QString::fromStdString(instruction.getManeuverModifier());
if (!type.isEmpty()) {
QString fn = "direction_" + type;
if (!modifier.isEmpty()) {
fn += "_" + modifier;
}
fn = fn.replace(' ', '_');
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right"));
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]);
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
icon_01->setVisible(true);
}
// Show lanes
auto lanes = instruction.getLanes();
for (int i = 0; i < lanes.size(); ++i) {
bool active = lanes[i].getActive();
// TODO: only use active direction if active
bool left = false, straight = false, right = false;
for (auto const &direction: lanes[i].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 = "lane_direction_";
if (left) {
fn += "turn_left";
} else if (right) {
fn += "turn_right";
} else if (straight) {
fn += "turn_straight";
}
if (!active) {
fn += "_inactive";
}
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel);
if (!label->parentWidget()) {
lane_layout->addWidget(label);
}
label->setPixmap(pixmap_cache[fn]);
label->setVisible(true);
}
for (int i = lanes.size(); i < lane_labels.size(); ++i) {
lane_labels[i]->setVisible(false);
}
setUpdatesEnabled(true);
setVisible(true);
}
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false);
setAttribute(Qt::WA_TranslucentBackground);
eta_doc.setUndoRedoEnabled(false);
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:60px;color:white;} b{font-size:70px;font-weight:600}");
}
void MapETA::paintEvent(QPaintEvent *event) {
if (!eta_doc.isEmpty()) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 150));
QSizeF txt_size = eta_doc.size();
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25);
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2);
eta_doc.drawContents(&p);
}
}
void MapETA::updateETA(float s, float s_typical, float d) {
// ETA
auto eta_t = QDateTime::currentDateTime().addSecs(s).time();
auto eta = format_24h ? std::array{eta_t.toString("HH:mm"), tr("eta")}
: std::array{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")};
// Remaining time
auto remaining = s < 3600 ? std::array{QString::number(int(s / 60)), tr("min")}
: std::array{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")};
QString color = "#25DA6E";
if (s / s_typical > 1.5) color = "#DA3025";
else if (s / s_typical > 1.2) color = "#DAA725";
// Distance
float num = uiState()->scene.is_metric ? (d / 1000.0) : (d * METER_TO_MILE);
auto distance = std::array{QString::number(num, 'f', num < 100 ? 1 : 0),
uiState()->scene.is_metric ? tr("km") : tr("mi")};
eta_doc.setHtml(QString(R"(<body><b>%1</b>%2 <span style="color:%3"><b>%4</b>%5</span> <b>%6</b>%7</body>)")
.arg(eta[0], eta[1], color, remaining[0], remaining[1], distance[0], distance[1]));
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD);
update();
}

@ -4,8 +4,6 @@
#include <QGeoCoordinate>
#include <QGestureEvent>
#include <QHash>
#include <QHBoxLayout>
#include <QLabel>
#include <QMap>
#include <QMapboxGL>
@ -15,7 +13,6 @@
#include <QPushButton>
#include <QScopedPointer>
#include <QString>
#include <QTextDocument>
#include <QVBoxLayout>
#include <QWheelEvent>
@ -23,42 +20,8 @@
#include "common/params.h"
#include "common/util.h"
#include "selfdrive/ui/ui.h"
class MapInstructions : public QWidget {
Q_OBJECT
private:
QLabel *distance;
QLabel *primary;
QLabel *secondary;
QLabel *icon_01;
QHBoxLayout *lane_layout;
bool is_rhd = false;
std::vector<QLabel *> lane_labels;
QHash<QString, QPixmap> pixmap_cache;
public:
MapInstructions(QWidget * parent=nullptr);
void buildPixmapCache();
QString getDistance(float d);
void updateInstructions(cereal::NavInstruction::Reader instruction);
};
class MapETA : public QWidget {
Q_OBJECT
public:
MapETA(QWidget * parent=nullptr);
void updateETA(float seconds, float seconds_typical, float distance);
private:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); }
bool format_24h = false;
QTextDocument eta_doc;
Params param;
};
#include "selfdrive/ui/qt/maps/map_eta.h"
#include "selfdrive/ui/qt/maps/map_instructions.h"
class MapWindow : public QOpenGLWidget {
Q_OBJECT

@ -0,0 +1,55 @@
#include "selfdrive/ui/qt/maps/map_eta.h"
#include <QDateTime>
#include <QPainter>
#include "selfdrive/ui/ui.h"
const float MANEUVER_TRANSITION_THRESHOLD = 10;
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false);
setAttribute(Qt::WA_TranslucentBackground);
eta_doc.setUndoRedoEnabled(false);
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:60px;color:white;} b{font-size:70px;font-weight:600}");
}
void MapETA::paintEvent(QPaintEvent *event) {
if (!eta_doc.isEmpty()) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 150));
QSizeF txt_size = eta_doc.size();
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25);
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2);
eta_doc.drawContents(&p);
}
}
void MapETA::updateETA(float s, float s_typical, float d) {
// ETA
auto eta_t = QDateTime::currentDateTime().addSecs(s).time();
auto eta = format_24h ? std::array{eta_t.toString("HH:mm"), tr("eta")}
: std::array{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")};
// Remaining time
auto remaining = s < 3600 ? std::array{QString::number(int(s / 60)), tr("min")}
: std::array{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")};
QString color = "#25DA6E";
if (s / s_typical > 1.5)
color = "#DA3025";
else if (s / s_typical > 1.2)
color = "#DAA725";
// Distance
float num = uiState()->scene.is_metric ? (d / 1000.0) : (d * METER_TO_MILE);
auto distance = std::array{QString::number(num, 'f', num < 100 ? 1 : 0),
uiState()->scene.is_metric ? tr("km") : tr("mi")};
eta_doc.setHtml(QString(R"(<body><b>%1</b>%2 <span style="color:%3"><b>%4</b>%5</span> <b>%6</b>%7</body>)")
.arg(eta[0], eta[1], color, remaining[0], remaining[1], distance[0], distance[1]));
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD);
update();
}

@ -0,0 +1,23 @@
#pragma once
#include <QPaintEvent>
#include <QTextDocument>
#include <QWidget>
#include "common/params.h"
class MapETA : public QWidget {
Q_OBJECT
public:
MapETA(QWidget * parent=nullptr);
void updateETA(float seconds, float seconds_typical, float distance);
private:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); }
bool format_24h = false;
QTextDocument eta_doc;
Params param;
};

@ -0,0 +1,146 @@
#include "selfdrive/ui/qt/maps/map_instructions.h"
#include <QDir>
#include <QVBoxLayout>
#include "selfdrive/ui/ui.h"
const QString ICON_SUFFIX = ".png";
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
is_rhd = Params().getBool("IsRhdDetected");
QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11);
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
QWidget *right_container = new QWidget(this);
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout(right_container);
layout->addWidget(distance = new QLabel);
distance->setStyleSheet(R"(font-size: 90px;)");
layout->addWidget(primary = new QLabel);
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
layout->addWidget(secondary = new QLabel);
secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
layout->addLayout(lane_layout = new QHBoxLayout);
main_layout->addWidget(right_container);
setStyleSheet("color:white");
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
setAutoFillBackground(true);
setPalette(pal);
buildPixmapCache();
}
void MapInstructions::buildPixmapCache() {
QDir dir("../assets/navigation");
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) {
QPixmap pm(dir.filePath(fn));
QString key = fn.left(fn.size() - ICON_SUFFIX.length());
pm = pm.scaledToWidth(200, Qt::SmoothTransformation);
// Maneuver icons
pixmap_cache[key] = pm;
// lane direction icons
if (key.contains("turn_")) {
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
// for rhd, reflect direction and then flip
if (key.contains("_left")) {
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1));
} else if (key.contains("_right")) {
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1));
}
}
}
QString MapInstructions::getDistance(float d) {
d = std::max(d, 0.0f);
if (uiState()->scene.is_metric) {
return (d > 500) ? QString::number(d / 1000, 'f', 1) + tr(" km")
: QString::number(50 * int(d / 50)) + tr(" m");
} else {
float feet = d * METER_TO_FOOT;
return (feet > 500) ? QString::number(d * METER_TO_MILE, 'f', 1) + tr(" mi")
: QString::number(50 * int(feet / 50)) + tr(" ft");
}
}
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
setUpdatesEnabled(false);
// 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);
distance->setText(getDistance(instruction.getManeuverDistance()));
// Show arrow with direction
QString type = QString::fromStdString(instruction.getManeuverType());
QString modifier = QString::fromStdString(instruction.getManeuverModifier());
if (!type.isEmpty()) {
QString fn = "direction_" + type;
if (!modifier.isEmpty()) {
fn += "_" + modifier;
}
fn = fn.replace(' ', '_');
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right"));
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]);
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
icon_01->setVisible(true);
}
// Show lanes
auto lanes = instruction.getLanes();
for (int i = 0; i < lanes.size(); ++i) {
bool active = lanes[i].getActive();
// TODO: only use active direction if active
bool left = false, straight = false, right = false;
for (auto const &direction : lanes[i].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 = "lane_direction_";
if (left) {
fn += "turn_left";
} else if (right) {
fn += "turn_right";
} else if (straight) {
fn += "turn_straight";
}
if (!active) {
fn += "_inactive";
}
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel);
if (!label->parentWidget()) {
lane_layout->addWidget(label);
}
label->setPixmap(pixmap_cache[fn]);
label->setVisible(true);
}
for (int i = lanes.size(); i < lane_labels.size(); ++i) {
lane_labels[i]->setVisible(false);
}
setUpdatesEnabled(true);
setVisible(true);
}

@ -0,0 +1,27 @@
#pragma once
#include <QHash>
#include <QHBoxLayout>
#include <QLabel>
#include "cereal/gen/cpp/log.capnp.h"
class MapInstructions : public QWidget {
Q_OBJECT
private:
QLabel *distance;
QLabel *primary;
QLabel *secondary;
QLabel *icon_01;
QHBoxLayout *lane_layout;
bool is_rhd = false;
std::vector<QLabel *> lane_labels;
QHash<QString, QPixmap> pixmap_cache;
public:
MapInstructions(QWidget * parent=nullptr);
void buildPixmapCache();
QString getDistance(float d);
void updateInstructions(cereal::NavInstruction::Reader instruction);
};
Loading…
Cancel
Save