diff --git a/.gitignore b/.gitignore index 7f4a21895d..ef8ea75368 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ data/resources-*_design/* data/drules_proto_design.bin data/colors_design.txt data/patterns_design.txt +data/route_points.dat # Compiled Python *.pyc diff --git a/map/routing_manager.cpp b/map/routing_manager.cpp index f28af80aa1..2a5c78fc32 100644 --- a/map/routing_manager.cpp +++ b/map/routing_manager.cpp @@ -24,12 +24,19 @@ #include "storage/country_info_getter.hpp" #include "storage/storage.hpp" +#include "coding/file_reader.hpp" +#include "coding/file_writer.hpp" +#include "coding/multilang_utf8_string.hpp" + #include "platform/country_file.hpp" #include "platform/mwm_traits.hpp" #include "platform/platform.hpp" #include "platform/socket.hpp" +#include "base/scope_guard.hpp" + #include "3party/Alohalytics/src/alohalytics.h" +#include "3party/jansson/myjansson.hpp" #include @@ -41,6 +48,8 @@ char const kRouterTypeKey[] = "router"; double const kRouteScaleMultiplier = 1.5; +std::string const kRoutePointsFile = "route_points.dat"; + uint32_t constexpr kInvalidTransactionId = 0; void FillTurnsDistancesForRendering(std::vector const & segments, @@ -72,6 +81,121 @@ void FillTrafficForRendering(std::vector const & segments for (auto const & s : segments) traffic.push_back(s.GetTraffic()); } + +RouteMarkData GetLastPassedPoint(std::vector const & points) +{ + ASSERT_GREATER_OR_EQUAL(points.size(), 2, ()); + ASSERT(points[0].m_pointType == RouteMarkType::Start, ()); + RouteMarkData data = points[0]; + + for (int i = static_cast(points.size()) - 1; i >= 0; i--) + { + if (points[i].m_isPassed) + { + data = points[i]; + break; + } + } + + // Last passed point will be considered as start point. + data.m_pointType = RouteMarkType::Start; + data.m_intermediateIndex = 0; + if (data.m_isMyPosition) + { + MyPositionMarkPoint * myPosMark = UserMarkContainer::UserMarkForMyPostion(); + data.m_position = myPosMark->GetPivot(); + data.m_isMyPosition = false; + } + + return data; +} + +void SerializeRoutePoint(json_t * node, RouteMarkData const & data) +{ + ASSERT(node != nullptr, ()); + ToJSONObject(*node, "type", static_cast(data.m_pointType)); + ToJSONObject(*node, "title", data.m_title); + ToJSONObject(*node, "subtitle", data.m_subTitle); + ToJSONObject(*node, "x", data.m_position.x); + ToJSONObject(*node, "y", data.m_position.y); +} + +RouteMarkData DeserializeRoutePoint(json_t * node) +{ + ASSERT(node != nullptr, ()); + RouteMarkData data; + + int type = 0; + FromJSONObject(node, "type", type); + data.m_pointType = static_cast(type); + + FromJSONObject(node, "title", data.m_title); + FromJSONObject(node, "subtitle", data.m_subTitle); + + FromJSONObject(node, "x", data.m_position.x); + FromJSONObject(node, "y", data.m_position.y); + + return data; +} + +std::string SerializeRoutePoints(std::vector const & points) +{ + ASSERT_GREATER_OR_EQUAL(points.size(), 2, ()); + auto pointsNode = my::NewJSONArray(); + + // Save last passed point. It will be used on points loading if my position + // isn't determined. + auto lastPassedPoint = GetLastPassedPoint(points); + auto lastPassedNode = my::NewJSONObject(); + SerializeRoutePoint(lastPassedNode.get(), lastPassedPoint); + json_array_append_new(pointsNode.get(), lastPassedNode.release()); + + for (auto const & p : points) + { + // Here we skip passed points and the start point. + if (p.m_isPassed || p.m_pointType == RouteMarkType::Start) + continue; + + auto pointNode = my::NewJSONObject(); + SerializeRoutePoint(pointNode.get(), p); + json_array_append_new(pointsNode.get(), pointNode.release()); + } + std::unique_ptr buffer( + json_dumps(pointsNode.get(), JSON_COMPACT | JSON_ENSURE_ASCII)); + return std::string(buffer.get()); +} + +std::vector DeserializeRoutePoints(std::string const & data) +{ + my::Json root(data.c_str()); + + if (root.get() == nullptr || !json_is_array(root.get())) + return {}; + + size_t const sz = json_array_size(root.get()); + if (sz == 0) + return {}; + + std::vector result; + result.reserve(sz); + for (size_t i = 0; i < sz; ++i) + { + auto pointNode = json_array_get(root.get(), i); + if (pointNode == nullptr) + continue; + + auto point = DeserializeRoutePoint(pointNode); + if (point.m_position.EqualDxDy(m2::PointD::Zero(), 1e-7)) + continue; + + result.push_back(std::move(point)); + } + + if (result.size() < 2) + return {}; + + return result; +} } // namespace namespace marketing @@ -698,6 +822,7 @@ void RoutingManager::OnLocationUpdate(location::GpsInfo & info) if (m_drapeEngine != nullptr) m_drapeEngine->SetGpsInfo(info, m_routingSession.IsNavigable(), routeMatchingInfo); + if (IsTrackingReporterEnabled()) m_trackingReporter.AddLocation(info, m_routingSession.MatchTraffic(routeMatchingInfo)); } @@ -817,7 +942,7 @@ uint32_t RoutingManager::GenerateRoutePointsTransactionId() const uint32_t RoutingManager::OpenRoutePointsTransaction() { auto const id = GenerateRoutePointsTransactionId(); - m_routePointsTransactions[id].m_routeMarks = std::move(GetRoutePoints()); + m_routePointsTransactions[id].m_routeMarks = GetRoutePoints(); return id; } @@ -862,3 +987,78 @@ void RoutingManager::CancelRoutePointsTransaction(uint32_t transactionId) routePoints.AddRoutePoint(std::move(markData)); routePoints.NotifyChanges(); } + +bool RoutingManager::HasSavedRoutePoints() const +{ + auto const fileName = GetPlatform().SettingsPathForFile(kRoutePointsFile); + return GetPlatform().IsFileExistsByFullPath(fileName); +} + +bool RoutingManager::LoadRoutePoints() +{ + if (!HasSavedRoutePoints()) + return false; + + // Delete file after loading. + auto const fileName = GetPlatform().SettingsPathForFile(kRoutePointsFile); + MY_SCOPE_GUARD(routePointsFileGuard, std::bind(&FileWriter::DeleteFileX, + std::cref(fileName))); + + std::string data; + try + { + ReaderPtr(GetPlatform().GetReader(fileName)).ReadAsString(data); + } + catch (RootException const & ex) + { + LOG(LWARNING, ("Loading road points failed:", ex.Msg())); + return false; + } + + auto points = DeserializeRoutePoints(data); + if (points.empty()) + return false; + + // If we have found my position, we use my position as start point. + MyPositionMarkPoint * myPosMark = UserMarkContainer::UserMarkForMyPostion(); + ASSERT(m_bmManager != nullptr, ()); + m_bmManager->GetUserMarksController(UserMarkType::ROUTING_MARK).Clear(); + for (auto & p : points) + { + if (p.m_pointType == RouteMarkType::Start && myPosMark->HasPosition()) + { + RouteMarkData startPt; + startPt.m_pointType = RouteMarkType::Start; + startPt.m_isMyPosition = true; + startPt.m_position = myPosMark->GetPivot(); + AddRoutePoint(std::move(startPt)); + } + else + { + AddRoutePoint(std::move(p)); + } + } + return true; +} + +void RoutingManager::SaveRoutePoints() const +{ + auto points = GetRoutePoints(); + if (points.size() < 2) + return; + + if (points.back().m_isPassed) + return; + + try + { + auto const fileName = GetPlatform().SettingsPathForFile(kRoutePointsFile); + FileWriter writer(fileName); + std::string const pointsData = SerializeRoutePoints(points); + writer.Write(pointsData.c_str(), pointsData.length()); + } + catch (RootException const & ex) + { + LOG(LWARNING, ("Saving road points failed:", ex.Msg())); + } +} diff --git a/map/routing_manager.hpp b/map/routing_manager.hpp index 4246d7c002..053aee10e4 100644 --- a/map/routing_manager.hpp +++ b/map/routing_manager.hpp @@ -221,6 +221,14 @@ public: void CancelRoutePointsTransaction(uint32_t transactionId); static uint32_t InvalidRoutePointsTransactionId(); + /// \returns true if there are route points saved in file and false otherwise. + bool HasSavedRoutePoints() const; + /// \brief It loads road points from file and delete file after loading. + /// \returns true if route points loaded and false otherwise. + bool LoadRoutePoints(); + /// \brief It saves route points to file. + void SaveRoutePoints() const; + private: void InsertRoute(routing::Route const & route); bool IsTrackingReporterEnabled() const; diff --git a/map/routing_mark.hpp b/map/routing_mark.hpp index 3104116a28..59c85a01d0 100644 --- a/map/routing_mark.hpp +++ b/map/routing_mark.hpp @@ -6,6 +6,7 @@ enum class RouteMarkType : uint8_t { + // Do not change the order! Start = 0, Intermediate = 1, Finish = 2 diff --git a/qt/draw_widget.cpp b/qt/draw_widget.cpp index 24b405e954..467cab3638 100644 --- a/qt/draw_widget.cpp +++ b/qt/draw_widget.cpp @@ -7,8 +7,6 @@ #include "map/framework.hpp" -#include "drape_frontend/visual_params.hpp" - #include "search/result.hpp" #include "storage/index.hpp" @@ -114,6 +112,8 @@ void DrawWidget::PrepareShutdown() auto & routingManager = m_framework.GetRoutingManager(); if (routingManager.IsRoutingActive() && routingManager.IsRoutingFollowing()) { + routingManager.SaveRoutePoints(); + auto style = m_framework.GetMapStyle(); if (style == MapStyle::MapStyleVehicleClear) m_framework.MarkMapStyle(MapStyle::MapStyleClear); @@ -150,6 +150,10 @@ void DrawWidget::initializeGL() { MapWidget::initializeGL(); m_framework.LoadBookmarks(); + + auto & routingManager = m_framework.GetRoutingManager(); + if (routingManager.LoadRoutePoints()) + routingManager.BuildRoute(0 /* timeoutSec */); } void DrawWidget::mousePressEvent(QMouseEvent * e)