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 + + + + +