You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
359 lines
11 KiB
359 lines
11 KiB
#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);
|
|
}
|
|
|