Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
5
tools/openlr/openlr_match_quality/CMakeLists.txt
Normal file
5
tools/openlr/openlr_match_quality/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
project(openlr_match_quality)
|
||||
|
||||
if (NOT SKIP_QT_GUI)
|
||||
add_subdirectory(openlr_assessment_tool)
|
||||
endif()
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
project(openlr_assessment_tool)
|
||||
|
||||
set(SRC
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
mainwindow.hpp
|
||||
map_widget.cpp
|
||||
map_widget.hpp
|
||||
points_controller_delegate_base.hpp
|
||||
segment_correspondence.cpp
|
||||
segment_correspondence.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
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<plist>
|
||||
<dict>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#include "mainwindow.hpp"
|
||||
|
||||
#include "qt/qt_common/helpers.hpp"
|
||||
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
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);
|
||||
openlr::MainWindow mainWindow(framework);
|
||||
|
||||
mainWindow.showMaximized();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.hpp"
|
||||
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/points_controller_delegate_base.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_drawer_delegate_base.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_panel.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_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 <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDockWidget>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeySequence>
|
||||
#include <QLayout>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
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<m2::PointD> 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<m2::PointD> 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<m2::PointD> 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<m2::PointD> const & points) override
|
||||
{
|
||||
auto editSession = m_bm.GetEditSession();
|
||||
editSession.SetIsVisible(UserMark::Type::DEBUG_MARK, true);
|
||||
for (auto const & p : points)
|
||||
editSession.CreateUserMark<DebugMarkPoint>(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<DataSource &>(GetDataSource()), nullptr /* numMwmIDs */)
|
||||
, m_roadGraph(m_dataSource, routing::IRoadGraph::Mode::ObeyOnewayTag,
|
||||
std::make_unique<routing::CarModelFactory>(storage::CountryParentGetter{}))
|
||||
{}
|
||||
|
||||
std::vector<m2::PointD> GetAllJunctionPointsInViewport() const override
|
||||
{
|
||||
std::vector<m2::PointD> 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<std::vector<FeaturePoint>, m2::PointD> GetCandidatePoints(m2::PointD const & p) const override
|
||||
{
|
||||
auto constexpr kInvalidIndex = std::numeric_limits<size_t>::max();
|
||||
|
||||
std::vector<FeaturePoint> 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<double>::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<m2::PointD> GetReachablePoints(m2::PointD const & p) const override
|
||||
{
|
||||
routing::FeaturesRoadGraph::EdgeListT edges;
|
||||
m_roadGraph.GetOutgoingEdges(geometry::PointWithAltitude(p, geometry::kDefaultAltitudeMeters), edges);
|
||||
|
||||
std::vector<m2::PointD> 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<m2::PointD> 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();
|
||||
|
||||
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 */);
|
||||
}
|
||||
|
||||
void MainWindow::CreateTrafficPanel(std::string const & dataFilePath)
|
||||
{
|
||||
m_trafficMode =
|
||||
new TrafficMode(dataFilePath, m_framework.GetDataSource(), std::make_unique<TrafficDrawerDelegate>(m_framework),
|
||||
std::make_unique<PointsControllerDelegate>(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());
|
||||
}
|
||||
catch (TrafficModeError const & e)
|
||||
{
|
||||
QMessageBox::critical(this, "Data loading error", QString("Can't load data file."));
|
||||
LOG(LERROR, (e.Msg()));
|
||||
return;
|
||||
}
|
||||
|
||||
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 */);
|
||||
}
|
||||
|
||||
void MainWindow::OnCloseTrafficSample()
|
||||
{
|
||||
// TODO(mgsergio):
|
||||
// If not saved, ask a user if he/she wants to save.
|
||||
// OnSaveTrafficSample()
|
||||
|
||||
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 */);
|
||||
|
||||
DestroyTrafficPanel();
|
||||
}
|
||||
|
||||
void MainWindow::OnSaveTrafficSample()
|
||||
{
|
||||
// TODO(mgsergio): Add default filename.
|
||||
auto const & fileName = QFileDialog::getSaveFileName(this, "Save sample");
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
if (!m_trafficMode->SaveSampleAs(fileName.toStdString()))
|
||||
QMessageBox::critical(this, "Saving error", QString("Can't save file: ") + strerror(errno));
|
||||
}
|
||||
|
||||
void MainWindow::OnPathEditingStop()
|
||||
{
|
||||
m_commitPathAction->setEnabled(false /* enabled */);
|
||||
m_cancelPathAction->setEnabled(false /* enabled */);
|
||||
m_cancelPathAction->setEnabled(false /* enabled */);
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
class Framework;
|
||||
class QHBoxLayout;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class MapWidget;
|
||||
class TrafficMode;
|
||||
class WebView;
|
||||
} // namespace openlr
|
||||
|
||||
namespace df
|
||||
{
|
||||
class DrapeApi;
|
||||
}
|
||||
|
||||
class QDockWidget;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
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;
|
||||
|
||||
openlr::TrafficMode * m_trafficMode = nullptr;
|
||||
QDockWidget * m_docWidget = nullptr;
|
||||
|
||||
QAction * m_goldifyMatchedPathAction = nullptr;
|
||||
QAction * m_saveTrafficSampleAction = nullptr;
|
||||
QAction * m_closeTrafficSampleAction = nullptr;
|
||||
QAction * m_startEditingAction = nullptr;
|
||||
QAction * m_commitPathAction = nullptr;
|
||||
QAction * m_cancelPathAction = nullptr;
|
||||
QAction * m_ignorePathAction = nullptr;
|
||||
|
||||
openlr::MapWidget * m_mapWidget = nullptr;
|
||||
QHBoxLayout * m_layout = nullptr;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp"
|
||||
|
||||
#include "qt/qt_common/helpers.hpp"
|
||||
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
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 openlr
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "qt/qt_common/map_widget.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class PointsController;
|
||||
} // namespace
|
||||
|
||||
class Framework;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
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 openlr
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "indexer/feature.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
using FeaturePoint = std::pair<FeatureID, size_t>;
|
||||
|
||||
/// 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<m2::PointD> 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<std::vector<FeaturePoint>, m2::PointD> GetCandidatePoints(m2::PointD const & p) const = 0;
|
||||
// Returns all points that are one step reachable from |p|.
|
||||
virtual std::vector<m2::PointD> GetReachablePoints(m2::PointD const & p) const = 0;
|
||||
|
||||
virtual ClickType CheckClick(m2::PointD const & clickPoint, m2::PointD const & lastClickedPoint,
|
||||
std::vector<m2::PointD> const & reachablePoints) const = 0;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/segment_correspondence.hpp"
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
SegmentCorrespondence::SegmentCorrespondence(SegmentCorrespondence const & sc)
|
||||
{
|
||||
m_partnerSegment = sc.m_partnerSegment;
|
||||
|
||||
m_positiveOffset = sc.m_positiveOffset;
|
||||
m_negativeOffset = sc.m_negativeOffset;
|
||||
|
||||
m_matchedPath = sc.m_matchedPath;
|
||||
m_fakePath = sc.m_fakePath;
|
||||
m_goldenPath = sc.m_goldenPath;
|
||||
|
||||
m_partnerXMLDoc.reset(sc.m_partnerXMLDoc);
|
||||
m_partnerXMLSegment = m_partnerXMLDoc.child("reportSegments");
|
||||
|
||||
m_status = sc.m_status;
|
||||
}
|
||||
|
||||
SegmentCorrespondence::SegmentCorrespondence(openlr::LinearSegment const & segment, uint32_t positiveOffset,
|
||||
uint32_t negativeOffset, openlr::Path const & matchedPath,
|
||||
openlr::Path const & fakePath, openlr::Path const & goldenPath,
|
||||
pugi::xml_node const & partnerSegmentXML)
|
||||
: m_partnerSegment(segment)
|
||||
, m_positiveOffset(positiveOffset)
|
||||
, m_negativeOffset(negativeOffset)
|
||||
, m_matchedPath(matchedPath)
|
||||
, m_fakePath(fakePath)
|
||||
{
|
||||
SetGoldenPath(goldenPath);
|
||||
|
||||
m_partnerXMLDoc.append_copy(partnerSegmentXML);
|
||||
m_partnerXMLSegment = m_partnerXMLDoc.child("reportSegments");
|
||||
CHECK(m_partnerXMLSegment, ("Node should contain <reportSegments> part"));
|
||||
}
|
||||
|
||||
void SegmentCorrespondence::SetGoldenPath(openlr::Path const & p)
|
||||
{
|
||||
m_goldenPath = p;
|
||||
m_status = p.empty() ? Status::Untouched : Status::Assessed;
|
||||
}
|
||||
|
||||
void SegmentCorrespondence::Ignore()
|
||||
{
|
||||
m_status = Status::Ignored;
|
||||
m_goldenPath.clear();
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class SegmentCorrespondence
|
||||
{
|
||||
public:
|
||||
enum class Status
|
||||
{
|
||||
Untouched,
|
||||
Assessed,
|
||||
Ignored
|
||||
};
|
||||
|
||||
SegmentCorrespondence(SegmentCorrespondence const & sc);
|
||||
SegmentCorrespondence(openlr::LinearSegment const & segment, uint32_t positiveOffset, uint32_t negativeOffset,
|
||||
openlr::Path const & matchedPath, openlr::Path const & fakePath,
|
||||
openlr::Path const & goldenPath, pugi::xml_node const & partnerSegmentXML);
|
||||
|
||||
openlr::Path const & GetMatchedPath() const { return m_matchedPath; }
|
||||
bool HasMatchedPath() const { return !m_matchedPath.empty(); }
|
||||
|
||||
uint32_t GetPositiveOffset() const { return m_positiveOffset; }
|
||||
uint32_t GetNegativeOffset() const { return m_negativeOffset; }
|
||||
|
||||
openlr::Path const & GetFakePath() const { return m_fakePath; }
|
||||
bool HasFakePath() const { return !m_fakePath.empty(); }
|
||||
|
||||
openlr::Path const & GetGoldenPath() const { return m_goldenPath; }
|
||||
bool HasGoldenPath() const { return !m_goldenPath.empty(); }
|
||||
void SetGoldenPath(openlr::Path const & p);
|
||||
|
||||
openlr::LinearSegment const & GetPartnerSegment() const { return m_partnerSegment; }
|
||||
|
||||
uint32_t GetPartnerSegmentId() const { return m_partnerSegment.m_segmentId; }
|
||||
|
||||
pugi::xml_document const & GetPartnerXML() const { return m_partnerXMLDoc; }
|
||||
pugi::xml_node const & GetPartnerXMLSegment() const { return m_partnerXMLSegment; }
|
||||
|
||||
Status GetStatus() const { return m_status; }
|
||||
|
||||
void Ignore();
|
||||
|
||||
private:
|
||||
openlr::LinearSegment m_partnerSegment;
|
||||
|
||||
uint32_t m_positiveOffset = 0;
|
||||
uint32_t m_negativeOffset = 0;
|
||||
|
||||
openlr::Path m_matchedPath;
|
||||
openlr::Path m_fakePath;
|
||||
openlr::Path m_goldenPath;
|
||||
|
||||
// A dirty hack to save back SegmentCorrespondence.
|
||||
// TODO(mgsergio): Consider unifying xml serialization with one used in openlr_stat.
|
||||
pugi::xml_document m_partnerXMLDoc;
|
||||
// This is used by GetPartnerXMLSegment shortcut to return const ref. pugi::xml_node is
|
||||
// just a wrapper so returning by value won't guarantee constness.
|
||||
pugi::xml_node m_partnerXMLSegment;
|
||||
|
||||
Status m_status = Status::Untouched;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <geometry/point2d.hpp>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
/// 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<m2::PointD> const & points) = 0;
|
||||
virtual void DrawEncodedSegment(std::vector<m2::PointD> const & points) = 0;
|
||||
virtual void DrawGoldenPath(std::vector<m2::PointD> const & points) = 0;
|
||||
|
||||
virtual void ClearGoldenPath() = 0;
|
||||
virtual void ClearAllPaths() = 0;
|
||||
|
||||
virtual void VisualizePoints(std::vector<m2::PointD> const & points) = 0;
|
||||
virtual void ClearAllVisualizedPoints() = 0;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,548 @@
|
|||
#include "traffic_mode.hpp"
|
||||
|
||||
#include "openlr/openlr_model_xml.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
|
||||
#include <QItemSelection>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void RemovePointFromPull(m2::PointD const & toBeRemoved, std::vector<m2::PointD> & pool)
|
||||
{
|
||||
pool.erase(remove_if(begin(pool), end(pool),
|
||||
[&toBeRemoved](m2::PointD const & p) { return p.EqualDxDy(toBeRemoved, 1e-6); }),
|
||||
end(pool));
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> GetReachablePoints(m2::PointD const & srcPoint, std::vector<m2::PointD> 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
|
||||
|
||||
namespace impl
|
||||
{
|
||||
// static
|
||||
size_t const RoadPointCandidate::kInvalidId = std::numeric_limits<size_t>::max();
|
||||
|
||||
/// This class denotes a "non-deterministic" feature point.
|
||||
/// I.e. it is a set of all pairs <FeatureID, point index>
|
||||
/// located at a specified coordinate.
|
||||
/// Only one point at a time is considered active.
|
||||
RoadPointCandidate::RoadPointCandidate(std::vector<FeaturePoint> 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
|
||||
|
||||
// TrafficMode -------------------------------------------------------------------------------------
|
||||
TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & dataSource,
|
||||
std::unique_ptr<TrafficDrawerDelegateBase> drawerDelegate,
|
||||
std::unique_ptr<PointsControllerDelegateBase> 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");
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
result.save_file(fileName.data(), " " /* indent */);
|
||||
return true;
|
||||
}
|
||||
|
||||
int TrafficMode::rowCount(QModelIndex const & parent) const
|
||||
{
|
||||
return static_cast<int>(m_segments.size());
|
||||
}
|
||||
|
||||
int TrafficMode::columnCount(QModelIndex const & parent) const
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
QVariant TrafficMode::data(QModelIndex const & index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() >= rowCount())
|
||||
return QVariant();
|
||||
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole)
|
||||
return QVariant();
|
||||
|
||||
if (index.column() == 0)
|
||||
return m_segments[index.row()].GetPartnerSegmentId();
|
||||
|
||||
if (index.column() == 1)
|
||||
return static_cast<int>(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();
|
||||
|
||||
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 &)
|
||||
{
|
||||
ASSERT(!selected.empty(), ());
|
||||
ASSERT(!m_segments.empty(), ());
|
||||
|
||||
auto const row = selected.front().top();
|
||||
|
||||
CHECK_LESS(static_cast<size_t>(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<int>(partnerSegment.m_segmentId));
|
||||
}
|
||||
|
||||
Qt::ItemFlags TrafficMode::flags(QModelIndex const & index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::ItemIsEnabled;
|
||||
|
||||
return QAbstractItemModel::flags(index);
|
||||
}
|
||||
|
||||
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<FeaturePoint> 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<uint32_t>(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<m2::PointD> TrafficMode::GetGoldenPathPoints() const
|
||||
{
|
||||
std::vector<m2::PointD> 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;
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include "points_controller_delegate_base.hpp"
|
||||
#include "segment_correspondence.hpp"
|
||||
#include "traffic_drawer_delegate_base.hpp"
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
class QItemSelection;
|
||||
class Selection;
|
||||
|
||||
DECLARE_EXCEPTION(TrafficModeError, RootException);
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
/// This class denotes a "non-deterministic" feature point.
|
||||
/// I.e. it is a set of all pairs <FeatureID, point index>
|
||||
/// located at a specified coordinate.
|
||||
/// Only one point at a time is considered active.
|
||||
class RoadPointCandidate
|
||||
{
|
||||
public:
|
||||
RoadPointCandidate(std::vector<openlr::FeaturePoint> 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<openlr::FeaturePoint> m_points;
|
||||
|
||||
size_t m_activePointIndex = kInvalidId;
|
||||
};
|
||||
} // namespace impl
|
||||
|
||||
/// 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<TrafficDrawerDelegateBase> drawerDelegate,
|
||||
std::unique_ptr<PointsControllerDelegateBase> pointsDelegate, QObject * parent = Q_NULLPTR);
|
||||
|
||||
bool SaveSampleAs(std::string const & fileName) const;
|
||||
|
||||
int rowCount(QModelIndex const & parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
int columnCount(QModelIndex const & parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
|
||||
|
||||
QVariant data(QModelIndex const & index, int role) const Q_DECL_OVERRIDE;
|
||||
|
||||
Qt::ItemFlags flags(QModelIndex const & index) const Q_DECL_OVERRIDE;
|
||||
|
||||
bool IsBuildingPath() const { return m_buildingPath; }
|
||||
void GoldifyMatchedPath();
|
||||
void StartBuildingPath();
|
||||
void PushPoint(m2::PointD const & coord, std::vector<FeaturePoint> 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<m2::PointD> GetGoldenPathPoints() const;
|
||||
|
||||
public slots:
|
||||
void OnItemSelected(QItemSelection const & selected, QItemSelection const &);
|
||||
void OnClick(m2::PointD const & clickPoint, Qt::MouseButton const button) { HandlePoint(clickPoint, button); }
|
||||
|
||||
signals:
|
||||
void EditingStopped();
|
||||
void SegmentSelected(int segmentId);
|
||||
|
||||
private:
|
||||
void HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button);
|
||||
bool StartBuildingPathChecks() const;
|
||||
|
||||
DataSource const & m_dataSource;
|
||||
std::vector<SegmentCorrespondence> m_segments;
|
||||
// Non-owning pointer to an element of m_segments.
|
||||
SegmentCorrespondence * m_currentSegment = nullptr;
|
||||
|
||||
std::unique_ptr<TrafficDrawerDelegateBase> m_drawerDelegate;
|
||||
std::unique_ptr<PointsControllerDelegateBase> m_pointsDelegate;
|
||||
|
||||
bool m_buildingPath = false;
|
||||
std::vector<impl::RoadPointCandidate> m_goldenPath;
|
||||
|
||||
// Clone this document and add things to its clone when saving sample.
|
||||
pugi::xml_document m_template;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_panel.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp"
|
||||
|
||||
#include <QtCore/QAbstractTableModel>
|
||||
#include <QtWidgets/QBoxLayout>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
#include <QtWidgets/QStyledItemDelegate>
|
||||
#include <QtWidgets/QTableView>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// 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<QComboBox *>(editor)->setCurrentText(value);
|
||||
}
|
||||
|
||||
void ComboBoxDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, QModelIndex const & index) const
|
||||
{
|
||||
model->setData(index, static_cast<QComboBox *>(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 openlr
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtWidgets/QStyledItemDelegate>
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QComboBox;
|
||||
class QTableView;
|
||||
class QWidget;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
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 openlr
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/trafficmodeinitdlg.h"
|
||||
#include "ui_trafficmodeinitdlg.h"
|
||||
|
||||
#include "platform/settings.hpp"
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string const kDataFilePath = "LastOpenlrAssessmentDataFilePath";
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
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 openlr
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
class QLineEdit;
|
||||
|
||||
namespace Ui {
|
||||
class TrafficModeInitDlg;
|
||||
}
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
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 openlr
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TrafficModeInitDlg</class>
|
||||
<widget class="QDialog" name="TrafficModeInitDlg">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>482</width>
|
||||
<height>122</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select traffic files</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="gridLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>441</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="chooseDataFileButton">
|
||||
<property name="text">
|
||||
<string>Choose...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>Ok</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="dataFileName"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="dataLabel">
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Data file:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>dataFileName</tabstop>
|
||||
<tabstop>chooseDataFileButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancelButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>TrafficModeInitDlg</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>290</x>
|
||||
<y>103</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>164</x>
|
||||
<y>98</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>okButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>TrafficModeInitDlg</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>405</x>
|
||||
<y>105</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>59</x>
|
||||
<y>94</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
Loading…
Add table
Add a link
Reference in a new issue