diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt
index 029698d24..2db32693a 100644
--- a/traffxml/CMakeLists.txt
+++ b/traffxml/CMakeLists.txt
@@ -16,3 +16,7 @@ target_link_libraries(${PROJECT_NAME}
openlr
coding
)
+
+if (NOT SKIP_QT_GUI)
+ omim_add_tool_subdirectory(traff_assessment_tool)
+endif()
diff --git a/traffxml/traff_assessment_tool/CMakeLists.txt b/traffxml/traff_assessment_tool/CMakeLists.txt
new file mode 100644
index 000000000..5ff1c6cc9
--- /dev/null
+++ b/traffxml/traff_assessment_tool/CMakeLists.txt
@@ -0,0 +1,30 @@
+project(traff_assessment_tool)
+
+set(SRC
+ main.cpp
+ mainwindow.cpp
+ mainwindow.hpp
+ map_widget.cpp
+ map_widget.hpp
+ points_controller_delegate_base.hpp
+ traffic_drawer_delegate_base.hpp
+ traffic_mode.cpp
+ traffic_mode.hpp
+ traffic_panel.cpp
+ traffic_panel.hpp
+ trafficmodeinitdlg.cpp
+ trafficmodeinitdlg.h
+ trafficmodeinitdlg.ui
+)
+
+omim_add_executable(${PROJECT_NAME} ${SRC})
+
+set_target_properties(${PROJECT_NAME} PROPERTIES AUTOUIC ON AUTOMOC ON)
+
+target_link_libraries(${PROJECT_NAME}
+ openlr
+ qt_common
+ map
+ gflags::gflags
+ traffxml
+)
diff --git a/traffxml/traff_assessment_tool/Info.plist b/traffxml/traff_assessment_tool/Info.plist
new file mode 100644
index 000000000..384699b4b
--- /dev/null
+++ b/traffxml/traff_assessment_tool/Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ NSPrincipalClass
+ NSApplication
+
+ NSHighResolutionCapable
+ True
+
+
diff --git a/traffxml/traff_assessment_tool/main.cpp b/traffxml/traff_assessment_tool/main.cpp
new file mode 100644
index 000000000..810d7bb4f
--- /dev/null
+++ b/traffxml/traff_assessment_tool/main.cpp
@@ -0,0 +1,41 @@
+#include "mainwindow.hpp"
+
+#include "qt/qt_common/helpers.hpp"
+
+#include "map/framework.hpp"
+
+#include
+
+#include
+
+namespace
+{
+DEFINE_string(resources_path, "", "Path to resources directory");
+DEFINE_string(data_path, "", "Path to data directory");
+} // namespace
+
+int main(int argc, char * argv[])
+{
+ gflags::SetUsageMessage("Visualize and check matched routes.");
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+ Platform & platform = GetPlatform();
+ if (!FLAGS_resources_path.empty())
+ platform.SetResourceDir(FLAGS_resources_path);
+ if (!FLAGS_data_path.empty())
+ platform.SetWritableDirForTests(FLAGS_data_path);
+
+ Q_INIT_RESOURCE(resources_common);
+ QApplication app(argc, argv);
+
+ qt::common::SetDefaultSurfaceFormat(app.platformName());
+
+ FrameworkParams params;
+
+ Framework framework(params);
+ traffxml::MainWindow mainWindow(framework);
+
+ mainWindow.showMaximized();
+
+ return app.exec();
+}
diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp
new file mode 100644
index 000000000..e9d51da06
--- /dev/null
+++ b/traffxml/traff_assessment_tool/mainwindow.cpp
@@ -0,0 +1,422 @@
+#include "traffxml/traff_assessment_tool/mainwindow.hpp"
+
+#include "traffxml/traff_assessment_tool/map_widget.hpp"
+#include "traffxml/traff_assessment_tool/points_controller_delegate_base.hpp"
+#include "traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp"
+#include "traffxml/traff_assessment_tool/traffic_panel.hpp"
+#include "traffxml/traff_assessment_tool/trafficmodeinitdlg.h"
+
+#include "map/framework.hpp"
+
+#include "drape_frontend/drape_api.hpp"
+
+#include "routing/data_source.hpp"
+#include "routing/features_road_graph.hpp"
+#include "routing/road_graph.hpp"
+
+#include "routing_common/car_model.hpp"
+
+#include "storage/country_parent_getter.hpp"
+
+#include "geometry/mercator.hpp"
+#include "geometry/point2d.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace traffxml
+{
+namespace
+{
+class TrafficDrawerDelegate : public TrafficDrawerDelegateBase
+{
+ static constexpr char const * kEncodedLineId = "encodedPath";
+ static constexpr char const * kDecodedLineId = "decodedPath";
+ static constexpr char const * kGoldenLineId = "goldenPath";
+
+public:
+ explicit TrafficDrawerDelegate(Framework & framework)
+ : m_framework(framework)
+ , m_drapeApi(m_framework.GetDrapeApi())
+ , m_bm(framework.GetBookmarkManager())
+ {
+ }
+
+ void SetViewportCenter(m2::PointD const & center) override
+ {
+ m_framework.SetViewportCenter(center);
+ }
+
+ void DrawDecodedSegments(std::vector const & points) override
+ {
+ CHECK(!points.empty(), ("Points must not be empty."));
+
+ LOG(LINFO, ("Decoded segment", points));
+ m_drapeApi.AddLine(kDecodedLineId,
+ df::DrapeApiLineData(points, dp::Color(0, 0, 255, 255))
+ .Width(3.0f).ShowPoints(true /* markPoints */));
+ }
+
+ void DrawEncodedSegment(std::vector const & points) override
+ {
+ LOG(LINFO, ("Encoded segment", points));
+ m_drapeApi.AddLine(kEncodedLineId,
+ df::DrapeApiLineData(points, dp::Color(255, 0, 0, 255))
+ .Width(3.0f).ShowPoints(true /* markPoints */));
+ }
+
+ void DrawGoldenPath(std::vector const & points) override
+ {
+ m_drapeApi.AddLine(kGoldenLineId,
+ df::DrapeApiLineData(points, dp::Color(255, 127, 36, 255))
+ .Width(4.0f).ShowPoints(true /* markPoints */));
+ }
+
+ void ClearGoldenPath() override
+ {
+ m_drapeApi.RemoveLine(kGoldenLineId);
+ }
+
+ void ClearAllPaths() override
+ {
+ m_drapeApi.Clear();
+ }
+
+ void VisualizePoints(std::vector const & points) override
+ {
+ auto editSession = m_bm.GetEditSession();
+ editSession.SetIsVisible(UserMark::Type::DEBUG_MARK, true);
+ for (auto const & p : points)
+ editSession.CreateUserMark(p);
+ }
+
+ void ClearAllVisualizedPoints() override
+ {
+ m_bm.GetEditSession().ClearGroup(UserMark::Type::DEBUG_MARK);
+ }
+
+private:
+ Framework & m_framework;
+ df::DrapeApi & m_drapeApi;
+ BookmarkManager & m_bm;
+};
+
+bool PointsMatch(m2::PointD const & a, m2::PointD const & b)
+{
+ auto constexpr kToleranceDistanceM = 1.0;
+ return mercator::DistanceOnEarth(a, b) < kToleranceDistanceM;
+}
+
+class PointsControllerDelegate : public PointsControllerDelegateBase
+{
+public:
+ explicit PointsControllerDelegate(Framework & framework)
+ : m_framework(framework)
+ , m_dataSource(const_cast(GetDataSource()), nullptr /* numMwmIDs */)
+ , m_roadGraph(m_dataSource, routing::IRoadGraph::Mode::ObeyOnewayTag,
+ std::make_unique(storage::CountryParentGetter{}))
+ {
+ }
+
+ std::vector GetAllJunctionPointsInViewport() const override
+ {
+ std::vector points;
+ auto const & rect = m_framework.GetCurrentViewport();
+ auto const pushPoint = [&points, &rect](m2::PointD const & point)
+ {
+ if (!rect.IsPointInside(point))
+ return;
+ for (auto const & p : points)
+ {
+ if (PointsMatch(point, p))
+ return;
+ }
+ points.push_back(point);
+ };
+
+ auto const pushFeaturePoints = [&pushPoint](FeatureType & ft)
+ {
+ if (ft.GetGeomType() != feature::GeomType::Line)
+ return;
+
+ /// @todo Transported (railway=rail) are also present here :)
+ auto const roadClass = ftypes::GetHighwayClass(feature::TypesHolder(ft));
+ if (roadClass == ftypes::HighwayClass::Undefined ||
+ roadClass == ftypes::HighwayClass::Pedestrian)
+ {
+ return;
+ }
+ ft.ForEachPoint(pushPoint, scales::GetUpperScale());
+ };
+
+ GetDataSource().ForEachInRect(pushFeaturePoints, rect, scales::GetUpperScale());
+ return points;
+ }
+
+ std::pair, m2::PointD> GetCandidatePoints(
+ m2::PointD const & p) const override
+ {
+ auto constexpr kInvalidIndex = std::numeric_limits::max();
+
+ std::vector points;
+ m2::PointD pointOnFt;
+ indexer::ForEachFeatureAtPoint(GetDataSource(), [&points, &p, &pointOnFt](FeatureType & ft)
+ {
+ if (ft.GetGeomType() != feature::GeomType::Line)
+ return;
+
+ ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
+
+ auto minDistance = std::numeric_limits::max();
+ auto bestPointIndex = kInvalidIndex;
+ for (size_t i = 0; i < ft.GetPointsCount(); ++i)
+ {
+ auto const & fp = ft.GetPoint(i);
+ auto const distance = mercator::DistanceOnEarth(fp, p);
+ if (PointsMatch(fp, p) && distance < minDistance)
+ {
+ bestPointIndex = i;
+ minDistance = distance;
+ }
+ }
+
+ if (bestPointIndex != kInvalidIndex)
+ {
+ points.emplace_back(ft.GetID(), bestPointIndex);
+ pointOnFt = ft.GetPoint(bestPointIndex);
+ }
+ }, p);
+
+ return std::make_pair(points, pointOnFt);
+ }
+
+ std::vector GetReachablePoints(m2::PointD const & p) const override
+ {
+ routing::FeaturesRoadGraph::EdgeListT edges;
+ m_roadGraph.GetOutgoingEdges(geometry::PointWithAltitude(p, geometry::kDefaultAltitudeMeters),
+ edges);
+
+ std::vector points;
+ for (auto const & e : edges)
+ points.push_back(e.GetEndJunction().GetPoint());
+ return points;
+ }
+
+ ClickType CheckClick(m2::PointD const & clickPoint,
+ m2::PointD const & lastClickedPoint,
+ std::vector const & reachablePoints) const override
+ {
+ // == Comparison is safe here since |clickPoint| is adjusted by GetFeaturesPointsByPoint
+ // so to be equal the closest feature's one.
+ if (clickPoint == lastClickedPoint)
+ return ClickType::Remove;
+ for (auto const & p : reachablePoints)
+ {
+ if (PointsMatch(clickPoint, p))
+ return ClickType::Add;
+ }
+ return ClickType::Miss;
+ }
+
+private:
+ DataSource const & GetDataSource() const { return m_framework.GetDataSource(); }
+
+ Framework & m_framework;
+ routing::MwmDataSource m_dataSource;
+ routing::FeaturesRoadGraph m_roadGraph;
+};
+} // namespace
+
+
+MainWindow::MainWindow(Framework & framework)
+ : m_framework(framework)
+{
+ m_mapWidget = new MapWidget(m_framework, this /* parent */);
+
+ m_layout = new QHBoxLayout();
+ m_layout->addWidget(m_mapWidget);
+
+ auto * window = new QWidget();
+ window->setLayout(m_layout);
+ window->setGraphicsEffect(nullptr);
+
+ setCentralWidget(window);
+
+ setWindowTitle(tr("Organic Maps"));
+ setWindowIcon(QIcon(":/ui/logo.png"));
+
+ QMenu * fileMenu = new QMenu("File", this);
+ menuBar()->addMenu(fileMenu);
+
+ fileMenu->addAction("Open sample", QKeySequence("Ctrl+O"), this, &MainWindow::OnOpenTrafficSample);
+
+ m_closeTrafficSampleAction = fileMenu->addAction("Close sample", QKeySequence("Ctrl+W"), this, &MainWindow::OnCloseTrafficSample);
+ m_saveTrafficSampleAction = fileMenu->addAction("Save sample", QKeySequence("Ctrl+S"), this, &MainWindow::OnSaveTrafficSample);
+
+ fileMenu->addSeparator();
+
+#ifdef openlr_obsolete
+ m_goldifyMatchedPathAction = fileMenu->addAction("Goldify", QKeySequence("Ctrl+G"), [this] { m_trafficMode->GoldifyMatchedPath(); });
+ m_startEditingAction = fileMenu->addAction("Edit", QKeySequence("Ctrl+E"),
+ [this] {
+ m_trafficMode->StartBuildingPath();
+ m_mapWidget->SetMode(MapWidget::Mode::TrafficMarkup);
+ m_commitPathAction->setEnabled(true /* enabled */);
+ m_cancelPathAction->setEnabled(true /* enabled */);
+ });
+ m_commitPathAction = fileMenu->addAction("Accept path",
+ QKeySequence("Ctrl+A"),
+ [this] {
+ m_trafficMode->CommitPath();
+ m_mapWidget->SetMode(MapWidget::Mode::Normal);
+ });
+ m_cancelPathAction = fileMenu->addAction("Revert path",
+ QKeySequence("Ctrl+R"),
+ [this] {
+ m_trafficMode->RollBackPath();
+ m_mapWidget->SetMode(MapWidget::Mode::Normal);
+ });
+ m_ignorePathAction = fileMenu->addAction("Ignore path",
+ QKeySequence("Ctrl+I"),
+ [this] {
+ m_trafficMode->IgnorePath();
+ m_mapWidget->SetMode(MapWidget::Mode::Normal);
+ });
+
+ m_goldifyMatchedPathAction->setEnabled(false /* enabled */);
+ m_closeTrafficSampleAction->setEnabled(false /* enabled */);
+ m_saveTrafficSampleAction->setEnabled(false /* enabled */);
+ m_startEditingAction->setEnabled(false /* enabled */);
+ m_commitPathAction->setEnabled(false /* enabled */);
+ m_cancelPathAction->setEnabled(false /* enabled */);
+ m_ignorePathAction->setEnabled(false /* enabled */);
+#endif
+}
+
+void MainWindow::CreateTrafficPanel(std::string const & dataFilePath)
+{
+ m_trafficMode = new TrafficMode(dataFilePath,
+ m_framework.GetDataSource(),
+ std::make_unique(m_framework),
+ std::make_unique(m_framework));
+
+ connect(m_mapWidget, &MapWidget::TrafficMarkupClick,
+ m_trafficMode, &TrafficMode::OnClick);
+ connect(m_trafficMode, &TrafficMode::EditingStopped,
+ this, &MainWindow::OnPathEditingStop);
+ connect(m_trafficMode, &TrafficMode::SegmentSelected,
+ [](int segmentId) { QApplication::clipboard()->setText(QString::number(segmentId)); });
+
+ m_docWidget = new QDockWidget(tr("Routes"), this);
+ addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_docWidget);
+
+ m_docWidget->setWidget(new TrafficPanel(m_trafficMode, m_docWidget));
+
+ m_docWidget->adjustSize();
+ m_docWidget->setMinimumWidth(400);
+ m_docWidget->show();
+}
+
+void MainWindow::DestroyTrafficPanel()
+{
+ removeDockWidget(m_docWidget);
+ delete m_docWidget;
+ m_docWidget = nullptr;
+
+ delete m_trafficMode;
+ m_trafficMode = nullptr;
+
+ m_mapWidget->SetMode(MapWidget::Mode::Normal);
+}
+
+void MainWindow::OnOpenTrafficSample()
+{
+ TrafficModeInitDlg dlg;
+ dlg.exec();
+ if (dlg.result() != QDialog::DialogCode::Accepted)
+ return;
+
+ try
+ {
+ CreateTrafficPanel(dlg.GetDataFilePath());
+ }
+#ifdef openlr_obsolete
+ catch (TrafficModeError const & e)
+#else
+ // TODO do we need to catch exceptions other than our own here?
+ catch (RootException const & e)
+#endif
+ {
+ QMessageBox::critical(this, "Data loading error", QString("Can't load data file."));
+ LOG(LERROR, (e.Msg()));
+ return;
+ }
+
+#ifdef openlr_obsolete
+ m_goldifyMatchedPathAction->setEnabled(true /* enabled */);
+ m_closeTrafficSampleAction->setEnabled(true /* enabled */);
+ m_saveTrafficSampleAction->setEnabled(true /* enabled */);
+ m_startEditingAction->setEnabled(true /* enabled */);
+ m_ignorePathAction->setEnabled(true /* enabled */);
+#endif
+}
+
+void MainWindow::OnCloseTrafficSample()
+{
+ // TODO(mgsergio):
+ // If not saved, ask a user if he/she wants to save.
+ // OnSaveTrafficSample()
+
+#ifdef openlr_obsolete
+ m_goldifyMatchedPathAction->setEnabled(false /* enabled */);
+ m_saveTrafficSampleAction->setEnabled(false /* enabled */);
+ m_closeTrafficSampleAction->setEnabled(false /* enabled */);
+ m_startEditingAction->setEnabled(false /* enabled */);
+ m_commitPathAction->setEnabled(false /* enabled */);
+ m_cancelPathAction->setEnabled(false /* enabled */);
+ m_ignorePathAction->setEnabled(false /* enabled */);
+#endif
+
+ DestroyTrafficPanel();
+}
+
+void MainWindow::OnSaveTrafficSample()
+{
+ // TODO(mgsergio): Add default filename.
+ auto const & fileName = QFileDialog::getSaveFileName(this, "Save sample");
+ if (fileName.isEmpty())
+ return;
+
+#ifdef openlr_obsolete
+ if (!m_trafficMode->SaveSampleAs(fileName.toStdString()))
+ {
+ QMessageBox::critical(
+ this, "Saving error",
+ QString("Can't save file: ") + strerror(errno));
+ }
+#endif
+}
+
+void MainWindow::OnPathEditingStop()
+{
+#ifdef openlr_obsolete
+ m_commitPathAction->setEnabled(false /* enabled */);
+ m_cancelPathAction->setEnabled(false /* enabled */);
+ m_cancelPathAction->setEnabled(false /* enabled */);
+#endif
+}
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/mainwindow.hpp b/traffxml/traff_assessment_tool/mainwindow.hpp
new file mode 100644
index 000000000..9ad80e2fa
--- /dev/null
+++ b/traffxml/traff_assessment_tool/mainwindow.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "base/string_utils.hpp"
+
+#include
+
+#include
+
+class Framework;
+class QHBoxLayout;
+
+namespace traffxml
+{
+class MapWidget;
+class TrafficMode;
+class WebView;
+}
+
+namespace df
+{
+class DrapeApi;
+}
+
+class QDockWidget;
+
+namespace traffxml
+{
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(Framework & framework);
+
+private:
+ void CreateTrafficPanel(std::string const & dataFilePath);
+ void DestroyTrafficPanel();
+
+ void OnOpenTrafficSample();
+ void OnCloseTrafficSample();
+ void OnSaveTrafficSample();
+ void OnPathEditingStop();
+
+ Framework & m_framework;
+
+ traffxml::TrafficMode * m_trafficMode = nullptr;
+ QDockWidget * m_docWidget = nullptr;
+
+#ifdef openlr_obsolete
+ QAction * m_goldifyMatchedPathAction = nullptr;
+#endif
+ QAction * m_saveTrafficSampleAction = nullptr;
+ QAction * m_closeTrafficSampleAction = nullptr;
+#ifdef openlr_obsolete
+ QAction * m_startEditingAction = nullptr;
+ QAction * m_commitPathAction = nullptr;
+ QAction * m_cancelPathAction = nullptr;
+ QAction * m_ignorePathAction = nullptr;
+#endif
+
+ traffxml::MapWidget * m_mapWidget = nullptr;
+ QHBoxLayout * m_layout = nullptr;
+};
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/map_widget.cpp b/traffxml/traff_assessment_tool/map_widget.cpp
new file mode 100644
index 000000000..97232c054
--- /dev/null
+++ b/traffxml/traff_assessment_tool/map_widget.cpp
@@ -0,0 +1,29 @@
+#include "traffxml/traff_assessment_tool/map_widget.hpp"
+
+#include "qt/qt_common/helpers.hpp"
+
+#include "map/framework.hpp"
+
+#include
+
+namespace traffxml
+{
+MapWidget::MapWidget(Framework & framework, QWidget * parent)
+ : Base(framework, false /* screenshotMode */, parent)
+{
+}
+
+void MapWidget::mousePressEvent(QMouseEvent * e)
+{
+ Base::mousePressEvent(e);
+
+ if (qt::common::IsRightButton(e))
+ ShowInfoPopup(e, GetDevicePoint(e));
+
+ if (m_mode == Mode::TrafficMarkup)
+ {
+ auto pt = GetDevicePoint(e);
+ emit TrafficMarkupClick(m_framework.PtoG(pt), e->button());
+ }
+}
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/map_widget.hpp b/traffxml/traff_assessment_tool/map_widget.hpp
new file mode 100644
index 000000000..7931fd1b0
--- /dev/null
+++ b/traffxml/traff_assessment_tool/map_widget.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "qt/qt_common/map_widget.hpp"
+
+namespace
+{
+class PointsController;
+} // namespace
+
+class Framework;
+
+namespace traffxml
+{
+class MapWidget : public qt::common::MapWidget
+{
+ Q_OBJECT
+
+ using Base = qt::common::MapWidget;
+
+public:
+ enum class Mode
+ {
+ Normal,
+ TrafficMarkup
+ };
+
+ MapWidget(Framework & framework, QWidget * parent);
+ ~MapWidget() override = default;
+
+ void SetMode(Mode const mode) { m_mode = mode; }
+
+ QSize sizeHint() const override
+ {
+ return QSize(800, 600);
+ }
+
+signals:
+ void TrafficMarkupClick(m2::PointD const & p, Qt::MouseButton const b);
+
+protected:
+ void mousePressEvent(QMouseEvent * e) override;
+
+private:
+ Mode m_mode = Mode::Normal;
+};
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp b/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp
new file mode 100644
index 000000000..5f838bfa5
--- /dev/null
+++ b/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "indexer/feature.hpp"
+
+#include "geometry/point2d.hpp"
+
+#include
+#include
+
+namespace traffxml
+{
+using FeaturePoint = std::pair;
+
+/// This class is responsible for collecting junction points and
+/// checking user's clicks.
+class PointsControllerDelegateBase
+{
+public:
+ enum class ClickType
+ {
+ Miss,
+ Add,
+ Remove
+ };
+
+ virtual ~PointsControllerDelegateBase() = default;
+
+ virtual std::vector GetAllJunctionPointsInViewport() const = 0;
+ /// Returns all junction points at a given location in the form of feature id and
+ /// point index in the feature.
+ virtual std::pair, m2::PointD> GetCandidatePoints(
+ m2::PointD const & p) const = 0;
+ // Returns all points that are one step reachable from |p|.
+ virtual std::vector GetReachablePoints(m2::PointD const & p) const = 0;
+
+ virtual ClickType CheckClick(m2::PointD const & clickPoint,
+ m2::PointD const & lastClickedPoint,
+ std::vector const & reachablePoints) const = 0;
+};
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp b/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp
new file mode 100644
index 000000000..756bf1903
--- /dev/null
+++ b/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include
+
+namespace traffxml
+{
+/// This class is used to delegate segments drawing to the DrapeEngine.
+class TrafficDrawerDelegateBase
+{
+public:
+ virtual ~TrafficDrawerDelegateBase() = default;
+
+ virtual void SetViewportCenter(m2::PointD const & center) = 0;
+
+ virtual void DrawDecodedSegments(std::vector const & points) = 0;
+ virtual void DrawEncodedSegment(std::vector const & points) = 0;
+ virtual void DrawGoldenPath(std::vector const & points) = 0;
+
+ virtual void ClearGoldenPath() = 0;
+ virtual void ClearAllPaths() = 0;
+
+ virtual void VisualizePoints(std::vector const & points) = 0;
+ virtual void ClearAllVisualizedPoints() = 0;
+};
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/traffic_mode.cpp b/traffxml/traff_assessment_tool/traffic_mode.cpp
new file mode 100644
index 000000000..f0e5511ea
--- /dev/null
+++ b/traffxml/traff_assessment_tool/traffic_mode.cpp
@@ -0,0 +1,577 @@
+#include "traffic_mode.hpp"
+
+#ifdef openlr_obsolete
+#include "openlr/openlr_model_xml.hpp"
+#endif
+
+#include "indexer/data_source.hpp"
+
+#include "base/assert.hpp"
+#include "base/scope_guard.hpp"
+
+#include
+#include
+
+namespace traffxml
+{
+namespace
+{
+void RemovePointFromPull(m2::PointD const & toBeRemoved, std::vector & pool)
+{
+ pool.erase(
+ remove_if(begin(pool), end(pool),
+ [&toBeRemoved](m2::PointD const & p) { return p.EqualDxDy(toBeRemoved, 1e-6); }),
+ end(pool));
+}
+
+std::vector GetReachablePoints(m2::PointD const & srcPoint,
+ std::vector const path,
+ PointsControllerDelegateBase const & pointsDelegate,
+ size_t const lookbackIndex)
+{
+ auto reachablePoints = pointsDelegate.GetReachablePoints(srcPoint);
+ if (lookbackIndex < path.size())
+ {
+ auto const & toBeRemoved = path[path.size() - lookbackIndex - 1];
+ RemovePointFromPull(toBeRemoved, reachablePoints);
+ }
+ return reachablePoints;
+}
+} // namespace
+
+#ifdef openlr_obsolete
+namespace impl
+{
+// static
+size_t const RoadPointCandidate::kInvalidId = std::numeric_limits::max();
+
+/// This class denotes a "non-deterministic" feature point.
+/// I.e. it is a set of all pairs
+/// located at a specified coordinate.
+/// Only one point at a time is considered active.
+RoadPointCandidate::RoadPointCandidate(std::vector const & points,
+ m2::PointD const & coord)
+ : m_coord(coord)
+ , m_points(points)
+{
+ LOG(LDEBUG, ("Candidate points:", points));
+}
+
+void RoadPointCandidate::ActivateCommonPoint(RoadPointCandidate const & rpc)
+{
+ for (auto const & fp1 : m_points)
+ {
+ for (auto const & fp2 : rpc.m_points)
+ {
+ if (fp1.first == fp2.first)
+ {
+ SetActivePoint(fp1.first);
+ return;
+ }
+ }
+ }
+ CHECK(false, ("One common feature id should exist."));
+}
+
+FeaturePoint const & RoadPointCandidate::GetPoint() const
+{
+ CHECK_NOT_EQUAL(m_activePointIndex, kInvalidId, ("No point is active."));
+ return m_points[m_activePointIndex];
+}
+
+m2::PointD const & RoadPointCandidate::GetCoordinate() const
+{
+ return m_coord;
+}
+
+void RoadPointCandidate::SetActivePoint(FeatureID const & fid)
+{
+ for (size_t i = 0; i < m_points.size(); ++i)
+ {
+ if (m_points[i].first == fid)
+ {
+ m_activePointIndex = i;
+ return;
+ }
+ }
+ CHECK(false, ("One point should match."));
+}
+} // namespace impl
+#endif
+
+// TrafficMode -------------------------------------------------------------------------------------
+TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & dataSource,
+ std::unique_ptr drawerDelegate,
+ std::unique_ptr pointsDelegate,
+ QObject * parent)
+ : QAbstractTableModel(parent)
+ , m_dataSource(dataSource)
+ , m_drawerDelegate(std::move(drawerDelegate))
+ , m_pointsDelegate(std::move(pointsDelegate))
+{
+ // TODO(mgsergio): Collect stat how many segments of each kind were parsed.
+ pugi::xml_document doc;
+ if (!doc.load_file(dataFileName.data()))
+ MYTHROW(TrafficModeError, ("Can't load file:", strerror(errno)));
+
+ // Save root node without children.
+ {
+ auto const root = doc.document_element();
+ auto node = m_template.append_child(root.name());
+ for (auto const & attr : root.attributes())
+ node.append_copy(attr);
+ }
+
+ // Select all Segment elements that are direct children of the root.
+ auto const segments = doc.document_element().select_nodes("./Segment");
+
+#ifdef openlr_obsolete
+ try
+ {
+ for (auto const & xpathNode : segments)
+ {
+ auto const xmlSegment = xpathNode.node();
+
+ openlr::Path matchedPath;
+ openlr::Path fakePath;
+ openlr::Path goldenPath;
+
+ openlr::LinearSegment segment;
+
+ // TODO(mgsergio): Unify error handling interface of openlr_xml_mode and decoded_path parsers.
+ auto const partnerSegmentXML = xmlSegment.child("reportSegments");
+ if (!openlr::SegmentFromXML(partnerSegmentXML, segment))
+ MYTHROW(TrafficModeError, ("An error occurred while parsing: can't parse segment"));
+
+ if (auto const route = xmlSegment.child("Route"))
+ openlr::PathFromXML(route, m_dataSource, matchedPath);
+ if (auto const route = xmlSegment.child("FakeRoute"))
+ openlr::PathFromXML(route, m_dataSource, fakePath);
+ if (auto const route = xmlSegment.child("GoldenRoute"))
+ openlr::PathFromXML(route, m_dataSource, goldenPath);
+
+ uint32_t positiveOffsetM = 0;
+ uint32_t negativeOffsetM = 0;
+ if (auto const reportSegmentLRC = partnerSegmentXML.child("ReportSegmentLRC"))
+ {
+ if (auto const method = reportSegmentLRC.child("method"))
+ {
+ if (auto const locationReference = method.child("olr:locationReference"))
+ {
+ if (auto const optionLinearLocationReference = locationReference
+ .child("olr:optionLinearLocationReference"))
+ {
+ if (auto const positiveOffset = optionLinearLocationReference.child("olr:positiveOffset"))
+ positiveOffsetM = UintValueFromXML(positiveOffset);
+
+ if (auto const negativeOffset = optionLinearLocationReference.child("olr:negativeOffset"))
+ negativeOffsetM = UintValueFromXML(negativeOffset);
+ }
+ }
+ }
+ }
+
+ m_segments.emplace_back(segment, positiveOffsetM, negativeOffsetM, matchedPath, fakePath,
+ goldenPath, partnerSegmentXML);
+ if (auto const status = xmlSegment.child("Ignored"))
+ {
+ if (status.text().as_bool())
+ m_segments.back().Ignore();
+ }
+ }
+ }
+ catch (openlr::DecodedPathLoadError const & e)
+ {
+ MYTHROW(TrafficModeError, ("An exception occurred while parsing", dataFileName, e.Msg()));
+ }
+#endif
+
+ LOG(LINFO, (segments.size(), "segments are loaded."));
+}
+
+// TODO(mgsergio): Check if a path was committed, or commit it.
+bool TrafficMode::SaveSampleAs(std::string const & fileName) const
+{
+ CHECK(!fileName.empty(), ("Can't save to an empty file."));
+
+ pugi::xml_document result;
+ result.reset(m_template);
+ auto root = result.document_element();
+
+#ifdef openlr_obsolete
+ for (auto const & sc : m_segments)
+ {
+ auto segment = root.append_child("Segment");
+ segment.append_copy(sc.GetPartnerXMLSegment());
+
+ if (sc.GetStatus() == SegmentCorrespondence::Status::Ignored)
+ {
+ segment.append_child("Ignored").text() = true;
+ }
+ if (sc.HasMatchedPath())
+ {
+ auto node = segment.append_child("Route");
+ openlr::PathToXML(sc.GetMatchedPath(), node);
+ }
+ if (sc.HasFakePath())
+ {
+ auto node = segment.append_child("FakeRoute");
+ openlr::PathToXML(sc.GetFakePath(), node);
+ }
+ if (sc.HasGoldenPath())
+ {
+ auto node = segment.append_child("GoldenRoute");
+ openlr::PathToXML(sc.GetGoldenPath(), node);
+ }
+ }
+#endif
+
+ result.save_file(fileName.data(), " " /* indent */);
+ return true;
+}
+
+int TrafficMode::rowCount(const QModelIndex & parent) const
+{
+#ifdef openlr_obsolete
+ return static_cast(m_segments.size());
+#else
+ // TODO return a meaningful value
+ return 0;
+#endif
+}
+
+int TrafficMode::columnCount(const QModelIndex & parent) const { return 4; }
+
+QVariant TrafficMode::data(const QModelIndex & index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (index.row() >= rowCount())
+ return QVariant();
+
+ if (role != Qt::DisplayRole && role != Qt::EditRole)
+ return QVariant();
+
+#ifdef openlr_obsolete
+ if (index.column() == 0)
+ return m_segments[index.row()].GetPartnerSegmentId();
+
+ if (index.column() == 1)
+ return static_cast(m_segments[index.row()].GetStatus());
+
+ if (index.column() == 2)
+ return m_segments[index.row()].GetPositiveOffset();
+
+ if (index.column() == 3)
+ return m_segments[index.row()].GetNegativeOffset();
+#endif
+
+ return QVariant();
+}
+
+QVariant TrafficMode::headerData(int section, Qt::Orientation orientation,
+ int role /* = Qt::DisplayRole */) const
+{
+ if (orientation != Qt::Horizontal && role != Qt::DisplayRole)
+ return QVariant();
+
+ switch (section)
+ {
+ case 0: return "Segment id"; break;
+ case 1: return "Status code"; break;
+ case 2: return "Positive offset (Meters)"; break;
+ case 3: return "Negative offset (Meters)"; break;
+ }
+ UNREACHABLE();
+}
+
+void TrafficMode::OnItemSelected(QItemSelection const & selected, QItemSelection const &)
+{
+#ifdef openlr_obsolete
+ ASSERT(!selected.empty(), ());
+ ASSERT(!m_segments.empty(), ());
+
+ auto const row = selected.front().top();
+
+ CHECK_LESS(static_cast(row), m_segments.size(), ());
+ m_currentSegment = &m_segments[row];
+
+ auto const & partnerSegment = m_currentSegment->GetPartnerSegment();
+ auto const & partnerSegmentPoints = partnerSegment.GetMercatorPoints();
+ auto const & viewportCenter = partnerSegmentPoints.front();
+
+ m_drawerDelegate->ClearAllPaths();
+ // TODO(mgsergio): Use a better way to set viewport and scale.
+ m_drawerDelegate->SetViewportCenter(viewportCenter);
+ m_drawerDelegate->DrawEncodedSegment(partnerSegmentPoints);
+ if (m_currentSegment->HasMatchedPath())
+ m_drawerDelegate->DrawDecodedSegments(GetPoints(m_currentSegment->GetMatchedPath()));
+ if (m_currentSegment->HasGoldenPath())
+ m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath()));
+
+ emit SegmentSelected(static_cast(partnerSegment.m_segmentId));
+#endif
+}
+
+Qt::ItemFlags TrafficMode::flags(QModelIndex const & index) const
+{
+ if (!index.isValid())
+ return Qt::ItemIsEnabled;
+
+ return QAbstractItemModel::flags(index);
+}
+
+#ifdef openlr_obsolete
+void TrafficMode::GoldifyMatchedPath()
+{
+ if (!m_currentSegment->HasMatchedPath())
+ {
+ QMessageBox::information(nullptr /* parent */, "Error",
+ "The selected segment does not have a matched path");
+ return;
+ }
+
+ if (!StartBuildingPathChecks())
+ return;
+
+ m_currentSegment->SetGoldenPath(m_currentSegment->GetMatchedPath());
+ m_goldenPath.clear();
+ m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath()));
+}
+
+void TrafficMode::StartBuildingPath()
+{
+ if (!StartBuildingPathChecks())
+ return;
+
+ m_currentSegment->SetGoldenPath({});
+
+ m_buildingPath = true;
+ m_drawerDelegate->ClearGoldenPath();
+ m_drawerDelegate->VisualizePoints(m_pointsDelegate->GetAllJunctionPointsInViewport());
+}
+
+void TrafficMode::PushPoint(m2::PointD const & coord, std::vector const & points)
+{
+ impl::RoadPointCandidate point(points, coord);
+ if (!m_goldenPath.empty())
+ m_goldenPath.back().ActivateCommonPoint(point);
+ m_goldenPath.push_back(point);
+}
+
+void TrafficMode::PopPoint()
+{
+ CHECK(!m_goldenPath.empty(), ("Attempt to pop point from an empty path."));
+ m_goldenPath.pop_back();
+}
+
+void TrafficMode::CommitPath()
+{
+ CHECK(m_currentSegment, ("No segments selected"));
+
+ if (!m_buildingPath)
+ MYTHROW(TrafficModeError, ("Path building is not started"));
+
+ SCOPE_GUARD(guard, [this] { emit EditingStopped(); });
+
+ m_buildingPath = false;
+ m_drawerDelegate->ClearAllVisualizedPoints();
+
+ if (m_goldenPath.size() == 1)
+ {
+ LOG(LDEBUG, ("Golden path is empty"));
+ return;
+ }
+
+ CHECK_GREATER(m_goldenPath.size(), 1, ("Path cannot consist of only one point"));
+
+ // Activate last point. Since no more points will be availabe we link it to the same
+ // feature as the previous one was linked to.
+ m_goldenPath.back().ActivateCommonPoint(m_goldenPath[GetPointsCount() - 2]);
+
+ openlr::Path path;
+ for (size_t i = 1; i < GetPointsCount(); ++i)
+ {
+ auto const prevPoint = m_goldenPath[i - 1];
+ auto point = m_goldenPath[i];
+
+ // The start and the end of the edge should lie on the same feature.
+ point.ActivateCommonPoint(prevPoint);
+
+ auto const & prevFt = prevPoint.GetPoint();
+ auto const & ft = point.GetPoint();
+
+ path.push_back(Edge::MakeReal(
+ ft.first, prevFt.second < ft.second /* forward */, base::checked_cast(prevFt.second),
+ geometry::PointWithAltitude(prevPoint.GetCoordinate(), 0 /* altitude */),
+ geometry::PointWithAltitude(point.GetCoordinate(), 0 /* altitude */)));
+ }
+
+ m_currentSegment->SetGoldenPath(path);
+ m_goldenPath.clear();
+}
+
+void TrafficMode::RollBackPath()
+{
+ CHECK(m_currentSegment, ("No segments selected"));
+ CHECK(m_buildingPath, ("No path building is in progress."));
+
+ m_buildingPath = false;
+
+ // TODO(mgsergio): Add a method for common visual manipulations.
+ m_drawerDelegate->ClearAllVisualizedPoints();
+ m_drawerDelegate->ClearGoldenPath();
+ if (m_currentSegment->HasGoldenPath())
+ m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath()));
+
+ m_goldenPath.clear();
+ emit EditingStopped();
+}
+
+void TrafficMode::IgnorePath()
+{
+ CHECK(m_currentSegment, ("No segments selected"));
+
+ if (m_currentSegment->HasGoldenPath())
+ {
+ auto const btn =
+ QMessageBox::question(nullptr /* parent */, "Override warning",
+ "The selected segment has a golden path. Do you want to discard it?");
+ if (btn == QMessageBox::No)
+ return;
+ }
+
+ m_buildingPath = false;
+
+ // TODO(mgsergio): Add a method for common visual manipulations.
+ m_drawerDelegate->ClearAllVisualizedPoints();
+ m_drawerDelegate->ClearGoldenPath();
+
+ m_currentSegment->Ignore();
+ m_goldenPath.clear();
+ emit EditingStopped();
+}
+
+size_t TrafficMode::GetPointsCount() const
+{
+ return m_goldenPath.size();
+}
+
+m2::PointD const & TrafficMode::GetPoint(size_t const index) const
+{
+ return m_goldenPath[index].GetCoordinate();
+}
+
+m2::PointD const & TrafficMode::GetLastPoint() const
+{
+ CHECK(!m_goldenPath.empty(), ("Attempt to get point from an empty path."));
+ return m_goldenPath.back().GetCoordinate();
+}
+
+std::vector TrafficMode::GetGoldenPathPoints() const
+{
+ std::vector coordinates;
+ for (auto const & roadPoint : m_goldenPath)
+ coordinates.push_back(roadPoint.GetCoordinate());
+ return coordinates;
+}
+
+// TODO(mgsergio): Draw the first point when the path size is 1.
+void TrafficMode::HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button)
+{
+ if (!m_buildingPath)
+ return;
+
+ auto const currentPathLength = GetPointsCount();
+ auto const lastClickedPoint = currentPathLength != 0
+ ? GetLastPoint()
+ : m2::PointD::Zero();
+
+ auto const & p = m_pointsDelegate->GetCandidatePoints(clickPoint);
+ auto const & candidatePoints = p.first;
+ clickPoint = p.second;
+ if (candidatePoints.empty())
+ return;
+
+ auto reachablePoints = GetReachablePoints(clickPoint, GetGoldenPathPoints(), *m_pointsDelegate,
+ 0 /* lookBackIndex */);
+ auto const & clickablePoints = currentPathLength != 0
+ ? GetReachablePoints(lastClickedPoint, GetGoldenPathPoints(), *m_pointsDelegate,
+ 1 /* lookbackIndex */)
+ // TODO(mgsergio): This is not quite correct since view port can change
+ // since first call to visualize points. But it's ok in general.
+ : m_pointsDelegate->GetAllJunctionPointsInViewport();
+
+ using ClickType = PointsControllerDelegateBase::ClickType;
+ switch (m_pointsDelegate->CheckClick(clickPoint, lastClickedPoint, clickablePoints))
+ {
+ case ClickType::Add:
+ // TODO(mgsergio): Think of refactoring this with if (accumulator.empty)
+ // instead of pushing point first ad then removing last selection.
+ PushPoint(clickPoint, candidatePoints);
+
+ if (currentPathLength > 0)
+ {
+ // TODO(mgsergio): Should I remove lastClickedPoint from clickablePoints
+ // as well?
+ RemovePointFromPull(lastClickedPoint, reachablePoints);
+ m_drawerDelegate->DrawGoldenPath(GetGoldenPathPoints());
+ }
+
+ m_drawerDelegate->ClearAllVisualizedPoints();
+ m_drawerDelegate->VisualizePoints(reachablePoints);
+ m_drawerDelegate->VisualizePoints({clickPoint});
+ break;
+ case ClickType::Remove: // TODO(mgsergio): Rename this case.
+ if (button == Qt::MouseButton::LeftButton) // RemovePoint
+ {
+ m_drawerDelegate->ClearAllVisualizedPoints();
+ m_drawerDelegate->ClearGoldenPath();
+
+ PopPoint();
+ if (m_goldenPath.empty())
+ {
+ m_drawerDelegate->VisualizePoints(m_pointsDelegate->GetAllJunctionPointsInViewport());
+ }
+ else
+ {
+ m_drawerDelegate->VisualizePoints(GetReachablePoints(
+ GetLastPoint(), GetGoldenPathPoints(), *m_pointsDelegate, 1 /* lookBackIndex */));
+ }
+
+ if (GetPointsCount() > 1)
+ m_drawerDelegate->DrawGoldenPath(GetGoldenPathPoints());
+ }
+ else if (button == Qt::MouseButton::RightButton)
+ {
+ CommitPath();
+ }
+ break;
+ case ClickType::Miss:
+ // TODO(mgsergio): This situation should be handled by checking candidatePoitns.empty() above.
+ // Not shure though if all cases are handled by that check.
+ return;
+ }
+}
+
+bool TrafficMode::StartBuildingPathChecks() const
+{
+ CHECK(m_currentSegment, ("A segment should be selected before path building is started."));
+
+ if (m_buildingPath)
+ MYTHROW(TrafficModeError, ("Path building already in progress."));
+
+ if (m_currentSegment->HasGoldenPath())
+ {
+ auto const btn = QMessageBox::question(
+ nullptr /* parent */, "Override warning",
+ "The selected segment already has a golden path. Do you want to override?");
+ if (btn == QMessageBox::No)
+ return false;
+ }
+
+ return true;
+}
+#endif
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/traffic_mode.hpp b/traffxml/traff_assessment_tool/traffic_mode.hpp
new file mode 100644
index 000000000..edf31be11
--- /dev/null
+++ b/traffxml/traff_assessment_tool/traffic_mode.hpp
@@ -0,0 +1,141 @@
+#pragma once
+
+#include "points_controller_delegate_base.hpp"
+#ifdef openlr_obsolete
+#include "segment_correspondence.hpp"
+#endif
+#include "traffic_drawer_delegate_base.hpp"
+
+#ifdef openlr_obsolete
+#include "openlr/decoded_path.hpp"
+#endif
+
+#include "indexer/data_source.hpp"
+
+#include "base/exception.hpp"
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+
+class QItemSelection;
+class Selection;
+
+DECLARE_EXCEPTION(TrafficModeError, RootException);
+
+namespace traffxml
+{
+#ifdef openlr_obsolete
+namespace impl
+{
+/// This class denotes a "non-deterministic" feature point.
+/// I.e. it is a set of all pairs
+/// located at a specified coordinate.
+/// Only one point at a time is considered active.
+class RoadPointCandidate
+{
+public:
+ RoadPointCandidate(std::vector const & points,
+ m2::PointD const & coord);
+
+ void ActivateCommonPoint(RoadPointCandidate const & rpc);
+ openlr::FeaturePoint const & GetPoint() const;
+ m2::PointD const & GetCoordinate() const;
+
+private:
+ static size_t const kInvalidId;
+
+ void SetActivePoint(FeatureID const & fid);
+
+ m2::PointD m_coord = m2::PointD::Zero();
+ std::vector m_points;
+
+ size_t m_activePointIndex = kInvalidId;
+};
+} // namespace impl
+#endif
+
+/// This class is used to map sample ids to real data
+/// and change sample evaluations.
+class TrafficMode : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ // TODO(mgsergio): Check we are on the right mwm. I.e. right mwm version and everything.
+ TrafficMode(std::string const & dataFileName, DataSource const & dataSource,
+ std::unique_ptr drawerDelegate,
+ std::unique_ptr pointsDelegate,
+ QObject * parent = Q_NULLPTR);
+
+ bool SaveSampleAs(std::string const & fileName) const;
+
+ int rowCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ int columnCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+
+ QVariant data(const QModelIndex & index, int role) const Q_DECL_OVERRIDE;
+
+ Qt::ItemFlags flags(QModelIndex const & index) const Q_DECL_OVERRIDE;
+
+ bool IsBuildingPath() const { return m_buildingPath; }
+#ifdef openlr_obsolete
+ void GoldifyMatchedPath();
+ void StartBuildingPath();
+ void PushPoint(m2::PointD const & coord,
+ std::vector const & points);
+ void PopPoint();
+ void CommitPath();
+ void RollBackPath();
+ void IgnorePath();
+
+ size_t GetPointsCount() const;
+ m2::PointD const & GetPoint(size_t const index) const;
+ m2::PointD const & GetLastPoint() const;
+ std::vector GetGoldenPathPoints() const;
+#endif
+
+public slots:
+ void OnItemSelected(QItemSelection const & selected, QItemSelection const &);
+ void OnClick(m2::PointD const & clickPoint, Qt::MouseButton const button)
+ {
+#ifdef openlr_obsolete
+ HandlePoint(clickPoint, button);
+#endif
+ }
+
+signals:
+ void EditingStopped();
+ void SegmentSelected(int segmentId);
+
+private:
+#ifdef openlr_obsolete
+ void HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button);
+ bool StartBuildingPathChecks() const;
+#endif
+
+ DataSource const & m_dataSource;
+#ifdef openlr_obsolete
+ std::vector m_segments;
+ // Non-owning pointer to an element of m_segments.
+ SegmentCorrespondence * m_currentSegment = nullptr;
+#endif
+
+ std::unique_ptr m_drawerDelegate;
+ std::unique_ptr m_pointsDelegate;
+
+ bool m_buildingPath = false;
+#ifdef openlr_obsolete
+ std::vector m_goldenPath;
+#endif
+
+ // Clone this document and add things to its clone when saving sample.
+ pugi::xml_document m_template;
+};
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/traffic_panel.cpp b/traffxml/traff_assessment_tool/traffic_panel.cpp
new file mode 100644
index 000000000..89030d16b
--- /dev/null
+++ b/traffxml/traff_assessment_tool/traffic_panel.cpp
@@ -0,0 +1,81 @@
+#include "traffxml/traff_assessment_tool/traffic_panel.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace traffxml
+{
+// ComboBoxDelegate --------------------------------------------------------------------------------
+ComboBoxDelegate::ComboBoxDelegate(QObject * parent)
+ : QStyledItemDelegate(parent)
+{
+}
+
+QWidget * ComboBoxDelegate::createEditor(QWidget * parent, QStyleOptionViewItem const & option,
+ QModelIndex const & index) const
+{
+ auto * editor = new QComboBox(parent);
+ editor->setFrame(false);
+ editor->setEditable(false);
+ editor->addItems({"Unevaluated", "Positive", "Negative", "RelPositive", "RelNegative", "Ignore"});
+
+ return editor;
+}
+
+void ComboBoxDelegate::setEditorData(QWidget * editor, QModelIndex const & index) const
+{
+ auto const value = index.model()->data(index, Qt::EditRole).toString();
+ static_cast(editor)->setCurrentText(value);
+}
+
+void ComboBoxDelegate::setModelData(QWidget * editor, QAbstractItemModel * model,
+ QModelIndex const & index) const
+{
+ model->setData(index, static_cast(editor)->currentText(), Qt::EditRole);
+}
+
+void ComboBoxDelegate::updateEditorGeometry(QWidget * editor, QStyleOptionViewItem const & option,
+ QModelIndex const & index) const
+{
+ editor->setGeometry(option.rect);
+}
+
+// TrafficPanel ------------------------------------------------------------------------------------
+TrafficPanel::TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent)
+ : QWidget(parent)
+{
+ CreateTable(trafficModel);
+
+ auto * layout = new QVBoxLayout();
+ layout->addWidget(m_table);
+ setLayout(layout);
+
+ // Select first segment by default;
+ auto const & index = m_table->model()->index(0, 0);
+ m_table->selectionModel()->select(index, QItemSelectionModel::Select);
+}
+
+void TrafficPanel::CreateTable(QAbstractItemModel * trafficModel)
+{
+ m_table = new QTableView();
+ m_table->setFocusPolicy(Qt::NoFocus);
+ m_table->setAlternatingRowColors(true);
+ m_table->setShowGrid(false);
+ m_table->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
+ m_table->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
+ m_table->verticalHeader()->setVisible(false);
+ m_table->horizontalHeader()->setVisible(true);
+ m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+
+ m_table->setModel(trafficModel);
+ m_table->setItemDelegate(new ComboBoxDelegate());
+
+ connect(m_table->selectionModel(),
+ SIGNAL(selectionChanged(QItemSelection const &, QItemSelection const &)),
+ trafficModel, SLOT(OnItemSelected(QItemSelection const &, QItemSelection const &)));
+}
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/traffic_panel.hpp b/traffxml/traff_assessment_tool/traffic_panel.hpp
new file mode 100644
index 000000000..27ab4d033
--- /dev/null
+++ b/traffxml/traff_assessment_tool/traffic_panel.hpp
@@ -0,0 +1,50 @@
+#pragma once
+
+#include
+
+class QAbstractItemModel;
+class QComboBox;
+class QTableView;
+class QWidget;
+
+namespace traffxml
+{
+class ComboBoxDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ ComboBoxDelegate(QObject * parent = 0);
+
+ QWidget * createEditor(QWidget * parent, QStyleOptionViewItem const & option,
+ QModelIndex const & index) const Q_DECL_OVERRIDE;
+
+ void setEditorData(QWidget * editor, QModelIndex const & index) const Q_DECL_OVERRIDE;
+
+ void setModelData(QWidget * editor, QAbstractItemModel * model,
+ QModelIndex const & index) const Q_DECL_OVERRIDE;
+
+ void updateEditorGeometry(QWidget * editor, QStyleOptionViewItem const & option,
+ QModelIndex const & index) const Q_DECL_OVERRIDE;
+};
+
+class TrafficPanel : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent);
+
+private:
+ void CreateTable(QAbstractItemModel * trafficModel);
+ void FillTable();
+
+signals:
+
+public slots:
+ // void OnCheckBoxClicked(int row, int state);
+
+private:
+ QTableView * m_table = Q_NULLPTR;
+};
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp
new file mode 100644
index 000000000..f916d6125
--- /dev/null
+++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp
@@ -0,0 +1,54 @@
+#include "traffxml/traff_assessment_tool/trafficmodeinitdlg.h"
+#include "ui_trafficmodeinitdlg.h"
+
+#include "platform/settings.hpp"
+
+#include
+
+#include
+
+namespace
+{
+std::string const kDataFilePath = "LastOpenlrAssessmentDataFilePath";
+} // namespace
+
+namespace traffxml
+{
+TrafficModeInitDlg::TrafficModeInitDlg(QWidget * parent) :
+ QDialog(parent),
+ m_ui(new Ui::TrafficModeInitDlg)
+{
+ m_ui->setupUi(this);
+
+ std::string lastDataFilePath;
+ if (settings::Get(kDataFilePath, lastDataFilePath))
+ m_ui->dataFileName->setText(QString::fromStdString(lastDataFilePath));
+
+ connect(m_ui->chooseDataFileButton, &QPushButton::clicked, [this](bool) {
+ SetFilePathViaDialog(*m_ui->dataFileName, tr("Choose data file"), "*.xml");
+ });
+}
+
+TrafficModeInitDlg::~TrafficModeInitDlg()
+{
+ delete m_ui;
+}
+
+void TrafficModeInitDlg::accept()
+{
+ m_dataFileName = m_ui->dataFileName->text().trimmed().toStdString();
+ settings::Set(kDataFilePath, m_dataFileName);
+ QDialog::accept();
+}
+
+void TrafficModeInitDlg::SetFilePathViaDialog(QLineEdit & dest, QString const & title,
+ QString const & filter)
+{
+ QFileDialog openFileDlg(nullptr, title, {} /* directory */, filter);
+ openFileDlg.exec();
+ if (openFileDlg.result() != QDialog::DialogCode::Accepted)
+ return;
+
+ dest.setText(openFileDlg.selectedFiles().first());
+}
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.h b/traffxml/traff_assessment_tool/trafficmodeinitdlg.h
new file mode 100644
index 000000000..6ad874e14
--- /dev/null
+++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include
+
+#include
+
+class QLineEdit;
+
+namespace Ui {
+class TrafficModeInitDlg;
+}
+
+namespace traffxml
+{
+class TrafficModeInitDlg : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TrafficModeInitDlg(QWidget * parent = nullptr);
+ ~TrafficModeInitDlg();
+
+ std::string GetDataFilePath() const { return m_dataFileName; }
+
+private:
+ void SetFilePathViaDialog(QLineEdit & dest, QString const & title,
+ QString const & filter = {});
+public slots:
+ void accept() override;
+
+private:
+ Ui::TrafficModeInitDlg * m_ui;
+
+ std::string m_dataFileName;
+};
+} // namespace traffxml
diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui b/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui
new file mode 100644
index 000000000..a7821ff6a
--- /dev/null
+++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui
@@ -0,0 +1,111 @@
+
+
+ TrafficModeInitDlg
+
+
+
+ 0
+ 0
+ 482
+ 122
+
+
+
+
+ 0
+ 0
+
+
+
+ Select traffic files
+
+
+ true
+
+
+
+
+ 20
+ 10
+ 441
+ 101
+
+
+
+ -
+
+
+ Cancel
+
+
+
+ -
+
+
+ Choose...
+
+
+
+ -
+
+
+ Ok
+
+
+
+ -
+
+
+ -
+
+
+ true
+
+
+ <html><head/><body><p>Data file:</p></body></html>
+
+
+
+
+
+
+
+ dataFileName
+ chooseDataFileButton
+
+
+
+
+ cancelButton
+ clicked()
+ TrafficModeInitDlg
+ reject()
+
+
+ 290
+ 103
+
+
+ 164
+ 98
+
+
+
+
+ okButton
+ clicked()
+ TrafficModeInitDlg
+ accept()
+
+
+ 405
+ 105
+
+
+ 59
+ 94
+
+
+
+
+