openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
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.

360 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);
}