Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
50
tools/openlr/CMakeLists.txt
Normal file
50
tools/openlr/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
project(openlr)
|
||||
|
||||
set(SRC
|
||||
cache_line_size.hpp
|
||||
candidate_paths_getter.cpp
|
||||
candidate_paths_getter.hpp
|
||||
candidate_points_getter.cpp
|
||||
candidate_points_getter.hpp
|
||||
decoded_path.cpp
|
||||
decoded_path.hpp
|
||||
graph.cpp
|
||||
graph.hpp
|
||||
helpers.cpp
|
||||
helpers.hpp
|
||||
openlr_decoder.cpp
|
||||
openlr_decoder.hpp
|
||||
openlr_model.cpp
|
||||
openlr_model.hpp
|
||||
openlr_model_xml.cpp
|
||||
openlr_model_xml.hpp
|
||||
paths_connector.cpp
|
||||
paths_connector.hpp
|
||||
road_info_getter.cpp
|
||||
road_info_getter.hpp
|
||||
router.cpp
|
||||
router.hpp
|
||||
score_candidate_paths_getter.cpp
|
||||
score_candidate_paths_getter.hpp
|
||||
score_candidate_points_getter.cpp
|
||||
score_candidate_points_getter.hpp
|
||||
score_paths_connector.cpp
|
||||
score_paths_connector.hpp
|
||||
score_types.hpp
|
||||
stats.hpp
|
||||
way_point.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
routing
|
||||
indexer
|
||||
geometry
|
||||
pugixml
|
||||
)
|
||||
|
||||
omim_add_tool_subdirectory(openlr_match_quality)
|
||||
omim_add_tool_subdirectory(openlr_stat)
|
||||
|
||||
omim_add_test_subdirectory(openlr_tests)
|
||||
8
tools/openlr/cache_line_size.hpp
Normal file
8
tools/openlr/cache_line_size.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
size_t constexpr kCacheLineSize = 64;
|
||||
} // namespace openlr
|
||||
275
tools/openlr/candidate_paths_getter.cpp
Normal file
275
tools/openlr/candidate_paths_getter.cpp
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
#include "openlr/candidate_paths_getter.hpp"
|
||||
|
||||
#include "openlr/candidate_points_getter.hpp"
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
using namespace routing;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace cpg
|
||||
{
|
||||
int const kNumBuckets = 256;
|
||||
double const kAnglesInBucket = 360.0 / kNumBuckets;
|
||||
|
||||
uint32_t Bearing(m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
auto const angle = location::AngleToBearing(math::RadToDeg(ang::AngleTo(a, b)));
|
||||
CHECK_LESS_OR_EQUAL(angle, 360, ("Angle should be less than or equal to 360."));
|
||||
CHECK_GREATER_OR_EQUAL(angle, 0, ("Angle should be greater than or equal to 0"));
|
||||
return math::Clamp(angle / kAnglesInBucket, 0.0, 255.0);
|
||||
}
|
||||
} // namespace cpg
|
||||
|
||||
// CandidatePathsGetter::Link ----------------------------------------------------------------------
|
||||
Graph::Edge CandidatePathsGetter::Link::GetStartEdge() const
|
||||
{
|
||||
auto * start = this;
|
||||
while (start->m_parent)
|
||||
start = start->m_parent.get();
|
||||
|
||||
return start->m_edge;
|
||||
}
|
||||
|
||||
bool CandidatePathsGetter::Link::IsPointOnPath(geometry::PointWithAltitude const & point) const
|
||||
{
|
||||
for (auto * l = this; l; l = l->m_parent.get())
|
||||
{
|
||||
if (l->m_edge.GetEndJunction() == point)
|
||||
{
|
||||
LOG(LDEBUG, ("A loop detected, skipping..."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// CandidatePathsGetter ----------------------------------------------------------------------------
|
||||
bool CandidatePathsGetter::GetLineCandidatesForPoints(vector<LocationReferencePoint> const & points,
|
||||
vector<vector<Graph::EdgeVector>> & lineCandidates)
|
||||
{
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
if (i != points.size() - 1 && points[i].m_distanceToNextPoint == 0)
|
||||
{
|
||||
LOG(LINFO, ("Distance to next point is zero. Skipping the whole segment"));
|
||||
++m_stats.m_zeroDistToNextPointCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
lineCandidates.emplace_back();
|
||||
auto const isLastPoint = i == points.size() - 1;
|
||||
double const distanceToNextPointM = (isLastPoint ? points[i - 1] : points[i]).m_distanceToNextPoint;
|
||||
|
||||
vector<m2::PointD> pointCandidates;
|
||||
m_pointsGetter.GetCandidatePoints(mercator::FromLatLon(points[i].m_latLon), pointCandidates);
|
||||
GetLineCandidates(points[i], isLastPoint, distanceToNextPointM, pointCandidates, lineCandidates.back());
|
||||
|
||||
if (lineCandidates.back().empty())
|
||||
{
|
||||
LOG(LINFO, ("No candidate lines found for point", points[i].m_latLon, "Giving up"));
|
||||
++m_stats.m_noCandidateFound;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(lineCandidates.size(), points.size(), ());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetStartLines(vector<m2::PointD> const & points, bool const isLastPoint,
|
||||
Graph::EdgeVector & edges)
|
||||
{
|
||||
for (auto const & pc : points)
|
||||
{
|
||||
Graph::EdgeListT tmp;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(geometry::PointWithAltitude(pc, 0 /* altitude */), tmp);
|
||||
else
|
||||
m_graph.GetIngoingEdges(geometry::PointWithAltitude(pc, 0 /* altitude */), tmp);
|
||||
|
||||
edges.insert(edges.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
// Same edges may start on different points if those points are close enough.
|
||||
base::SortUnique(edges, less<Graph::Edge>(), EdgesAreAlmostEqual);
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetAllSuitablePaths(Graph::EdgeVector const & startLines, bool isLastPoint, double bearDistM,
|
||||
FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
double distanceToNextPointM, vector<LinkPtr> & allPaths)
|
||||
{
|
||||
queue<LinkPtr> q;
|
||||
|
||||
for (auto const & e : startLines)
|
||||
{
|
||||
auto const u = make_shared<Link>(nullptr /* parent */, e, 0 /* distanceM */);
|
||||
q.push(u);
|
||||
}
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const u = q.front();
|
||||
q.pop();
|
||||
|
||||
auto const & currentEdge = u->m_edge;
|
||||
auto const currentEdgeLen = EdgeLength(currentEdge);
|
||||
|
||||
// TODO(mgsergio): Maybe weak this constraint a bit.
|
||||
if (u->m_distanceM + currentEdgeLen >= bearDistM)
|
||||
{
|
||||
allPaths.push_back(u);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT_LESS(u->m_distanceM + currentEdgeLen, bearDistM, ());
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(currentEdge.GetEndJunction(), edges);
|
||||
else
|
||||
m_graph.GetIngoingEdges(currentEdge.GetStartJunction(), edges);
|
||||
|
||||
for (auto const & e : edges)
|
||||
{
|
||||
// Fake edges are allowed only at the start/end of the path.
|
||||
if (e.IsFake())
|
||||
continue;
|
||||
|
||||
if (EdgesAreAlmostEqual(e.GetReverseEdge(), currentEdge))
|
||||
continue;
|
||||
|
||||
ASSERT(currentEdge.HasRealPart(), ());
|
||||
|
||||
if (!PassesRestriction(e, functionalRoadClass, formOfWay, 2 /* kFRCThreshold */, m_infoGetter))
|
||||
continue;
|
||||
|
||||
// TODO(mgsergio): Should we check form of way as well?
|
||||
|
||||
if (u->IsPointOnPath(e.GetEndJunction()))
|
||||
continue;
|
||||
|
||||
auto const p = make_shared<Link>(u, e, u->m_distanceM + currentEdgeLen);
|
||||
q.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetBestCandidatePaths(vector<LinkPtr> const & allPaths, bool const isLastPoint,
|
||||
uint32_t const requiredBearing, double const bearDistM,
|
||||
m2::PointD const & startPoint, vector<Graph::EdgeVector> & candidates)
|
||||
{
|
||||
set<CandidatePath> candidatePaths;
|
||||
set<CandidatePath> fakeEndingsCandidatePaths;
|
||||
|
||||
BearingPointsSelector pointsSelector(bearDistM, isLastPoint);
|
||||
for (auto const & l : allPaths)
|
||||
{
|
||||
auto const bearStartPoint = pointsSelector.GetStartPoint(l->GetStartEdge());
|
||||
auto const startPointsDistance = mercator::DistanceOnEarth(bearStartPoint, startPoint);
|
||||
|
||||
// Number of edges counting from the last one to check bearing on. Accorfing to OpenLR spec
|
||||
// we have to check bearing on a point that is no longer than 25 meters traveling down the path.
|
||||
// But sometimes we may skip the best place to stop and generate a candidate. So we check several
|
||||
// edges before the last one to avoid such a situation. Number of iterations is taken
|
||||
// by intuition.
|
||||
// Example:
|
||||
// o -------- o { Partners segment. }
|
||||
// o ------- o --- o { Our candidate. }
|
||||
// ^ 25m
|
||||
// ^ This one may be better than
|
||||
// ^ this one.
|
||||
// So we want to check them all.
|
||||
uint32_t traceBackIterationsLeft = 3;
|
||||
for (auto part = l; part; part = part->m_parent)
|
||||
{
|
||||
if (traceBackIterationsLeft == 0)
|
||||
break;
|
||||
|
||||
--traceBackIterationsLeft;
|
||||
|
||||
auto const bearEndPoint = pointsSelector.GetEndPoint(part->m_edge, part->m_distanceM);
|
||||
|
||||
auto const bearing = cpg::Bearing(bearStartPoint, bearEndPoint);
|
||||
auto const bearingDiff = AbsDifference(bearing, requiredBearing);
|
||||
auto const pathDistDiff = AbsDifference(part->m_distanceM, bearDistM);
|
||||
|
||||
// TODO(mgsergio): Check bearing is within tolerance. Add to canidates if it is.
|
||||
|
||||
if (part->m_hasFake)
|
||||
fakeEndingsCandidatePaths.emplace(part, bearingDiff, pathDistDiff, startPointsDistance);
|
||||
else
|
||||
candidatePaths.emplace(part, bearingDiff, pathDistDiff, startPointsDistance);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(none_of(begin(candidatePaths), end(candidatePaths), mem_fn(&CandidatePath::HasFakeEndings)), ());
|
||||
ASSERT(fakeEndingsCandidatePaths.empty() || any_of(begin(fakeEndingsCandidatePaths), end(fakeEndingsCandidatePaths),
|
||||
mem_fn(&CandidatePath::HasFakeEndings)),
|
||||
());
|
||||
|
||||
vector<CandidatePath> paths;
|
||||
copy_n(begin(candidatePaths), min(static_cast<size_t>(kMaxCandidates), candidatePaths.size()), back_inserter(paths));
|
||||
|
||||
copy_n(begin(fakeEndingsCandidatePaths),
|
||||
min(static_cast<size_t>(kMaxFakeCandidates), fakeEndingsCandidatePaths.size()), back_inserter(paths));
|
||||
|
||||
LOG(LDEBUG, ("List candidate paths..."));
|
||||
for (auto const & path : paths)
|
||||
{
|
||||
LOG(LDEBUG, ("CP:", path.m_bearingDiff, path.m_pathDistanceDiff, path.m_startPointDistance));
|
||||
Graph::EdgeVector edges;
|
||||
for (auto * p = path.m_path.get(); p; p = p->m_parent.get())
|
||||
edges.push_back(p->m_edge);
|
||||
if (!isLastPoint)
|
||||
reverse(begin(edges), end(edges));
|
||||
|
||||
candidates.emplace_back(std::move(edges));
|
||||
}
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetLineCandidates(openlr::LocationReferencePoint const & p, bool const isLastPoint,
|
||||
double const distanceToNextPointM,
|
||||
vector<m2::PointD> const & pointCandidates,
|
||||
vector<Graph::EdgeVector> & candidates)
|
||||
{
|
||||
double const kDefaultBearDistM = 25.0;
|
||||
double const bearDistM = min(kDefaultBearDistM, distanceToNextPointM);
|
||||
|
||||
LOG(LINFO, ("BearDist is", bearDistM));
|
||||
|
||||
Graph::EdgeVector startLines;
|
||||
GetStartLines(pointCandidates, isLastPoint, startLines);
|
||||
|
||||
LOG(LINFO, (startLines.size(), "start line candidates found for point (LatLon)", p.m_latLon));
|
||||
LOG(LDEBUG, ("Listing start lines:"));
|
||||
for (auto const & e : startLines)
|
||||
LOG(LDEBUG, (LogAs2GisPath(e)));
|
||||
|
||||
auto const startPoint = mercator::FromLatLon(p.m_latLon);
|
||||
|
||||
vector<LinkPtr> allPaths;
|
||||
GetAllSuitablePaths(startLines, isLastPoint, bearDistM, p.m_functionalRoadClass, p.m_formOfWay, distanceToNextPointM,
|
||||
allPaths);
|
||||
GetBestCandidatePaths(allPaths, isLastPoint, p.m_bearing, bearDistM, startPoint, candidates);
|
||||
LOG(LDEBUG, (candidates.size(), "candidate paths found for point (LatLon)", p.m_latLon));
|
||||
}
|
||||
} // namespace openlr
|
||||
124
tools/openlr/candidate_paths_getter.hpp
Normal file
124
tools/openlr/candidate_paths_getter.hpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class CandidatePointsGetter;
|
||||
|
||||
class CandidatePathsGetter
|
||||
{
|
||||
public:
|
||||
CandidatePathsGetter(CandidatePointsGetter & pointsGetter, Graph & graph, RoadInfoGetter & infoGetter,
|
||||
v2::Stats & stat)
|
||||
: m_pointsGetter(pointsGetter)
|
||||
, m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stats(stat)
|
||||
{}
|
||||
|
||||
bool GetLineCandidatesForPoints(std::vector<LocationReferencePoint> const & points,
|
||||
std::vector<std::vector<Graph::EdgeVector>> & lineCandidates);
|
||||
|
||||
private:
|
||||
struct Link;
|
||||
using LinkPtr = std::shared_ptr<Link>;
|
||||
|
||||
size_t static constexpr kMaxCandidates = 5;
|
||||
size_t static constexpr kMaxFakeCandidates = 2;
|
||||
|
||||
// TODO(mgsergio): Rename to Vertex.
|
||||
struct Link
|
||||
{
|
||||
Link(LinkPtr const & parent, Graph::Edge const & edge, double const distanceM)
|
||||
: m_parent(parent)
|
||||
, m_edge(edge)
|
||||
, m_distanceM(distanceM)
|
||||
, m_hasFake((parent && parent->m_hasFake) || edge.IsFake())
|
||||
{}
|
||||
|
||||
bool IsPointOnPath(geometry::PointWithAltitude const & point) const;
|
||||
|
||||
Graph::Edge GetStartEdge() const;
|
||||
|
||||
LinkPtr const m_parent;
|
||||
Graph::Edge const m_edge;
|
||||
double const m_distanceM;
|
||||
bool const m_hasFake;
|
||||
};
|
||||
|
||||
struct CandidatePath
|
||||
{
|
||||
double static constexpr kBearingDiffFactor = 5;
|
||||
double static constexpr kPathDistanceFactor = 1;
|
||||
double static constexpr kPointDistanceFactor = 2;
|
||||
|
||||
CandidatePath() = default;
|
||||
|
||||
CandidatePath(LinkPtr const path, uint32_t const bearingDiff, double const pathDistanceDiff,
|
||||
double const startPointDistance)
|
||||
: m_path(path)
|
||||
, m_bearingDiff(bearingDiff)
|
||||
, m_pathDistanceDiff(pathDistanceDiff)
|
||||
, m_startPointDistance(startPointDistance)
|
||||
{}
|
||||
|
||||
bool operator<(CandidatePath const & o) const { return GetPenalty() < o.GetPenalty(); }
|
||||
|
||||
double GetPenalty() const
|
||||
{
|
||||
return kBearingDiffFactor * m_bearingDiff + kPathDistanceFactor * m_pathDistanceDiff +
|
||||
kPointDistanceFactor * m_startPointDistance;
|
||||
}
|
||||
|
||||
bool HasFakeEndings() const { return m_path && m_path->m_hasFake; }
|
||||
|
||||
LinkPtr m_path = nullptr;
|
||||
uint32_t m_bearingDiff = std::numeric_limits<uint32_t>::max(); // Domain is roughly [0, 30]
|
||||
double m_pathDistanceDiff = std::numeric_limits<double>::max(); // Domain is roughly [0, 25]
|
||||
double m_startPointDistance = std::numeric_limits<double>::max(); // Domain is roughly [0, 50]
|
||||
};
|
||||
|
||||
// Note: In all methods below if |isLastPoint| is true than algorithm should
|
||||
// calculate all parameters (such as bearing, distance to next point, etc.)
|
||||
// relative to the last point.
|
||||
// o ----> o ----> o <---- o.
|
||||
// 1 2 3 4
|
||||
// ^ isLastPoint = true.
|
||||
// To calculate bearing for points 1 to 3 one have to go beardist from
|
||||
// previous point to the next one (eg. from 1 to 2 and from 2 to 3).
|
||||
// For point 4 one have to go from 4 to 3 reversing directions. And
|
||||
// distance-to-next point is taken from point 3. You can learn more in
|
||||
// TomTom OpenLR spec.
|
||||
|
||||
void GetStartLines(std::vector<m2::PointD> const & points, bool const isLastPoint, Graph::EdgeVector & edges);
|
||||
|
||||
void GetAllSuitablePaths(Graph::EdgeVector const & startLines, bool isLastPoint, double bearDistM,
|
||||
FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay, double distanceToNextPointM,
|
||||
std::vector<LinkPtr> & allPaths);
|
||||
|
||||
void GetBestCandidatePaths(std::vector<LinkPtr> const & allPaths, bool const isLastPoint,
|
||||
uint32_t const requiredBearing, double const bearDistM, m2::PointD const & startPoint,
|
||||
std::vector<Graph::EdgeVector> & candidates);
|
||||
|
||||
void GetLineCandidates(openlr::LocationReferencePoint const & p, bool const isLastPoint,
|
||||
double const distanceToNextPointM, std::vector<m2::PointD> const & pointCandidates,
|
||||
std::vector<Graph::EdgeVector> & candidates);
|
||||
|
||||
CandidatePointsGetter & m_pointsGetter;
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stats;
|
||||
};
|
||||
} // namespace openlr
|
||||
82
tools/openlr/candidate_points_getter.cpp
Normal file
82
tools/openlr/candidate_points_getter.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include "openlr/candidate_points_getter.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace routing;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
void CandidatePointsGetter::FillJunctionPointCandidates(m2::PointD const & p, std::vector<m2::PointD> & candidates)
|
||||
{
|
||||
// TODO(mgsergio): Get optimal value using experiments on a sample.
|
||||
// Or start with small radius and scale it up when there are too few points.
|
||||
size_t const kRectSideMeters = 110;
|
||||
|
||||
auto const rect = mercator::RectByCenterXYAndSizeInMeters(p, kRectSideMeters);
|
||||
auto const selectCandidates = [&rect, &candidates](FeatureType & ft)
|
||||
{
|
||||
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
ft.ForEachPoint([&rect, &candidates](m2::PointD const & candidate)
|
||||
{
|
||||
if (rect.IsPointInside(candidate))
|
||||
candidates.emplace_back(candidate);
|
||||
}, scales::GetUpperScale());
|
||||
};
|
||||
|
||||
m_dataSource.ForEachInRect(selectCandidates, rect, scales::GetUpperScale());
|
||||
|
||||
// TODO: Move this to a separate stage.
|
||||
// 1030292476 Does not match. Some problem occur with points.
|
||||
// Either points duplicate or something alike. Check this
|
||||
// later. The idea to fix this was to move SortUnique to the stage
|
||||
// after enriching with projections.
|
||||
|
||||
base::SortUnique(candidates, [&p](m2::PointD const & a, m2::PointD const & b)
|
||||
{ return mercator::DistanceOnEarth(a, p) < mercator::DistanceOnEarth(b, p); },
|
||||
[](m2::PointD const & a, m2::PointD const & b) { return a == b; });
|
||||
|
||||
candidates.resize(std::min(m_maxJunctionCandidates, candidates.size()));
|
||||
}
|
||||
|
||||
void CandidatePointsGetter::EnrichWithProjectionPoints(m2::PointD const & p, std::vector<m2::PointD> & candidates)
|
||||
{
|
||||
m_graph.ResetFakes();
|
||||
|
||||
std::vector<std::pair<Graph::Edge, geometry::PointWithAltitude>> vicinities;
|
||||
m_graph.FindClosestEdges(p, static_cast<uint32_t>(m_maxProjectionCandidates), vicinities);
|
||||
for (auto const & v : vicinities)
|
||||
{
|
||||
auto const & edge = v.first;
|
||||
auto const & junction = v.second;
|
||||
|
||||
ASSERT(edge.HasRealPart() && !edge.IsFake(), ());
|
||||
|
||||
if (PointsAreClose(edge.GetStartPoint(), junction.GetPoint()) ||
|
||||
PointsAreClose(edge.GetEndPoint(), junction.GetPoint()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const firstHalf = Edge::MakeFake(edge.GetStartJunction(), junction, edge);
|
||||
auto const secondHalf = Edge::MakeFake(junction, edge.GetEndJunction(), edge);
|
||||
|
||||
m_graph.AddOutgoingFakeEdge(firstHalf);
|
||||
m_graph.AddIngoingFakeEdge(firstHalf);
|
||||
m_graph.AddOutgoingFakeEdge(secondHalf);
|
||||
m_graph.AddIngoingFakeEdge(secondHalf);
|
||||
candidates.push_back(junction.GetPoint());
|
||||
}
|
||||
}
|
||||
} // namespace openlr
|
||||
43
tools/openlr/candidate_points_getter.hpp
Normal file
43
tools/openlr/candidate_points_getter.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class CandidatePointsGetter
|
||||
{
|
||||
public:
|
||||
CandidatePointsGetter(size_t const maxJunctionCandidates, size_t const maxProjectionCandidates,
|
||||
DataSource const & dataSource, Graph & graph)
|
||||
: m_maxJunctionCandidates(maxJunctionCandidates)
|
||||
, m_maxProjectionCandidates(maxProjectionCandidates)
|
||||
, m_dataSource(dataSource)
|
||||
, m_graph(graph)
|
||||
{}
|
||||
|
||||
void GetCandidatePoints(m2::PointD const & p, std::vector<m2::PointD> & candidates)
|
||||
{
|
||||
FillJunctionPointCandidates(p, candidates);
|
||||
EnrichWithProjectionPoints(p, candidates);
|
||||
}
|
||||
|
||||
private:
|
||||
void FillJunctionPointCandidates(m2::PointD const & p, std::vector<m2::PointD> & candidates);
|
||||
void EnrichWithProjectionPoints(m2::PointD const & p, std::vector<m2::PointD> & candidates);
|
||||
|
||||
size_t const m_maxJunctionCandidates;
|
||||
size_t const m_maxProjectionCandidates;
|
||||
|
||||
DataSource const & m_dataSource;
|
||||
Graph & m_graph;
|
||||
};
|
||||
} // namespace openlr
|
||||
175
tools/openlr/decoded_path.cpp
Normal file
175
tools/openlr/decoded_path.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#include "openlr/decoded_path.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#define THROW_IF_NODE_IS_EMPTY(node, exc, msg) \
|
||||
if (!node) \
|
||||
MYTHROW(exc, msg)
|
||||
|
||||
namespace
|
||||
{
|
||||
uint32_t UintFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse uint"));
|
||||
return node.text().as_uint();
|
||||
}
|
||||
|
||||
bool IsForwardFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse IsForward"));
|
||||
return node.text().as_bool();
|
||||
}
|
||||
|
||||
void LatLonToXML(ms::LatLon const & latLon, pugi::xml_node & node)
|
||||
{
|
||||
auto const kDigitsAfterComma = 5;
|
||||
node.append_child("lat").text() = strings::to_string_dac(latLon.m_lat, kDigitsAfterComma).data();
|
||||
node.append_child("lon").text() = strings::to_string_dac(latLon.m_lon, kDigitsAfterComma).data();
|
||||
}
|
||||
|
||||
void LatLonFromXML(pugi::xml_node const & node, ms::LatLon & latLon)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse latLon"));
|
||||
latLon.m_lat = node.child("lat").text().as_double();
|
||||
latLon.m_lon = node.child("lon").text().as_double();
|
||||
}
|
||||
|
||||
void FeatureIdFromXML(pugi::xml_node const & node, DataSource const & dataSource, FeatureID & fid)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse CountryName"));
|
||||
auto const countryName = node.child("CountryName").text().as_string();
|
||||
fid.m_mwmId = dataSource.GetMwmIdByCountryFile(platform::CountryFile(countryName));
|
||||
CHECK(fid.m_mwmId.IsAlive(), ("Can't get mwm id for country", countryName));
|
||||
fid.m_index = node.child("Index").text().as_uint();
|
||||
}
|
||||
|
||||
void FeatureIdToXML(FeatureID const & fid, pugi::xml_node & node)
|
||||
{
|
||||
node.append_child("CountryName").text() = fid.m_mwmId.GetInfo()->GetCountryName().data();
|
||||
node.append_child("Index").text() = fid.m_index;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
uint32_t UintValueFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
auto const value = node.child("olr:value");
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
return UintFromXML(value);
|
||||
}
|
||||
|
||||
void WriteAsMappingForSpark(std::string const & fileName, std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
std::ofstream ofs(fileName);
|
||||
if (!ofs.is_open())
|
||||
MYTHROW(DecodedPathSaveError, ("Can't write to file", fileName, strerror(errno)));
|
||||
|
||||
WriteAsMappingForSpark(ofs, paths);
|
||||
|
||||
if (ofs.fail())
|
||||
MYTHROW(DecodedPathSaveError, ("An error occured while writing file", fileName, strerror(errno)));
|
||||
}
|
||||
|
||||
void WriteAsMappingForSpark(std::ostream & ost, std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
auto const flags = ost.flags();
|
||||
ost << std::fixed; // Avoid scientific notation cause '-' is used as fields separator.
|
||||
SCOPE_GUARD(guard, ([&ost, &flags] { ost.flags(flags); }));
|
||||
|
||||
for (auto const & p : paths)
|
||||
{
|
||||
if (p.m_path.empty())
|
||||
continue;
|
||||
|
||||
ost << p.m_segmentId.Get() << '\t';
|
||||
|
||||
auto const kFieldSep = '-';
|
||||
auto const kSegmentSep = '=';
|
||||
for (auto it = std::begin(p.m_path); it != std::end(p.m_path); ++it)
|
||||
{
|
||||
auto const & fid = it->GetFeatureId();
|
||||
ost << fid.m_mwmId.GetInfo()->GetCountryName() << kFieldSep << fid.m_index << kFieldSep << it->GetSegId()
|
||||
<< kFieldSep << (it->IsForward() ? "fwd" : "bwd") << kFieldSep
|
||||
<< mercator::DistanceOnEarth(GetStart(*it), GetEnd(*it));
|
||||
|
||||
if (std::next(it) != std::end(p.m_path))
|
||||
ost << kSegmentSep;
|
||||
}
|
||||
ost << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void PathFromXML(pugi::xml_node const & node, DataSource const & dataSource, Path & p)
|
||||
{
|
||||
auto const edges = node.select_nodes("RoadEdge");
|
||||
for (auto const & xmlE : edges)
|
||||
{
|
||||
auto e = xmlE.node();
|
||||
|
||||
FeatureID fid;
|
||||
FeatureIdFromXML(e.child("FeatureID"), dataSource, fid);
|
||||
|
||||
auto const isForward = IsForwardFromXML(e.child("IsForward"));
|
||||
auto const segmentId = UintFromXML(e.child("SegmentId"));
|
||||
|
||||
ms::LatLon start, end;
|
||||
LatLonFromXML(e.child("StartJunction"), start);
|
||||
LatLonFromXML(e.child("EndJunction"), end);
|
||||
|
||||
p.push_back(
|
||||
Edge::MakeReal(fid, isForward, segmentId,
|
||||
geometry::PointWithAltitude(mercator::FromLatLon(start), geometry::kDefaultAltitudeMeters),
|
||||
geometry::PointWithAltitude(mercator::FromLatLon(end), geometry::kDefaultAltitudeMeters)));
|
||||
}
|
||||
}
|
||||
|
||||
void PathToXML(Path const & path, pugi::xml_node & node)
|
||||
{
|
||||
for (auto const & e : path)
|
||||
{
|
||||
auto edge = node.append_child("RoadEdge");
|
||||
|
||||
{
|
||||
auto fid = edge.append_child("FeatureID");
|
||||
FeatureIdToXML(e.GetFeatureId(), fid);
|
||||
}
|
||||
|
||||
edge.append_child("IsForward").text() = e.IsForward();
|
||||
edge.append_child("SegmentId").text() = e.GetSegId();
|
||||
{
|
||||
auto start = edge.append_child("StartJunction");
|
||||
auto end = edge.append_child("EndJunction");
|
||||
LatLonToXML(mercator::ToLatLon(GetStart(e)), start);
|
||||
LatLonToXML(mercator::ToLatLon(GetEnd(e)), end);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace openlr
|
||||
|
||||
namespace routing
|
||||
{
|
||||
std::vector<m2::PointD> GetPoints(openlr::Path const & p)
|
||||
{
|
||||
CHECK(!p.empty(), ("Path should not be empty"));
|
||||
std::vector<m2::PointD> points;
|
||||
points.push_back(GetStart(p.front()));
|
||||
for (auto const & e : p)
|
||||
points.push_back(GetEnd(e));
|
||||
|
||||
return points;
|
||||
}
|
||||
} // namespace routing
|
||||
55
tools/openlr/decoded_path.hpp
Normal file
55
tools/openlr/decoded_path.hpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
#include "base/newtype.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
class DataSource;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
using Path = routing::RoadGraphBase::EdgeVector;
|
||||
using routing::Edge;
|
||||
|
||||
NEWTYPE(uint32_t, PartnerSegmentId);
|
||||
NEWTYPE_SIMPLE_OUTPUT(PartnerSegmentId);
|
||||
|
||||
DECLARE_EXCEPTION(DecodedPathLoadError, RootException);
|
||||
DECLARE_EXCEPTION(DecodedPathSaveError, RootException);
|
||||
|
||||
struct DecodedPath
|
||||
{
|
||||
PartnerSegmentId m_segmentId;
|
||||
Path m_path;
|
||||
};
|
||||
|
||||
uint32_t UintValueFromXML(pugi::xml_node const & node);
|
||||
|
||||
void WriteAsMappingForSpark(std::string const & fileName, std::vector<DecodedPath> const & paths);
|
||||
void WriteAsMappingForSpark(std::ostream & ost, std::vector<DecodedPath> const & paths);
|
||||
|
||||
void PathFromXML(pugi::xml_node const & node, DataSource const & dataSource, Path & path);
|
||||
void PathToXML(Path const & path, pugi::xml_node & node);
|
||||
} // namespace openlr
|
||||
|
||||
namespace routing
|
||||
{
|
||||
inline m2::PointD GetStart(Edge const & e)
|
||||
{
|
||||
return e.GetStartJunction().GetPoint();
|
||||
}
|
||||
inline m2::PointD GetEnd(Edge const & e)
|
||||
{
|
||||
return e.GetEndJunction().GetPoint();
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> GetPoints(routing::RoadGraphBase::EdgeVector const & p);
|
||||
} // namespace routing
|
||||
86
tools/openlr/graph.cpp
Normal file
86
tools/openlr/graph.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include "openlr/graph.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using EdgeGetter = void (IRoadGraph::*)(geometry::PointWithAltitude const &, RoadGraphBase::EdgeListT &) const;
|
||||
|
||||
void GetRegularEdges(geometry::PointWithAltitude const & junction, IRoadGraph const & graph,
|
||||
EdgeGetter const edgeGetter, Graph::EdgeCacheT & cache, Graph::EdgeListT & edges)
|
||||
{
|
||||
auto const it = cache.find(junction);
|
||||
if (it == end(cache))
|
||||
{
|
||||
auto & es = cache[junction];
|
||||
(graph.*edgeGetter)(junction, es);
|
||||
edges.append(begin(es), end(es));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const & es = it->second;
|
||||
edges.append(begin(es), end(es));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Graph::Graph(DataSource & dataSource, shared_ptr<CarModelFactory> carModelFactory)
|
||||
: m_dataSource(dataSource, nullptr /* numMwmIDs */)
|
||||
, m_graph(m_dataSource, IRoadGraph::Mode::ObeyOnewayTag, carModelFactory)
|
||||
{}
|
||||
|
||||
void Graph::GetOutgoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularOutgoingEdges(junction, edges);
|
||||
m_graph.GetFakeOutgoingEdges(junction, edges);
|
||||
}
|
||||
|
||||
void Graph::GetIngoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularIngoingEdges(junction, edges);
|
||||
m_graph.GetFakeIngoingEdges(junction, edges);
|
||||
}
|
||||
|
||||
void Graph::GetRegularOutgoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularEdges(junction, m_graph, &IRoadGraph::GetRegularOutgoingEdges, m_outgoingCache, edges);
|
||||
}
|
||||
|
||||
void Graph::GetRegularIngoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularEdges(junction, m_graph, &IRoadGraph::GetRegularIngoingEdges, m_ingoingCache, edges);
|
||||
}
|
||||
|
||||
void Graph::FindClosestEdges(m2::PointD const & point, uint32_t const count,
|
||||
vector<pair<Edge, Junction>> & vicinities) const
|
||||
{
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(point, FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
count, vicinities);
|
||||
}
|
||||
|
||||
void Graph::AddIngoingFakeEdge(Edge const & e)
|
||||
{
|
||||
m_graph.AddIngoingFakeEdge(e);
|
||||
}
|
||||
|
||||
void Graph::AddOutgoingFakeEdge(Edge const & e)
|
||||
{
|
||||
m_graph.AddOutgoingFakeEdge(e);
|
||||
}
|
||||
|
||||
void Graph::GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const
|
||||
{
|
||||
m_graph.GetFeatureTypes(featureId, types);
|
||||
}
|
||||
} // namespace openlr
|
||||
62
tools/openlr/graph.hpp
Normal file
62
tools/openlr/graph.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/data_source.hpp"
|
||||
#include "routing/features_road_graph.hpp"
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "routing_common/car_model.hpp"
|
||||
|
||||
#include "indexer/feature_data.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// TODO(mgsergio): Inherit from FeaturesRoadGraph.
|
||||
class Graph
|
||||
{
|
||||
public:
|
||||
using Edge = routing::Edge;
|
||||
using EdgeListT = routing::FeaturesRoadGraph::EdgeListT;
|
||||
using EdgeVector = routing::FeaturesRoadGraph::EdgeVector;
|
||||
using Junction = geometry::PointWithAltitude;
|
||||
|
||||
Graph(DataSource & dataSource, std::shared_ptr<routing::CarModelFactory> carModelFactory);
|
||||
|
||||
// Appends edges such as that edge.GetStartJunction() == junction to the |edges|.
|
||||
void GetOutgoingEdges(geometry::PointWithAltitude const & junction, EdgeListT & edges);
|
||||
// Appends edges such as that edge.GetEndJunction() == junction to the |edges|.
|
||||
void GetIngoingEdges(geometry::PointWithAltitude const & junction, EdgeListT & edges);
|
||||
|
||||
// Appends edges such as that edge.GetStartJunction() == junction and edge.IsFake() == false
|
||||
// to the |edges|.
|
||||
void GetRegularOutgoingEdges(Junction const & junction, EdgeListT & edges);
|
||||
// Appends edges such as that edge.GetEndJunction() == junction and edge.IsFale() == false
|
||||
// to the |edges|.
|
||||
void GetRegularIngoingEdges(Junction const & junction, EdgeListT & edges);
|
||||
|
||||
void FindClosestEdges(m2::PointD const & point, uint32_t const count,
|
||||
std::vector<std::pair<Edge, Junction>> & vicinities) const;
|
||||
|
||||
void AddIngoingFakeEdge(Edge const & e);
|
||||
void AddOutgoingFakeEdge(Edge const & e);
|
||||
|
||||
void ResetFakes() { m_graph.ResetFakes(); }
|
||||
|
||||
void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const;
|
||||
|
||||
using EdgeCacheT = std::map<Junction, EdgeListT>;
|
||||
|
||||
private:
|
||||
routing::MwmDataSource m_dataSource;
|
||||
routing::FeaturesRoadGraph m_graph;
|
||||
EdgeCacheT m_outgoingCache, m_ingoingCache;
|
||||
};
|
||||
} // namespace openlr
|
||||
230
tools/openlr/helpers.cpp
Normal file
230
tools/openlr/helpers.cpp
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
|
||||
#include "routing/features_road_graph.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/stl_iterator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace openlr;
|
||||
using namespace std;
|
||||
|
||||
openlr::FunctionalRoadClass HighwayClassToFunctionalRoadClass(ftypes::HighwayClass const & hwClass)
|
||||
{
|
||||
switch (hwClass)
|
||||
{
|
||||
case ftypes::HighwayClass::Motorway: return openlr::FunctionalRoadClass::FRC0;
|
||||
case ftypes::HighwayClass::Trunk: return openlr::FunctionalRoadClass::FRC0;
|
||||
case ftypes::HighwayClass::Primary: return openlr::FunctionalRoadClass::FRC1;
|
||||
case ftypes::HighwayClass::Secondary: return openlr::FunctionalRoadClass::FRC2;
|
||||
case ftypes::HighwayClass::Tertiary: return openlr::FunctionalRoadClass::FRC3;
|
||||
case ftypes::HighwayClass::LivingStreet: return openlr::FunctionalRoadClass::FRC4;
|
||||
case ftypes::HighwayClass::Service: return openlr::FunctionalRoadClass::FRC5;
|
||||
case ftypes::HighwayClass::ServiceMinor: return openlr::FunctionalRoadClass::FRC6;
|
||||
default: return openlr::FunctionalRoadClass::FRC7;
|
||||
}
|
||||
}
|
||||
|
||||
/// \returns nullopt if |e| doesn't conform to |functionalRoadClass| and score otherwise.
|
||||
optional<Score> GetFrcScore(Graph::Edge const & e, FunctionalRoadClass functionalRoadClass, RoadInfoGetter & infoGetter)
|
||||
{
|
||||
CHECK(!e.IsFake(), ());
|
||||
Score constexpr kMaxScoreForFrc = 25;
|
||||
|
||||
if (functionalRoadClass == FunctionalRoadClass::NotAValue)
|
||||
return nullopt;
|
||||
|
||||
auto const hwClass = infoGetter.Get(e.GetFeatureId()).m_hwClass;
|
||||
|
||||
using ftypes::HighwayClass;
|
||||
|
||||
switch (functionalRoadClass)
|
||||
{
|
||||
case FunctionalRoadClass::FRC0:
|
||||
return hwClass == HighwayClass::Motorway || hwClass == HighwayClass::Trunk ? optional<Score>(kMaxScoreForFrc) : nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC1:
|
||||
return (hwClass == HighwayClass::Motorway || hwClass == HighwayClass::Trunk || hwClass == HighwayClass::Primary) ? optional<Score>(kMaxScoreForFrc)
|
||||
: nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC2:
|
||||
case FunctionalRoadClass::FRC3:
|
||||
if (hwClass == HighwayClass::Secondary || hwClass == HighwayClass::Tertiary)
|
||||
return optional<Score>(kMaxScoreForFrc);
|
||||
|
||||
return hwClass == HighwayClass::Primary || hwClass == HighwayClass::LivingStreet ? optional<Score>(0) : nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC4:
|
||||
if (hwClass == HighwayClass::LivingStreet || hwClass == HighwayClass::Service)
|
||||
return optional<Score>(kMaxScoreForFrc);
|
||||
|
||||
return (hwClass == HighwayClass::Tertiary || hwClass == HighwayClass::Secondary) ? optional<Score>(0) : nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC5:
|
||||
case FunctionalRoadClass::FRC6:
|
||||
case FunctionalRoadClass::FRC7:
|
||||
return (hwClass == HighwayClass::LivingStreet || hwClass == HighwayClass::Service ||
|
||||
hwClass == HighwayClass::ServiceMinor)
|
||||
? optional<Score>(kMaxScoreForFrc)
|
||||
: nullopt;
|
||||
|
||||
case FunctionalRoadClass::NotAValue: UNREACHABLE();
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// BearingPointsSelector ---------------------------------------------------------------------------
|
||||
BearingPointsSelector::BearingPointsSelector(uint32_t bearDistM, bool isLastPoint)
|
||||
: m_bearDistM(bearDistM)
|
||||
, m_isLastPoint(isLastPoint)
|
||||
{}
|
||||
|
||||
m2::PointD BearingPointsSelector::GetStartPoint(Graph::Edge const & e) const
|
||||
{
|
||||
return m_isLastPoint ? e.GetEndPoint() : e.GetStartPoint();
|
||||
}
|
||||
|
||||
m2::PointD BearingPointsSelector::GetEndPoint(Graph::Edge const & e, double distanceM) const
|
||||
{
|
||||
if (distanceM < m_bearDistM && m_bearDistM <= distanceM + EdgeLength(e))
|
||||
{
|
||||
auto const edgeLen = EdgeLength(e);
|
||||
auto const edgeBearDist = min(m_bearDistM - distanceM, edgeLen);
|
||||
CHECK_LESS_OR_EQUAL(edgeBearDist, edgeLen, ());
|
||||
return m_isLastPoint ? PointAtSegmentM(e.GetEndPoint(), e.GetStartPoint(), edgeBearDist)
|
||||
: PointAtSegmentM(e.GetStartPoint(), e.GetEndPoint(), edgeBearDist);
|
||||
}
|
||||
return m_isLastPoint ? e.GetStartPoint() : e.GetEndPoint();
|
||||
}
|
||||
|
||||
bool PointsAreClose(m2::PointD const & p1, m2::PointD const & p2)
|
||||
{
|
||||
double const kMwmRoadCrossingRadiusMeters = routing::GetRoadCrossingRadiusMeters();
|
||||
return mercator::DistanceOnEarth(p1, p2) < kMwmRoadCrossingRadiusMeters;
|
||||
}
|
||||
|
||||
double EdgeLength(Graph::Edge const & e)
|
||||
{
|
||||
return mercator::DistanceOnEarth(e.GetStartPoint(), e.GetEndPoint());
|
||||
}
|
||||
|
||||
bool EdgesAreAlmostEqual(Graph::Edge const & e1, Graph::Edge const & e2)
|
||||
{
|
||||
// TODO(mgsergio): Do I need to check fields other than points?
|
||||
return PointsAreClose(e1.GetStartPoint(), e2.GetStartPoint()) && PointsAreClose(e1.GetEndPoint(), e2.GetEndPoint());
|
||||
}
|
||||
|
||||
string LogAs2GisPath(Graph::EdgeVector const & path)
|
||||
{
|
||||
CHECK(!path.empty(), ("Paths should not be empty"));
|
||||
|
||||
ostringstream ost;
|
||||
ost << "https://2gis.ru/moscow?queryState=";
|
||||
|
||||
auto ll = mercator::ToLatLon(path.front().GetStartPoint());
|
||||
ost << "center%2F" << ll.m_lon << "%2C" << ll.m_lat << "%2F";
|
||||
ost << "zoom%2F" << 17 << "%2F";
|
||||
ost << "ruler%2Fpoints%2F";
|
||||
for (auto const & e : path)
|
||||
{
|
||||
ll = mercator::ToLatLon(e.GetStartPoint());
|
||||
ost << ll.m_lon << "%20" << ll.m_lat << "%2C";
|
||||
}
|
||||
ll = mercator::ToLatLon(path.back().GetEndPoint());
|
||||
ost << ll.m_lon << "%20" << ll.m_lat;
|
||||
|
||||
return ost.str();
|
||||
}
|
||||
|
||||
string LogAs2GisPath(Graph::Edge const & e)
|
||||
{
|
||||
return LogAs2GisPath(Graph::EdgeVector({e}));
|
||||
}
|
||||
|
||||
bool PassesRestriction(Graph::Edge const & e, FunctionalRoadClass restriction, FormOfWay formOfWay, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter)
|
||||
{
|
||||
if (e.IsFake() || restriction == FunctionalRoadClass::NotAValue)
|
||||
return true;
|
||||
|
||||
auto const frc = HighwayClassToFunctionalRoadClass(infoGetter.Get(e.GetFeatureId()).m_hwClass);
|
||||
return static_cast<int>(frc) <= static_cast<int>(restriction) + frcThreshold;
|
||||
}
|
||||
|
||||
bool PassesRestrictionV3(Graph::Edge const & e, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
RoadInfoGetter & infoGetter, Score & score)
|
||||
{
|
||||
CHECK(!e.IsFake(), ("Edges should not be fake:", e));
|
||||
auto const frcScore = GetFrcScore(e, functionalRoadClass, infoGetter);
|
||||
if (!frcScore)
|
||||
return false;
|
||||
|
||||
score = *frcScore;
|
||||
Score constexpr kScoreForFormOfWay = 25;
|
||||
if (formOfWay == FormOfWay::Roundabout && infoGetter.Get(e.GetFeatureId()).m_isRoundabout)
|
||||
score += kScoreForFormOfWay;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConformLfrcnp(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter)
|
||||
{
|
||||
if (e.IsFake() || lowestFrcToNextPoint == FunctionalRoadClass::NotAValue)
|
||||
return true;
|
||||
|
||||
auto const frc = HighwayClassToFunctionalRoadClass(infoGetter.Get(e.GetFeatureId()).m_hwClass);
|
||||
return static_cast<int>(frc) <= static_cast<int>(lowestFrcToNextPoint) + frcThreshold;
|
||||
}
|
||||
|
||||
bool ConformLfrcnpV3(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, RoadInfoGetter & infoGetter)
|
||||
{
|
||||
return GetFrcScore(e, lowestFrcToNextPoint, infoGetter).has_value();
|
||||
}
|
||||
|
||||
size_t IntersectionLen(Graph::EdgeVector a, Graph::EdgeVector b)
|
||||
{
|
||||
sort(a.begin(), a.end());
|
||||
sort(b.begin(), b.end());
|
||||
return set_intersection(a.begin(), a.end(), b.begin(), b.end(), CounterIterator()).GetCount();
|
||||
}
|
||||
|
||||
bool SuffixEqualsPrefix(Graph::EdgeVector const & a, Graph::EdgeVector const & b, size_t len)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(len, a.size(), ());
|
||||
CHECK_LESS_OR_EQUAL(len, b.size(), ());
|
||||
return equal(a.end() - len, a.end(), b.begin());
|
||||
}
|
||||
|
||||
// Returns a length of the longest suffix of |a| that matches any prefix of |b|.
|
||||
// Neither |a| nor |b| can contain several repetitions of any edge.
|
||||
// Returns -1 if |a| intersection |b| is not equal to some suffix of |a| and some prefix of |b|.
|
||||
int32_t PathOverlappingLen(Graph::EdgeVector const & a, Graph::EdgeVector const & b)
|
||||
{
|
||||
auto const len = IntersectionLen(a, b);
|
||||
if (SuffixEqualsPrefix(a, b, len))
|
||||
return base::checked_cast<int32_t>(len);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
m2::PointD PointAtSegmentM(m2::PointD const & p1, m2::PointD const & p2, double const distanceM)
|
||||
{
|
||||
auto const v = p2 - p1;
|
||||
auto const l = v.Length();
|
||||
auto const L = mercator::DistanceOnEarth(p1, p2);
|
||||
auto const delta = distanceM * l / L;
|
||||
return PointAtSegment(p1, p2, delta);
|
||||
}
|
||||
} // namespace openlr
|
||||
71
tools/openlr/helpers.hpp
Normal file
71
tools/openlr/helpers.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class RoadInfoGetter;
|
||||
|
||||
// This class is used to get points for further bearing calculations.
|
||||
class BearingPointsSelector
|
||||
{
|
||||
public:
|
||||
BearingPointsSelector(uint32_t bearDistM, bool isLastPoint);
|
||||
|
||||
m2::PointD GetStartPoint(Graph::Edge const & e) const;
|
||||
m2::PointD GetEndPoint(Graph::Edge const & e, double distanceM) const;
|
||||
|
||||
private:
|
||||
double m_bearDistM;
|
||||
bool m_isLastPoint;
|
||||
};
|
||||
|
||||
bool PointsAreClose(m2::PointD const & p1, m2::PointD const & p2);
|
||||
|
||||
double EdgeLength(Graph::Edge const & e);
|
||||
|
||||
bool EdgesAreAlmostEqual(Graph::Edge const & e1, Graph::Edge const & e2);
|
||||
|
||||
// TODO(mgsergio): Remove when unused.
|
||||
std::string LogAs2GisPath(Graph::EdgeVector const & path);
|
||||
std::string LogAs2GisPath(Graph::Edge const & e);
|
||||
|
||||
template <typename T, typename U, std::enable_if_t<!(std::is_signed<T>::value ^ std::is_signed<U>::value), int> = 0>
|
||||
std::common_type_t<T, U> AbsDifference(T const a, U const b)
|
||||
{
|
||||
return a >= b ? a - b : b - a;
|
||||
}
|
||||
|
||||
bool PassesRestriction(Graph::Edge const & e, FunctionalRoadClass restriction, FormOfWay formOfWay, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter);
|
||||
/// \returns true if |e| conforms |functionalRoadClass| and |formOfWay| and false otherwise.
|
||||
/// \note If the method returns true |score| should be considered next.
|
||||
bool PassesRestrictionV3(Graph::Edge const & e, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
RoadInfoGetter & infoGetter, Score & score);
|
||||
|
||||
/// \returns true if edge |e| conforms Lowest Functional Road Class to Next Point.
|
||||
/// \note frc means Functional Road Class. Please see openlr documentation for details:
|
||||
/// http://www.openlr.org/data/docs/whitepaper/1_0/OpenLR-Whitepaper_v1.0.pdf
|
||||
bool ConformLfrcnp(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter);
|
||||
bool ConformLfrcnpV3(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, RoadInfoGetter & infoGetter);
|
||||
|
||||
size_t IntersectionLen(Graph::EdgeVector a, Graph::EdgeVector b);
|
||||
|
||||
bool SuffixEqualsPrefix(Graph::EdgeVector const & a, Graph::EdgeVector const & b, size_t len);
|
||||
|
||||
// Returns a length of the longest suffix of |a| that matches any prefix of |b|.
|
||||
// Neither |a| nor |b| can contain several repetitions of any edge.
|
||||
// Returns -1 if |a| intersection |b| is not equal to some suffix of |a| and some prefix of |b|.
|
||||
int32_t PathOverlappingLen(Graph::EdgeVector const & a, Graph::EdgeVector const & b);
|
||||
|
||||
m2::PointD PointAtSegmentM(m2::PointD const & p1, m2::PointD const & p2, double const distanceM);
|
||||
} // namespace openlr
|
||||
499
tools/openlr/openlr_decoder.cpp
Normal file
499
tools/openlr/openlr_decoder.cpp
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
#include "openlr/openlr_decoder.hpp"
|
||||
|
||||
#include "openlr/cache_line_size.hpp"
|
||||
#include "openlr/candidate_paths_getter.hpp"
|
||||
#include "openlr/candidate_points_getter.hpp"
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/paths_connector.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/router.hpp"
|
||||
#include "openlr/score_candidate_paths_getter.hpp"
|
||||
#include "openlr/score_candidate_points_getter.hpp"
|
||||
#include "openlr/score_paths_connector.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/way_point.hpp"
|
||||
|
||||
#include "routing/features_road_graph.hpp"
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "routing_common/car_model.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/polyline2d.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/math.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/timer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct alignas(kCacheLineSize) Stats
|
||||
{
|
||||
void Add(Stats const & rhs)
|
||||
{
|
||||
m_shortRoutes += rhs.m_shortRoutes;
|
||||
m_zeroCanditates += rhs.m_zeroCanditates;
|
||||
m_moreThanOneCandidate += rhs.m_moreThanOneCandidate;
|
||||
m_routesFailed += rhs.m_routesFailed;
|
||||
m_tightOffsets += rhs.m_tightOffsets;
|
||||
m_routesHandled += rhs.m_routesHandled;
|
||||
}
|
||||
|
||||
void Report() const
|
||||
{
|
||||
LOG(LINFO, ("Parsed segments:", m_routesHandled));
|
||||
LOG(LINFO, ("Routes failed:", m_routesFailed));
|
||||
LOG(LINFO, ("Tight offsets:", m_tightOffsets));
|
||||
LOG(LINFO, ("Short routes:", m_shortRoutes));
|
||||
LOG(LINFO, ("Ambiguous routes:", m_moreThanOneCandidate));
|
||||
LOG(LINFO, ("Path is not reconstructed:", m_zeroCanditates));
|
||||
}
|
||||
|
||||
uint32_t m_shortRoutes = 0;
|
||||
uint32_t m_zeroCanditates = 0;
|
||||
uint32_t m_moreThanOneCandidate = 0;
|
||||
uint32_t m_routesFailed = 0;
|
||||
uint32_t m_tightOffsets = 0;
|
||||
uint32_t m_routesHandled = 0;
|
||||
};
|
||||
|
||||
bool IsRealVertex(m2::PointD const & p, FeatureID const & fid, DataSource const & dataSource)
|
||||
{
|
||||
FeaturesLoaderGuard g(dataSource, fid.m_mwmId);
|
||||
auto const ft = g.GetOriginalFeatureByIndex(fid.m_index);
|
||||
bool matched = false;
|
||||
ft->ForEachPoint([&p, &matched](m2::PointD const & fp)
|
||||
{
|
||||
if (p == fp)
|
||||
matched = true;
|
||||
}, FeatureType::BEST_GEOMETRY);
|
||||
return matched;
|
||||
}
|
||||
|
||||
void ExpandFake(Graph::EdgeVector & path, Graph::EdgeVector::iterator edgeIt, DataSource const & dataSource, Graph & g)
|
||||
{
|
||||
if (!edgeIt->IsFake())
|
||||
return;
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
bool startIsFake = true;
|
||||
if (IsRealVertex(edgeIt->GetStartPoint(), edgeIt->GetFeatureId(), dataSource))
|
||||
{
|
||||
g.GetRegularOutgoingEdges(edgeIt->GetStartJunction(), edges);
|
||||
startIsFake = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(IsRealVertex(edgeIt->GetEndPoint(), edgeIt->GetFeatureId(), dataSource), ());
|
||||
g.GetRegularIngoingEdges(edgeIt->GetEndJunction(), edges);
|
||||
}
|
||||
|
||||
CHECK(!edges.empty(), ());
|
||||
|
||||
auto it = find_if(begin(edges), end(edges), [&edgeIt](Graph::Edge const & real)
|
||||
{
|
||||
if (real.GetFeatureId() == edgeIt->GetFeatureId() && real.GetSegId() == edgeIt->GetSegId())
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// For features which cross mwm border FeatureIds may not match. Check geometry.
|
||||
if (it == end(edges))
|
||||
{
|
||||
it = find_if(begin(edges), end(edges), [&edgeIt, &startIsFake](Graph::Edge const & real)
|
||||
{
|
||||
// Features from the same mwm should be already matched.
|
||||
if (real.GetFeatureId().m_mwmId == edgeIt->GetFeatureId().m_mwmId)
|
||||
return false;
|
||||
|
||||
auto const fakePoint = startIsFake ? edgeIt->GetStartPoint() : edgeIt->GetEndPoint();
|
||||
m2::ParametrizedSegment<m2::PointD> const realGeometry(real.GetStartPoint(), real.GetEndPoint());
|
||||
auto const projectedPoint = realGeometry.ClosestPointTo(fakePoint);
|
||||
|
||||
auto constexpr kCrossMwmMatchDistanceM = 1.0;
|
||||
if (mercator::DistanceOnEarth(fakePoint, projectedPoint) < kCrossMwmMatchDistanceM)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
CHECK(it != end(edges), ());
|
||||
|
||||
// If a fake edge is larger than a half of the corresponding real one, substitute
|
||||
// the fake one with real one. Drop the fake one otherwize.
|
||||
if (2 * EdgeLength(*edgeIt) >= EdgeLength(*it))
|
||||
*edgeIt = *it;
|
||||
else
|
||||
path.erase(edgeIt);
|
||||
}
|
||||
|
||||
void ExpandFakes(DataSource const & dataSource, Graph & g, Graph::EdgeVector & path)
|
||||
{
|
||||
ASSERT(!path.empty(), ());
|
||||
|
||||
ExpandFake(path, std::begin(path), dataSource, g);
|
||||
if (path.empty())
|
||||
return;
|
||||
ExpandFake(path, std::end(path) - 1, dataSource, g);
|
||||
}
|
||||
|
||||
// Returns an iterator pointing to the first edge that should not be cut off.
|
||||
// Offsets denote a distance in meters one should travel from the start/end of the path
|
||||
// to some point along that path and drop everything form the start to that point or from
|
||||
// that point to the end.
|
||||
template <typename InputIterator>
|
||||
InputIterator CutOffset(InputIterator start, InputIterator stop, double offset, bool keepEnd)
|
||||
{
|
||||
if (offset == 0.0)
|
||||
return start;
|
||||
|
||||
for (double distance = 0.0; start != stop; ++start)
|
||||
{
|
||||
auto const edgeLen = EdgeLength(*start);
|
||||
if (distance <= offset && offset < distance + edgeLen)
|
||||
{
|
||||
// Throw out this edge if (offset - distance) is greater than edgeLength / 2.
|
||||
if (!keepEnd && offset - distance >= edgeLen / 2.0)
|
||||
++start;
|
||||
break;
|
||||
}
|
||||
distance += edgeLen;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
void CopyWithoutOffsets(InputIterator start, InputIterator stop, OutputIterator out, uint32_t positiveOffset,
|
||||
uint32_t negativeOffset, bool keepEnds)
|
||||
{
|
||||
auto from = start;
|
||||
auto to = stop;
|
||||
|
||||
if (distance(start, stop) > 1)
|
||||
{
|
||||
from = CutOffset(start, stop, positiveOffset, keepEnds);
|
||||
// |to| points past the last edge we need to take.
|
||||
to = CutOffset(reverse_iterator<InputIterator>(stop), reverse_iterator<InputIterator>(start), negativeOffset,
|
||||
keepEnds)
|
||||
.base();
|
||||
}
|
||||
|
||||
if (!keepEnds)
|
||||
CHECK(from <= to, ("From iterator is less or equal than to."));
|
||||
|
||||
if (from >= to)
|
||||
return;
|
||||
|
||||
copy(from, to, out);
|
||||
}
|
||||
|
||||
class SegmentsDecoderV2
|
||||
{
|
||||
public:
|
||||
SegmentsDecoderV2(DataSource & dataSource, unique_ptr<CarModelFactory> cmf)
|
||||
: m_dataSource(dataSource)
|
||||
, m_graph(dataSource, std::move(cmf))
|
||||
, m_infoGetter(dataSource)
|
||||
{}
|
||||
|
||||
bool DecodeSegment(LinearSegment const & segment, DecodedPath & path, v2::Stats & stat)
|
||||
{
|
||||
double constexpr kPathLengthTolerance = 0.30;
|
||||
uint32_t constexpr kMaxJunctionCandidates = 10;
|
||||
uint32_t constexpr kMaxProjectionCandidates = 5;
|
||||
|
||||
m_graph.ResetFakes();
|
||||
|
||||
path.m_segmentId.Set(segment.m_segmentId);
|
||||
|
||||
auto const & points = segment.GetLRPs();
|
||||
CHECK_GREATER(points.size(), 1, ("A segment cannot consist of less than two points"));
|
||||
vector<vector<Graph::EdgeVector>> lineCandidates;
|
||||
lineCandidates.reserve(points.size());
|
||||
LOG(LDEBUG, ("Decoding segment:", segment.m_segmentId, "with", points.size(), "points"));
|
||||
|
||||
CandidatePointsGetter pointsGetter(kMaxJunctionCandidates, kMaxProjectionCandidates, m_dataSource, m_graph);
|
||||
CandidatePathsGetter pathsGetter(pointsGetter, m_graph, m_infoGetter, stat);
|
||||
|
||||
if (!pathsGetter.GetLineCandidatesForPoints(points, lineCandidates))
|
||||
return false;
|
||||
|
||||
vector<Graph::EdgeVector> resultPath;
|
||||
PathsConnector connector(kPathLengthTolerance, m_graph, m_infoGetter, stat);
|
||||
if (!connector.ConnectCandidates(points, lineCandidates, resultPath))
|
||||
return false;
|
||||
|
||||
Graph::EdgeVector route;
|
||||
for (auto const & part : resultPath)
|
||||
route.insert(end(route), begin(part), end(part));
|
||||
|
||||
double requiredRouteDistanceM = 0.0;
|
||||
// Sum app all distances between points. Last point's m_distanceToNextPoint
|
||||
// should be equal to zero, but let's skip it just in case.
|
||||
CHECK(!points.empty(), ());
|
||||
for (auto it = begin(points); it != prev(end(points)); ++it)
|
||||
requiredRouteDistanceM += it->m_distanceToNextPoint;
|
||||
|
||||
double actualRouteDistanceM = 0.0;
|
||||
for (auto const & e : route)
|
||||
actualRouteDistanceM += EdgeLength(e);
|
||||
|
||||
auto const scale = actualRouteDistanceM / requiredRouteDistanceM;
|
||||
LOG(LDEBUG, ("actualRouteDistance:", actualRouteDistanceM, "requiredRouteDistance:", requiredRouteDistanceM,
|
||||
"scale:", scale));
|
||||
|
||||
auto const positiveOffsetM = segment.m_locationReference.m_positiveOffsetMeters * scale;
|
||||
auto const negativeOffsetM = segment.m_locationReference.m_negativeOffsetMeters * scale;
|
||||
|
||||
if (positiveOffsetM + negativeOffsetM >= requiredRouteDistanceM)
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Wrong offsets for segment:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
ExpandFakes(m_dataSource, m_graph, route);
|
||||
ASSERT(none_of(begin(route), end(route), mem_fn(&Graph::Edge::IsFake)), (segment.m_segmentId));
|
||||
CopyWithoutOffsets(begin(route), end(route), back_inserter(path.m_path), positiveOffsetM, negativeOffsetM,
|
||||
false /* keep ends */);
|
||||
|
||||
if (path.m_path.empty())
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Path is empty after offsets cutting. segmentId:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
DataSource const & m_dataSource;
|
||||
Graph m_graph;
|
||||
RoadInfoGetter m_infoGetter;
|
||||
};
|
||||
|
||||
// The idea behind the third version of matching algorithm is to collect a lot of candidates (paths)
|
||||
// which correspond an openlr edges with some score. And on the final stage to decide which
|
||||
// candidate is better.
|
||||
class SegmentsDecoderV3
|
||||
{
|
||||
public:
|
||||
SegmentsDecoderV3(DataSource & dataSource, unique_ptr<CarModelFactory> carModelFactory)
|
||||
: m_dataSource(dataSource)
|
||||
, m_graph(dataSource, std::move(carModelFactory))
|
||||
, m_infoGetter(dataSource)
|
||||
{}
|
||||
|
||||
bool DecodeSegment(LinearSegment const & segment, DecodedPath & path, v2::Stats & stat)
|
||||
{
|
||||
LOG(LINFO, ("DecodeSegment(...) seg id:", segment.m_segmentId, ", point num:", segment.GetLRPs().size()));
|
||||
|
||||
uint32_t constexpr kMaxJunctionCandidates = 10;
|
||||
uint32_t constexpr kMaxProjectionCandidates = 5;
|
||||
|
||||
path.m_segmentId.Set(segment.m_segmentId);
|
||||
|
||||
auto const & points = segment.GetLRPs();
|
||||
CHECK_GREATER(points.size(), 1, ("A segment cannot consist of less than two points"));
|
||||
vector<ScorePathVec> lineCandidates;
|
||||
lineCandidates.reserve(points.size());
|
||||
LOG(LINFO, ("Decoding segment:", segment.m_segmentId, "with", points.size(), "points"));
|
||||
|
||||
ScoreCandidatePointsGetter pointsGetter(kMaxJunctionCandidates, kMaxProjectionCandidates, m_dataSource, m_graph);
|
||||
ScoreCandidatePathsGetter pathsGetter(pointsGetter, m_graph, m_infoGetter, stat);
|
||||
|
||||
if (!pathsGetter.GetLineCandidatesForPoints(points, segment.m_source, lineCandidates))
|
||||
return false;
|
||||
|
||||
vector<Graph::EdgeVector> resultPath;
|
||||
ScorePathsConnector connector(m_graph, m_infoGetter, stat);
|
||||
if (!connector.FindBestPath(points, lineCandidates, segment.m_source, resultPath))
|
||||
{
|
||||
LOG(LINFO, ("Connections not found:", segment.m_segmentId));
|
||||
auto const mercatorPoints = segment.GetMercatorPoints();
|
||||
for (auto const & mercatorPoint : mercatorPoints)
|
||||
LOG(LINFO, (mercator::ToLatLon(mercatorPoint)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Graph::EdgeVector route;
|
||||
for (auto const & part : resultPath)
|
||||
route.insert(route.end(), part.begin(), part.end());
|
||||
|
||||
double requiredRouteDistanceM = 0.0;
|
||||
// Sum up all distances between points. Last point's m_distanceToNextPoint
|
||||
// should be equal to zero, but let's skip it just in case.
|
||||
CHECK(!points.empty(), ());
|
||||
for (auto it = points.begin(); it != prev(points.end()); ++it)
|
||||
requiredRouteDistanceM += it->m_distanceToNextPoint;
|
||||
|
||||
double actualRouteDistanceM = 0.0;
|
||||
for (auto const & e : route)
|
||||
actualRouteDistanceM += EdgeLength(e);
|
||||
|
||||
auto const scale = actualRouteDistanceM / requiredRouteDistanceM;
|
||||
LOG(LINFO, ("actualRouteDistance:", actualRouteDistanceM, "requiredRouteDistance:", requiredRouteDistanceM,
|
||||
"scale:", scale));
|
||||
|
||||
if (segment.m_locationReference.m_positiveOffsetMeters + segment.m_locationReference.m_negativeOffsetMeters >=
|
||||
requiredRouteDistanceM)
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Wrong offsets for segment:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const positiveOffsetM = segment.m_locationReference.m_positiveOffsetMeters * scale;
|
||||
auto const negativeOffsetM = segment.m_locationReference.m_negativeOffsetMeters * scale;
|
||||
|
||||
CHECK(none_of(route.begin(), route.end(), mem_fn(&Graph::Edge::IsFake)), (segment.m_segmentId));
|
||||
CopyWithoutOffsets(route.begin(), route.end(), back_inserter(path.m_path), positiveOffsetM, negativeOffsetM,
|
||||
true /* keep ends */);
|
||||
|
||||
if (path.m_path.empty())
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Path is empty after offsets cutting. segmentId:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
DataSource const & m_dataSource;
|
||||
Graph m_graph;
|
||||
RoadInfoGetter m_infoGetter;
|
||||
};
|
||||
|
||||
size_t constexpr GetOptimalBatchSize()
|
||||
{
|
||||
// This code computes the most optimal (in the sense of cache lines
|
||||
// occupancy) batch size.
|
||||
size_t constexpr a = math::LCM(sizeof(LinearSegment), kCacheLineSize) / sizeof(LinearSegment);
|
||||
size_t constexpr b = math::LCM(sizeof(IRoadGraph::EdgeVector), kCacheLineSize) / sizeof(IRoadGraph::EdgeVector);
|
||||
return math::LCM(a, b);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// OpenLRDecoder::SegmentsFilter -------------------------------------------------------------
|
||||
OpenLRDecoder::SegmentsFilter::SegmentsFilter(string const & idsPath, bool const multipointsOnly)
|
||||
: m_idsSet(false)
|
||||
, m_multipointsOnly(multipointsOnly)
|
||||
{
|
||||
if (idsPath.empty())
|
||||
return;
|
||||
|
||||
ifstream ifs(idsPath);
|
||||
CHECK(ifs, ("Can't find", idsPath));
|
||||
m_ids.insert(istream_iterator<uint32_t>(ifs), istream_iterator<uint32_t>());
|
||||
|
||||
CHECK(!ifs, ("Garbage in", idsPath));
|
||||
m_idsSet = true;
|
||||
}
|
||||
|
||||
bool OpenLRDecoder::SegmentsFilter::Matches(LinearSegment const & segment) const
|
||||
{
|
||||
if (m_multipointsOnly && segment.m_locationReference.m_points.size() == 2)
|
||||
return false;
|
||||
if (m_idsSet && m_ids.count(segment.m_segmentId) == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// OpenLRDecoder -----------------------------------------------------------------------------
|
||||
OpenLRDecoder::OpenLRDecoder(vector<FrozenDataSource> & dataSources,
|
||||
CountryParentNameGetter const & countryParentNameGetter)
|
||||
: m_dataSources(dataSources)
|
||||
, m_countryParentNameGetter(countryParentNameGetter)
|
||||
{}
|
||||
|
||||
void OpenLRDecoder::DecodeV2(vector<LinearSegment> const & segments, uint32_t const numThreads,
|
||||
vector<DecodedPath> & paths)
|
||||
{
|
||||
Decode<SegmentsDecoderV2, v2::Stats>(segments, numThreads, paths);
|
||||
}
|
||||
|
||||
void OpenLRDecoder::DecodeV3(vector<LinearSegment> const & segments, uint32_t numThreads, vector<DecodedPath> & paths)
|
||||
{
|
||||
Decode<SegmentsDecoderV3, v2::Stats>(segments, numThreads, paths);
|
||||
}
|
||||
|
||||
template <typename Decoder, typename Stats>
|
||||
void OpenLRDecoder::Decode(vector<LinearSegment> const & segments, uint32_t const numThreads,
|
||||
vector<DecodedPath> & paths)
|
||||
{
|
||||
auto const worker = [&](size_t threadNum, DataSource & dataSource, Stats & stat)
|
||||
{
|
||||
size_t constexpr kBatchSize = GetOptimalBatchSize();
|
||||
size_t constexpr kProgressFrequency = 100;
|
||||
|
||||
size_t const numSegments = segments.size();
|
||||
|
||||
Decoder decoder(dataSource, make_unique<CarModelFactory>(m_countryParentNameGetter));
|
||||
base::Timer timer;
|
||||
for (size_t i = threadNum * kBatchSize; i < numSegments; i += numThreads * kBatchSize)
|
||||
{
|
||||
for (size_t j = i; j < numSegments && j < i + kBatchSize; ++j)
|
||||
{
|
||||
if (!decoder.DecodeSegment(segments[j], paths[j], stat))
|
||||
++stat.m_routesFailed;
|
||||
++stat.m_routesHandled;
|
||||
|
||||
if (stat.m_routesHandled % kProgressFrequency == 0 || i == segments.size() - 1)
|
||||
{
|
||||
LOG(LINFO, ("Thread", threadNum, "processed", stat.m_routesHandled, "failed:", stat.m_routesFailed));
|
||||
timer.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
base::Timer timer;
|
||||
vector<Stats> stats(numThreads);
|
||||
vector<thread> workers;
|
||||
for (size_t i = 1; i < numThreads; ++i)
|
||||
workers.emplace_back(worker, i, ref(m_dataSources[i]), ref(stats[i]));
|
||||
|
||||
worker(0 /* threadNum */, m_dataSources[0], stats[0]);
|
||||
for (auto & worker : workers)
|
||||
worker.join();
|
||||
|
||||
Stats allStats;
|
||||
for (auto const & s : stats)
|
||||
allStats.Add(s);
|
||||
|
||||
allStats.Report();
|
||||
LOG(LINFO, ("Matching tool:", timer.ElapsedSeconds(), "seconds."));
|
||||
}
|
||||
} // namespace openlr
|
||||
58
tools/openlr/openlr_decoder.hpp
Normal file
58
tools/openlr/openlr_decoder.hpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
struct LinearSegment;
|
||||
struct DecodedPath;
|
||||
|
||||
DECLARE_EXCEPTION(DecoderError, RootException);
|
||||
|
||||
class Graph;
|
||||
class RoadInfoGetter;
|
||||
|
||||
class OpenLRDecoder
|
||||
{
|
||||
public:
|
||||
using CountryParentNameGetter = std::function<std::string(std::string const &)>;
|
||||
|
||||
class SegmentsFilter
|
||||
{
|
||||
public:
|
||||
SegmentsFilter(std::string const & idsPath, bool const multipointsOnly);
|
||||
|
||||
bool Matches(LinearSegment const & segment) const;
|
||||
|
||||
private:
|
||||
std::unordered_set<uint32_t> m_ids;
|
||||
bool m_idsSet;
|
||||
bool const m_multipointsOnly;
|
||||
};
|
||||
|
||||
OpenLRDecoder(std::vector<FrozenDataSource> & dataSources, CountryParentNameGetter const & countryParentNameGetter);
|
||||
|
||||
// Maps partner segments to mwm paths. |segments| should be sorted by partner id.
|
||||
void DecodeV2(std::vector<LinearSegment> const & segments, uint32_t const numThreads,
|
||||
std::vector<DecodedPath> & paths);
|
||||
|
||||
void DecodeV3(std::vector<LinearSegment> const & segments, uint32_t numThreads, std::vector<DecodedPath> & paths);
|
||||
|
||||
private:
|
||||
template <typename Decoder, typename Stats>
|
||||
void Decode(std::vector<LinearSegment> const & segments, uint32_t const numThreads, std::vector<DecodedPath> & paths);
|
||||
|
||||
std::vector<FrozenDataSource> & m_dataSources;
|
||||
CountryParentNameGetter m_countryParentNameGetter;
|
||||
};
|
||||
} // namespace openlr
|
||||
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>
|
||||
41
tools/openlr/openlr_model.cpp
Normal file
41
tools/openlr/openlr_model.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// LinearSegment -----------------------------------------------------------------------------------
|
||||
vector<m2::PointD> LinearSegment::GetMercatorPoints() const
|
||||
{
|
||||
vector<m2::PointD> points;
|
||||
points.reserve(m_locationReference.m_points.size());
|
||||
for (auto const & point : m_locationReference.m_points)
|
||||
points.push_back(mercator::FromLatLon(point.m_latLon));
|
||||
return points;
|
||||
}
|
||||
|
||||
vector<LocationReferencePoint> const & LinearSegment::GetLRPs() const
|
||||
{
|
||||
return m_locationReference.m_points;
|
||||
}
|
||||
|
||||
vector<LocationReferencePoint> & LinearSegment::GetLRPs()
|
||||
{
|
||||
return m_locationReference.m_points;
|
||||
}
|
||||
|
||||
string DebugPrint(LinearSegmentSource source)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case LinearSegmentSource::NotValid: return "NotValid";
|
||||
case LinearSegmentSource::FromLocationReferenceTag: return "FromLocationReferenceTag";
|
||||
case LinearSegmentSource::FromCoordinatesTag: return "FromCoordinatesTag";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace openlr
|
||||
124
tools/openlr/openlr_model.hpp
Normal file
124
tools/openlr/openlr_model.hpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// The form of way (FOW) describes the physical road type of a line.
|
||||
enum class FormOfWay
|
||||
{
|
||||
// The physical road type is unknown.
|
||||
Undefined,
|
||||
// A road permitted for motorized vehicles only in combination with a prescribed minimum speed.
|
||||
// It has two or more physically separated carriageways and no single level-crossings.
|
||||
Motorway,
|
||||
// A road with physically separated carriageways regardless of the number of lanes.
|
||||
// If a road is also a motorway, it should be coded as such and not as a multiple carriageway.
|
||||
MultipleCarriageway,
|
||||
// All roads without separate carriageways are considered as roads with a single carriageway.
|
||||
SingleCarriageway,
|
||||
// A road which forms a ring on which traffic traveling in only one direction is allowed.
|
||||
Roundabout,
|
||||
// An open area (partly) enclosed by roads which is used for non-traffic purposes
|
||||
// and which is not a Roundabout.
|
||||
Trafficsquare,
|
||||
// A road especially designed to enter or leave a line.
|
||||
Sliproad,
|
||||
// The physical road type is known, but does not fit into one of the other categories.
|
||||
Other,
|
||||
// A path only allowed for bikes.
|
||||
BikePath,
|
||||
// A path only allowed for pedestrians.
|
||||
Footpath,
|
||||
NotAValue
|
||||
};
|
||||
|
||||
// The functional road class (FRC) of a line is a road classification based on the importance
|
||||
// of the road represented by the line.
|
||||
enum class FunctionalRoadClass
|
||||
{
|
||||
// Main road, highest importance.
|
||||
FRC0,
|
||||
// First class road.
|
||||
FRC1,
|
||||
|
||||
// Other road classes.
|
||||
|
||||
FRC2,
|
||||
FRC3,
|
||||
FRC4,
|
||||
FRC5,
|
||||
FRC6,
|
||||
FRC7,
|
||||
NotAValue
|
||||
};
|
||||
|
||||
// LinearSegment structure may be filled from olr:locationReference xml tag,
|
||||
// from coordinates tag or not valid.
|
||||
enum class LinearSegmentSource
|
||||
{
|
||||
NotValid,
|
||||
FromLocationReferenceTag,
|
||||
FromCoordinatesTag,
|
||||
};
|
||||
|
||||
struct LocationReferencePoint
|
||||
{
|
||||
// Coordinates of the point of interest.
|
||||
ms::LatLon m_latLon = ms::LatLon::Zero();
|
||||
// The bearing (BEAR) describes the angle between the true North and a line which is defined
|
||||
// by the coordinate of the LocationReferencePoint and a coordinate which is BEARDIST along
|
||||
// the line defined by the LocationReference-point attributes.
|
||||
// For more information see OpenLR-Whitepaper `Bearing' section.
|
||||
uint8_t m_bearing = 0;
|
||||
FunctionalRoadClass m_functionalRoadClass = FunctionalRoadClass::NotAValue;
|
||||
FormOfWay m_formOfWay = FormOfWay::NotAValue;
|
||||
|
||||
// The distance to next point field describes the distance to the next LocationReferencePoint
|
||||
// in the topological connection of the LocationReferencePoints. The distance is measured in meters
|
||||
// and is calculated along the location reference path between two subsequent LR-points.
|
||||
// The last LRP has the distance value 0.
|
||||
// Should not be used in the last point of a segment.
|
||||
uint32_t m_distanceToNextPoint = 0;
|
||||
// The lowest FunctionalRoadClass to the next point (LFRCNP) is the lowest FunctionalRoadClass
|
||||
// value which appears in the location reference path between two consecutive LR-points.
|
||||
// This information could be used to limit the number of road classes which need to be
|
||||
// scanned during the decoding.
|
||||
// Should not be used in the last point of a segment.
|
||||
FunctionalRoadClass m_lfrcnp = FunctionalRoadClass::NotAValue;
|
||||
bool m_againstDrivingDirection = false;
|
||||
};
|
||||
|
||||
struct LinearLocationReference
|
||||
{
|
||||
std::vector<LocationReferencePoint> m_points;
|
||||
uint32_t m_positiveOffsetMeters = 0;
|
||||
uint32_t m_negativeOffsetMeters = 0;
|
||||
};
|
||||
|
||||
struct LinearSegment
|
||||
{
|
||||
static auto constexpr kInvalidSegmentId = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
std::vector<m2::PointD> GetMercatorPoints() const;
|
||||
std::vector<LocationReferencePoint> const & GetLRPs() const;
|
||||
std::vector<LocationReferencePoint> & GetLRPs();
|
||||
|
||||
LinearSegmentSource m_source = LinearSegmentSource::NotValid;
|
||||
// TODO(mgsergio): Think of using openlr::PartnerSegmentId
|
||||
uint32_t m_segmentId = kInvalidSegmentId;
|
||||
// TODO(mgsergio): Make sure that one segment cannot contain
|
||||
// more than one location reference.
|
||||
LinearLocationReference m_locationReference;
|
||||
uint32_t m_segmentLengthMeters = 0;
|
||||
// uint32_t m_segmentRefSpeed; Always null in partners data. (No docs found).
|
||||
};
|
||||
|
||||
std::string DebugPrint(LinearSegmentSource source);
|
||||
} // namespace openlr
|
||||
348
tools/openlr/openlr_model_xml.cpp
Normal file
348
tools/openlr/openlr_model_xml.cpp
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
#include "openlr/openlr_model_xml.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace // Primitive utilities to handle simple OpenLR-like XML data.
|
||||
{
|
||||
template <typename Value>
|
||||
bool IntegerFromXML(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
if (!node)
|
||||
return false;
|
||||
value = static_cast<Value>(is_signed<Value>::value ? node.text().as_int() : node.text().as_uint());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<double> DoubleFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
return node ? node.text().as_double() : std::optional<double>();
|
||||
}
|
||||
|
||||
bool GetLatLon(pugi::xml_node const & node, int32_t & lat, int32_t & lon)
|
||||
{
|
||||
if (!IntegerFromXML(node.child("olr:latitude"), lat) || !IntegerFromXML(node.child("olr:longitude"), lon))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This helper is used to parse records like this:
|
||||
// <olr:lfrcnp olr:table="olr001_FunctionalRoadClass" olr:code="4"/>
|
||||
template <typename Value>
|
||||
bool TableValueFromXML(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
if (!node)
|
||||
return false;
|
||||
value = static_cast<Value>(is_signed<Value>::value ? node.attribute("olr:code").as_int()
|
||||
: node.attribute("olr:code").as_uint());
|
||||
return true;
|
||||
}
|
||||
|
||||
pugi::xml_node GetLinearLocationReference(pugi::xml_node const & node)
|
||||
{
|
||||
// There should be only one linear location reference child of a location reference.
|
||||
return node.select_node(".//olr:locationReference/olr:optionLinearLocationReference").node();
|
||||
}
|
||||
|
||||
pugi::xml_node GetCoordinates(pugi::xml_node const & node)
|
||||
{
|
||||
return node.select_node(".//coordinates").node();
|
||||
}
|
||||
|
||||
bool IsLocationReferenceTag(pugi::xml_node const & node)
|
||||
{
|
||||
return node.select_node(".//olr:locationReference").node();
|
||||
}
|
||||
|
||||
bool IsCoordinatesTag(pugi::xml_node const & node)
|
||||
{
|
||||
return node.select_node("coordinates").node();
|
||||
}
|
||||
|
||||
// This helper is used do deal with xml nodes of the form
|
||||
// <node>
|
||||
// <value>integer<value>
|
||||
// </node>
|
||||
template <typename Value>
|
||||
bool ValueFromXML(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
auto const valueNode = node.child("olr:value");
|
||||
return IntegerFromXML(valueNode, value);
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
bool ParseValueIfExists(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
if (!node)
|
||||
return true;
|
||||
return ValueFromXML(node, value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace // OpenLR tools and abstractions
|
||||
{
|
||||
bool FirstCoordinateFromXML(pugi::xml_node const & node, ms::LatLon & latLon)
|
||||
{
|
||||
int32_t lat, lon;
|
||||
if (!GetLatLon(node.child("olr:coordinate"), lat, lon))
|
||||
return false;
|
||||
|
||||
latLon.m_lat = ((lat - math::Sign(lat) * 0.5) * 360) / (1 << 24);
|
||||
latLon.m_lon = ((lon - math::Sign(lon) * 0.5) * 360) / (1 << 24);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<ms::LatLon> LatLonFormXML(pugi::xml_node const & node)
|
||||
{
|
||||
auto const lat = DoubleFromXML(node.child("latitude"));
|
||||
auto const lon = DoubleFromXML(node.child("longitude"));
|
||||
|
||||
return lat && lon ? ms::LatLon(*lat, *lon) : ms::LatLon::Zero();
|
||||
}
|
||||
|
||||
bool CoordinateFromXML(pugi::xml_node const & node, ms::LatLon const & prevCoord, ms::LatLon & latLon)
|
||||
{
|
||||
int32_t lat, lon;
|
||||
if (!GetLatLon(node.child("olr:coordinate"), lat, lon))
|
||||
return false;
|
||||
|
||||
// This constant is provided by the given OpenLR variant
|
||||
// with no special meaning and is used as a factor to store doubles as ints.
|
||||
auto const kOpenlrDeltaFactor = 100000;
|
||||
|
||||
latLon.m_lat = prevCoord.m_lat + static_cast<double>(lat) / kOpenlrDeltaFactor;
|
||||
latLon.m_lon = prevCoord.m_lon + static_cast<double>(lon) / kOpenlrDeltaFactor;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinePropertiesFromXML(pugi::xml_node const & linePropNode, openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
if (!linePropNode)
|
||||
{
|
||||
LOG(LERROR, ("linePropNode is NULL"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TableValueFromXML(linePropNode.child("olr:frc"), locPoint.m_functionalRoadClass))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse functional road class"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TableValueFromXML(linePropNode.child("olr:fow"), locPoint.m_formOfWay))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse form of a way"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValueFromXML(linePropNode.child("olr:bearing"), locPoint.m_bearing))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse bearing"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PathPropertiesFromXML(pugi::xml_node const & locPointNode, openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
// Last point does not contain path properties.
|
||||
if (strcmp(locPointNode.name(), "olr:last") == 0)
|
||||
return true;
|
||||
|
||||
auto const propNode = locPointNode.child("olr:pathProperties");
|
||||
if (!propNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't parse path properties"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValueFromXML(propNode.child("olr:dnp"), locPoint.m_distanceToNextPoint))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse dnp"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TableValueFromXML(propNode.child("olr:lfrcnp"), locPoint.m_lfrcnp))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse lfrcnp"));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const directionNode = propNode.child("olr:againstDrivingDirection");
|
||||
if (!directionNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't parse driving direction"));
|
||||
return false;
|
||||
}
|
||||
locPoint.m_againstDrivingDirection = directionNode.text().as_bool();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocationReferencePointFromXML(pugi::xml_node const & locPointNode, openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
if (!FirstCoordinateFromXML(locPointNode, locPoint.m_latLon))
|
||||
{
|
||||
LOG(LERROR, ("Can't get first coordinate"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return LinePropertiesFromXML(locPointNode.child("olr:lineProperties"), locPoint) &&
|
||||
PathPropertiesFromXML(locPointNode, locPoint);
|
||||
}
|
||||
|
||||
bool LocationReferencePointFromXML(pugi::xml_node const & locPointNode, ms::LatLon const & firstPoint,
|
||||
openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
if (!CoordinateFromXML(locPointNode, firstPoint, locPoint.m_latLon))
|
||||
{
|
||||
LOG(LERROR, ("Can't get last coordinate"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return LinePropertiesFromXML(locPointNode.child("olr:lineProperties"), locPoint) &&
|
||||
PathPropertiesFromXML(locPointNode, locPoint);
|
||||
}
|
||||
|
||||
bool LinearLocationReferenceFromXML(pugi::xml_node const & locRefNode, openlr::LinearLocationReference & locRef)
|
||||
{
|
||||
if (!locRefNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't get location reference."));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
openlr::LocationReferencePoint point;
|
||||
if (!LocationReferencePointFromXML(locRefNode.child("olr:first"), point))
|
||||
return false;
|
||||
locRef.m_points.push_back(point);
|
||||
}
|
||||
|
||||
for (auto const pointNode : locRefNode.select_nodes("olr:intermediates"))
|
||||
{
|
||||
openlr::LocationReferencePoint point;
|
||||
if (!LocationReferencePointFromXML(pointNode.node(), locRef.m_points.back().m_latLon, point))
|
||||
return false;
|
||||
locRef.m_points.push_back(point);
|
||||
}
|
||||
|
||||
{
|
||||
openlr::LocationReferencePoint point;
|
||||
if (!LocationReferencePointFromXML(locRefNode.child("olr:last"), locRef.m_points.back().m_latLon, point))
|
||||
return false;
|
||||
locRef.m_points.push_back(point);
|
||||
}
|
||||
|
||||
if (!ParseValueIfExists(locRefNode.child("olr:positiveOffset"), locRef.m_positiveOffsetMeters))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse positive offset"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseValueIfExists(locRefNode.child("olr:negativeOffset"), locRef.m_negativeOffsetMeters))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse negative offset"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoordinatesFromXML(pugi::xml_node const & coordsNode, openlr::LinearLocationReference & locRef)
|
||||
{
|
||||
if (!coordsNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't get <coordinates>."));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const latLonStart = LatLonFormXML(coordsNode.child("start"));
|
||||
auto const latLonEnd = LatLonFormXML(coordsNode.child("end"));
|
||||
if (!latLonStart || !latLonEnd)
|
||||
{
|
||||
LOG(LERROR, ("Can't get <start> or <end> of <coordinates>."));
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(LINFO, ("from:", *latLonStart, "to:", *latLonEnd));
|
||||
locRef.m_points.resize(2);
|
||||
locRef.m_points[0].m_latLon = *latLonStart;
|
||||
locRef.m_points[1].m_latLon = *latLonEnd;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
bool ParseOpenlr(pugi::xml_document const & document, vector<LinearSegment> & segments)
|
||||
{
|
||||
for (auto const segmentXpathNode : document.select_nodes("//reportSegments"))
|
||||
{
|
||||
LinearSegment segment;
|
||||
auto const & node = segmentXpathNode.node();
|
||||
if (!IsLocationReferenceTag(node) && !IsCoordinatesTag(node))
|
||||
{
|
||||
LOG(LWARNING, ("A segment with a strange tag. It is not <coordinates>"
|
||||
" or <optionLinearLocationReference>, skipping..."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SegmentFromXML(node, segment))
|
||||
return false;
|
||||
|
||||
segments.push_back(segment);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SegmentFromXML(pugi::xml_node const & segmentNode, LinearSegment & segment)
|
||||
{
|
||||
CHECK(segmentNode, ());
|
||||
if (!IntegerFromXML(segmentNode.child("ReportSegmentID"), segment.m_segmentId))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse segment id"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IntegerFromXML(segmentNode.child("segmentLength"), segment.m_segmentLengthMeters))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse segment length"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsLocationReferenceTag(segmentNode))
|
||||
{
|
||||
auto const locRefNode = GetLinearLocationReference(segmentNode);
|
||||
auto const result = LinearLocationReferenceFromXML(locRefNode, segment.m_locationReference);
|
||||
if (result)
|
||||
segment.m_source = LinearSegmentSource::FromLocationReferenceTag;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CHECK(IsCoordinatesTag(segmentNode), ());
|
||||
auto const coordsNode = GetCoordinates(segmentNode);
|
||||
if (!CoordinatesFromXML(coordsNode, segment.m_locationReference))
|
||||
return false;
|
||||
|
||||
CHECK_EQUAL(segment.m_locationReference.m_points.size(), 2, ());
|
||||
segment.m_locationReference.m_points[0].m_distanceToNextPoint = segment.m_segmentLengthMeters;
|
||||
segment.m_source = LinearSegmentSource::FromCoordinatesTag;
|
||||
return true;
|
||||
}
|
||||
} // namespace openlr
|
||||
18
tools/openlr/openlr_model_xml.hpp
Normal file
18
tools/openlr/openlr_model_xml.hpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace pugi
|
||||
{
|
||||
class xml_document;
|
||||
class xml_node;
|
||||
} // namespace pugi
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
struct LinearSegment;
|
||||
|
||||
bool SegmentFromXML(pugi::xml_node const & segmentNode, LinearSegment & segment);
|
||||
|
||||
bool ParseOpenlr(pugi::xml_document const & document, std::vector<LinearSegment> & segments);
|
||||
} // namespace openlr
|
||||
12
tools/openlr/openlr_stat/CMakeLists.txt
Normal file
12
tools/openlr/openlr_stat/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
project(openlr_stat)
|
||||
|
||||
set(SRC openlr_stat.cpp)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
openlr
|
||||
storage
|
||||
platform
|
||||
gflags::gflags
|
||||
)
|
||||
282
tools/openlr/openlr_stat/openlr_stat.cpp
Normal file
282
tools/openlr/openlr_stat/openlr_stat.cpp
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
#include "openlr/openlr_decoder.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "storage/country_parent_getter.hpp"
|
||||
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/openlr_model_xml.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <clocale>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
DEFINE_string(input, "", "Path to OpenLR file.");
|
||||
DEFINE_string(spark_output, "", "Path to output file in spark-oriented format");
|
||||
DEFINE_string(assessment_output, "", "Path to output file in assessment-tool oriented format");
|
||||
|
||||
DEFINE_string(non_matched_ids, "non-matched-ids.txt", "Path to a file ids of non-matched segments will be saved to");
|
||||
DEFINE_string(mwms_path, "", "Path to a folder with mwms.");
|
||||
DEFINE_string(resources_path, "", "Path to a folder with resources.");
|
||||
DEFINE_int32(limit, -1, "Max number of segments to handle. -1 for all.");
|
||||
DEFINE_bool(multipoints_only, false, "Only segments with multiple points to handle.");
|
||||
DEFINE_int32(num_threads, 1, "Number of threads.");
|
||||
DEFINE_string(ids_path, "", "Path to a file with segment ids to process.");
|
||||
DEFINE_string(countries_filename, "",
|
||||
"Name of countries file which describes mwm tree. Used to get country specific "
|
||||
"routing restrictions.");
|
||||
DEFINE_int32(algo_version, 0, "Use new decoding algorithm");
|
||||
|
||||
using namespace openlr;
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t const kMinNumThreads = 1;
|
||||
int32_t const kMaxNumThreads = 128;
|
||||
int32_t const kHandleAllSegments = -1;
|
||||
|
||||
void LoadDataSources(std::string const & pathToMWMFolder, std::vector<FrozenDataSource> & dataSources)
|
||||
{
|
||||
CHECK(Platform::IsDirectory(pathToMWMFolder), (pathToMWMFolder, "must be a directory."));
|
||||
|
||||
Platform::FilesList files;
|
||||
Platform::GetFilesByRegExp(pathToMWMFolder, boost::regex(".*\\") + DATA_FILE_EXTENSION, files);
|
||||
|
||||
CHECK(!files.empty(), (pathToMWMFolder, "Contains no .mwm files."));
|
||||
|
||||
size_t const numDataSources = dataSources.size();
|
||||
std::vector<uint64_t> numCountries(numDataSources);
|
||||
|
||||
for (auto const & fileName : files)
|
||||
{
|
||||
auto const fullFileName = base::JoinPath(pathToMWMFolder, fileName);
|
||||
ModelReaderPtr reader(GetPlatform().GetReader(fullFileName, "f"));
|
||||
platform::LocalCountryFile localFile(pathToMWMFolder, platform::CountryFile(base::FilenameWithoutExt(fileName)),
|
||||
version::ReadVersionDate(reader));
|
||||
|
||||
LOG(LINFO, ("Found mwm:", fullFileName));
|
||||
try
|
||||
{
|
||||
localFile.SyncWithDisk();
|
||||
for (size_t i = 0; i < numDataSources; ++i)
|
||||
{
|
||||
auto const result = dataSources[i].RegisterMap(localFile);
|
||||
CHECK_EQUAL(result.second, MwmSet::RegResult::Success, ("Can't register mwm:", localFile));
|
||||
|
||||
auto const & info = result.first.GetInfo();
|
||||
if (info && info->GetType() == MwmInfo::COUNTRY)
|
||||
++numCountries[i];
|
||||
}
|
||||
}
|
||||
catch (RootException const & ex)
|
||||
{
|
||||
CHECK(false, (ex.Msg(), "Bad mwm file:", localFile));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numDataSources; ++i)
|
||||
if (numCountries[i] == 0)
|
||||
LOG(LWARNING, ("No countries for thread", i));
|
||||
}
|
||||
|
||||
bool ValidateLimit(char const * flagname, int32_t value)
|
||||
{
|
||||
if (value < -1)
|
||||
{
|
||||
LOG(LINFO, ("Valid value for --", std::string(flagname), ":", value, "must be greater or equal to -1."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateNumThreads(char const * flagname, int32_t value)
|
||||
{
|
||||
if (value < kMinNumThreads || value > kMaxNumThreads)
|
||||
{
|
||||
LOG(LINFO, ("Valid value for --", std::string(flagname), ":", value, "must be between", kMinNumThreads, "and",
|
||||
kMaxNumThreads));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateMwmPath(char const * flagname, std::string const & value)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
LOG(LINFO, ("--", std::string(flagname), "should be specified."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateVersion(char const * flagname, int32_t value)
|
||||
{
|
||||
if (value == 0)
|
||||
{
|
||||
LOG(LINFO, ("--", std::string(flagname), "should be specified."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value != 1 && value != 2 && value != 3)
|
||||
{
|
||||
LOG(LINFO, ("--", std::string(flagname), "should be one of 1, 2 or 3."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool const g_limitDummy = gflags::RegisterFlagValidator(&FLAGS_limit, &ValidateLimit);
|
||||
bool const g_numThreadsDummy = gflags::RegisterFlagValidator(&FLAGS_num_threads, &ValidateNumThreads);
|
||||
bool const g_mwmsPathDummy = gflags::RegisterFlagValidator(&FLAGS_mwms_path, &ValidateMwmPath);
|
||||
bool const g_algoVersion = gflags::RegisterFlagValidator(&FLAGS_algo_version, &ValidateVersion);
|
||||
|
||||
void SaveNonMatchedIds(std::string const & filename, std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
if (filename.empty())
|
||||
return;
|
||||
|
||||
std::ofstream ofs(filename);
|
||||
for (auto const & p : paths)
|
||||
if (p.m_path.empty())
|
||||
ofs << p.m_segmentId << std::endl;
|
||||
}
|
||||
|
||||
std::vector<LinearSegment> LoadSegments(pugi::xml_document & document)
|
||||
{
|
||||
std::vector<LinearSegment> segments;
|
||||
if (!ParseOpenlr(document, segments))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse data."));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
OpenLRDecoder::SegmentsFilter filter(FLAGS_ids_path, FLAGS_multipoints_only);
|
||||
if (FLAGS_limit != kHandleAllSegments && FLAGS_limit >= 0 && static_cast<size_t>(FLAGS_limit) < segments.size())
|
||||
segments.resize(FLAGS_limit);
|
||||
|
||||
base::EraseIf(segments, [&filter](LinearSegment const & segment) { return !filter.Matches(segment); });
|
||||
|
||||
std::sort(segments.begin(), segments.end(), base::LessBy(&LinearSegment::m_segmentId));
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
void WriteAssessmentFile(std::string const fileName, pugi::xml_document const & doc,
|
||||
std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
std::unordered_map<uint32_t, pugi::xml_node> xmlSegments;
|
||||
for (auto const & xpathNode : doc.select_nodes("//reportSegments"))
|
||||
{
|
||||
auto const xmlSegment = xpathNode.node();
|
||||
xmlSegments[xmlSegment.child("ReportSegmentID").text().as_uint()] = xmlSegment;
|
||||
}
|
||||
|
||||
pugi::xml_document result;
|
||||
auto segments = result.append_child("Segments");
|
||||
auto const dict = doc.select_node(".//Dictionary").node();
|
||||
char const xmlns[] = "xmlns";
|
||||
|
||||
// Copy namespaces from <Dictionary> to <Segments>
|
||||
for (auto const & attr : dict.attributes())
|
||||
{
|
||||
if (strncmp(xmlns, attr.name(), sizeof(xmlns) - 1) != 0)
|
||||
continue;
|
||||
|
||||
// Don't copy default namespace.
|
||||
if (strncmp(xmlns, attr.name(), sizeof(xmlns)) == 0)
|
||||
continue;
|
||||
|
||||
segments.append_copy(attr);
|
||||
}
|
||||
|
||||
for (auto const & p : paths)
|
||||
{
|
||||
auto segment = segments.append_child("Segment");
|
||||
{
|
||||
auto const xmlSegment = xmlSegments[p.m_segmentId.Get()];
|
||||
segment.append_copy(xmlSegment);
|
||||
}
|
||||
if (!p.m_path.empty())
|
||||
{
|
||||
auto node = segment.append_child("Route");
|
||||
openlr::PathToXML(p.m_path, node);
|
||||
}
|
||||
}
|
||||
|
||||
result.save_file(fileName.data(), " " /* indent */);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
gflags::SetUsageMessage("OpenLR stats tool.");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (!FLAGS_resources_path.empty())
|
||||
GetPlatform().SetResourceDir(FLAGS_resources_path);
|
||||
|
||||
classificator::Load();
|
||||
|
||||
auto const numThreads = static_cast<uint32_t>(FLAGS_num_threads);
|
||||
|
||||
std::vector<FrozenDataSource> dataSources(numThreads);
|
||||
|
||||
LoadDataSources(FLAGS_mwms_path, dataSources);
|
||||
|
||||
OpenLRDecoder decoder(dataSources,
|
||||
storage::CountryParentGetter(FLAGS_countries_filename, GetPlatform().ResourcesDir()));
|
||||
|
||||
pugi::xml_document document;
|
||||
auto const load_result = document.load_file(FLAGS_input.data());
|
||||
if (!load_result)
|
||||
{
|
||||
LOG(LERROR, ("Can't load file", FLAGS_input, ":", load_result.description()));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
std::setlocale(LC_ALL, "en_US.UTF-8");
|
||||
auto const segments = LoadSegments(document);
|
||||
|
||||
std::vector<DecodedPath> paths(segments.size());
|
||||
switch (FLAGS_algo_version)
|
||||
{
|
||||
case 2: decoder.DecodeV2(segments, numThreads, paths); break;
|
||||
case 3: decoder.DecodeV3(segments, numThreads, paths); break;
|
||||
default: CHECK(false, ("Wrong algorithm version."));
|
||||
}
|
||||
|
||||
SaveNonMatchedIds(FLAGS_non_matched_ids, paths);
|
||||
if (!FLAGS_assessment_output.empty())
|
||||
WriteAssessmentFile(FLAGS_assessment_output, document, paths);
|
||||
if (!FLAGS_spark_output.empty())
|
||||
WriteAsMappingForSpark(FLAGS_spark_output, paths);
|
||||
|
||||
return 0;
|
||||
}
|
||||
12
tools/openlr/openlr_tests/CMakeLists.txt
Normal file
12
tools/openlr/openlr_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
project(openlr_tests)
|
||||
|
||||
set(SRC decoded_path_test.cpp)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
generator_tests_support
|
||||
platform_tests_support
|
||||
openlr
|
||||
indexer
|
||||
)
|
||||
208
tools/openlr/openlr_tests/decoded_path_test.cpp
Normal file
208
tools/openlr/openlr_tests/decoded_path_test.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
|
||||
#include "generator/generator_tests_support/test_feature.hpp"
|
||||
#include "generator/generator_tests_support/test_mwm_builder.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "platform/platform_tests_support/scoped_dir.hpp"
|
||||
#include "platform/platform_tests_support/scoped_file.hpp"
|
||||
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
using namespace generator::tests_support;
|
||||
using namespace platform::tests_support;
|
||||
using namespace platform;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
string const kTestDir = "openlr_decoded_path_test";
|
||||
string const kTestMwm = "test";
|
||||
|
||||
double RoughUpToFive(double d)
|
||||
{
|
||||
stringstream s;
|
||||
s << setprecision(5) << fixed;
|
||||
s << d;
|
||||
s >> d;
|
||||
return d;
|
||||
}
|
||||
|
||||
m2::PointD RoughPoint(m2::PointD const & p)
|
||||
{
|
||||
return {RoughUpToFive(p.x), RoughUpToFive(p.y)};
|
||||
}
|
||||
|
||||
geometry::PointWithAltitude RoughJunction(geometry::PointWithAltitude const & j)
|
||||
{
|
||||
return geometry::PointWithAltitude(RoughPoint(j.GetPoint()), j.GetAltitude());
|
||||
}
|
||||
|
||||
routing::Edge RoughEdgeJunctions(routing::Edge const & e)
|
||||
{
|
||||
return routing::Edge::MakeReal(e.GetFeatureId(), e.IsForward(), e.GetSegId(), RoughJunction(e.GetStartJunction()),
|
||||
RoughJunction(e.GetEndJunction()));
|
||||
}
|
||||
|
||||
void RoughJunctionsInPath(openlr::Path & p)
|
||||
{
|
||||
for (auto & e : p)
|
||||
e = RoughEdgeJunctions(e);
|
||||
}
|
||||
|
||||
void TestSerializeDeserialize(openlr::Path const & path, DataSource const & dataSource)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
openlr::PathToXML(path, doc);
|
||||
|
||||
openlr::Path restoredPath;
|
||||
openlr::PathFromXML(doc, dataSource, restoredPath);
|
||||
|
||||
// Fix mercator::From/ToLatLon floating point error
|
||||
// for we could use TEST_EQUAL on result.
|
||||
RoughJunctionsInPath(restoredPath);
|
||||
|
||||
TEST_EQUAL(path, restoredPath, ());
|
||||
}
|
||||
|
||||
openlr::Path MakePath(FeatureType const & road, bool const forward)
|
||||
{
|
||||
CHECK_EQUAL(road.GetGeomType(), feature::GeomType::Line, ());
|
||||
CHECK_GREATER(road.GetPointsCount(), 0, ());
|
||||
openlr::Path path;
|
||||
|
||||
size_t const maxPointIndex = road.GetPointsCount() - 1;
|
||||
for (size_t i = 0; i < maxPointIndex; ++i)
|
||||
{
|
||||
size_t current{};
|
||||
size_t next{};
|
||||
if (forward)
|
||||
{
|
||||
current = i;
|
||||
next = i + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = maxPointIndex - i;
|
||||
next = current - 1;
|
||||
}
|
||||
|
||||
auto const from = road.GetPoint(current);
|
||||
auto const to = road.GetPoint(next);
|
||||
path.push_back(routing::Edge::MakeReal(
|
||||
road.GetID(), forward, base::checked_cast<uint32_t>(current - static_cast<size_t>(!forward)) /* segId */,
|
||||
geometry::PointWithAltitude(from, 0 /* altitude */), geometry::PointWithAltitude(to, 0 /* altitude */)));
|
||||
}
|
||||
|
||||
RoughJunctionsInPath(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void WithRoad(vector<m2::PointD> const & points, Func && fn)
|
||||
{
|
||||
classificator::Load();
|
||||
auto & platform = GetPlatform();
|
||||
|
||||
auto const mwmPath = base::JoinPath(platform.WritableDir(), kTestDir);
|
||||
|
||||
LocalCountryFile country(mwmPath, CountryFile(kTestMwm), 0 /* version */);
|
||||
ScopedDir testScopedDir(kTestDir);
|
||||
ScopedFile testScopedMwm(base::JoinPath(kTestDir, kTestMwm + DATA_FILE_EXTENSION), ScopedFile::Mode::Create);
|
||||
|
||||
{
|
||||
TestMwmBuilder builder(country, feature::DataHeader::MapType::Country);
|
||||
builder.Add(TestRoad(points, "Interstate 60", "en"));
|
||||
}
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
auto const regResult = dataSource.RegisterMap(country);
|
||||
TEST_EQUAL(regResult.second, MwmSet::RegResult::Success, ());
|
||||
|
||||
MwmSet::MwmHandle mwmHandle = dataSource.GetMwmHandleById(regResult.first);
|
||||
TEST(mwmHandle.IsAlive(), ());
|
||||
|
||||
FeaturesLoaderGuard const guard(dataSource, regResult.first);
|
||||
auto road = guard.GetFeatureByIndex(0);
|
||||
TEST(road, ());
|
||||
|
||||
road->ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
fn(dataSource, *road);
|
||||
}
|
||||
|
||||
UNIT_TEST(MakePath_Test)
|
||||
{
|
||||
std::vector<m2::PointD> const points{{0, 0}, {0, 1}, {1, 0}, {1, 1}};
|
||||
WithRoad(points, [&points](DataSource const & dataSource, FeatureType & road)
|
||||
{
|
||||
auto const & id = road.GetID();
|
||||
{
|
||||
openlr::Path const expected{routing::Edge::MakeReal(id, true /* forward */, 0 /* segId*/, points[0], points[1]),
|
||||
routing::Edge::MakeReal(id, true /* forward */, 1 /* segId*/, points[1], points[2]),
|
||||
routing::Edge::MakeReal(id, true /* forward */, 2 /* segId*/, points[2], points[3])};
|
||||
auto const path = MakePath(road, true /* forward */);
|
||||
TEST_EQUAL(path, expected, ());
|
||||
}
|
||||
{
|
||||
openlr::Path const expected{routing::Edge::MakeReal(id, false /* forward */, 2 /* segId*/, points[3], points[2]),
|
||||
routing::Edge::MakeReal(id, false /* forward */, 1 /* segId*/, points[2], points[1]),
|
||||
routing::Edge::MakeReal(id, false /* forward */, 0 /* segId*/, points[1], points[0])};
|
||||
{
|
||||
auto const path = MakePath(road, false /* forward */);
|
||||
TEST_EQUAL(path, expected, ());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UNIT_TEST(PathSerializeDeserialize_Test)
|
||||
{
|
||||
WithRoad({{0, 0}, {0, 1}, {1, 0}, {1, 1}}, [](DataSource const & dataSource, FeatureType & road)
|
||||
{
|
||||
{
|
||||
auto const path = MakePath(road, true /* forward */);
|
||||
TestSerializeDeserialize(path, dataSource);
|
||||
}
|
||||
{
|
||||
auto const path = MakePath(road, false /* forward */);
|
||||
TestSerializeDeserialize(path, dataSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UNIT_TEST(GetPoints_Test)
|
||||
{
|
||||
vector<m2::PointD> const points{{0, 0}, {0, 1}, {1, 0}, {1, 1}};
|
||||
WithRoad(points, [&points](DataSource const &, FeatureType & road)
|
||||
{
|
||||
{
|
||||
auto path = MakePath(road, true /* forward */);
|
||||
// RoughJunctionsInPath(path);
|
||||
TEST_EQUAL(GetPoints(path), points, ());
|
||||
}
|
||||
{
|
||||
auto path = MakePath(road, false /* forward */);
|
||||
// RoughJunctionsInPath(path);
|
||||
auto reversed = points;
|
||||
reverse(begin(reversed), end(reversed));
|
||||
TEST_EQUAL(GetPoints(path), reversed, ());
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
219
tools/openlr/paths_connector.cpp
Normal file
219
tools/openlr/paths_connector.cpp
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#include "openlr/paths_connector.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/stl_iterator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool ValidatePath(Graph::EdgeVector const & path, double const distanceToNextPoint, double const pathLengthTolerance)
|
||||
{
|
||||
double pathLen = 0.0;
|
||||
for (auto const & e : path)
|
||||
pathLen += EdgeLength(e);
|
||||
|
||||
double pathDiffPercent =
|
||||
AbsDifference(static_cast<double>(distanceToNextPoint), pathLen) / static_cast<double>(distanceToNextPoint);
|
||||
|
||||
LOG(LDEBUG, ("Validating path:", LogAs2GisPath(path)));
|
||||
|
||||
if (pathDiffPercent > pathLengthTolerance)
|
||||
{
|
||||
LOG(LDEBUG, ("Shortest path doest not meet required length constraints, error:", pathDiffPercent));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PathsConnector::PathsConnector(double pathLengthTolerance, Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat)
|
||||
: m_pathLengthTolerance(pathLengthTolerance)
|
||||
, m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stat(stat)
|
||||
{}
|
||||
|
||||
bool PathsConnector::ConnectCandidates(vector<LocationReferencePoint> const & points,
|
||||
vector<vector<Graph::EdgeVector>> const & lineCandidates,
|
||||
vector<Graph::EdgeVector> & resultPath)
|
||||
{
|
||||
ASSERT(!points.empty(), ());
|
||||
|
||||
resultPath.resize(points.size() - 1);
|
||||
|
||||
// TODO(mgsergio): Discard last point on failure.
|
||||
// TODO(mgsergio): Do not iterate more than kMaxRetries times.
|
||||
// TODO(mgserigio): Make kMaxRetries depend on points number in the segment.
|
||||
for (size_t i = 1; i < points.size(); ++i)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
auto const & point = points[i - 1];
|
||||
auto const distanceToNextPoint = static_cast<double>(point.m_distanceToNextPoint);
|
||||
auto const & fromCandidates = lineCandidates[i - 1];
|
||||
auto const & toCandidates = lineCandidates[i];
|
||||
auto & resultPathPart = resultPath[i - 1];
|
||||
|
||||
Graph::EdgeVector fakePath;
|
||||
|
||||
for (size_t fromInd = 0; fromInd < fromCandidates.size() && !found; ++fromInd)
|
||||
{
|
||||
for (size_t toInd = 0; toInd < toCandidates.size() && !found; ++toInd)
|
||||
{
|
||||
resultPathPart.clear();
|
||||
|
||||
found = ConnectAdjacentCandidateLines(fromCandidates[fromInd], toCandidates[toInd], point.m_lfrcnp,
|
||||
distanceToNextPoint, resultPathPart);
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
|
||||
found = ValidatePath(resultPathPart, distanceToNextPoint, m_pathLengthTolerance);
|
||||
if (fakePath.empty() && found && (resultPathPart.front().IsFake() || resultPathPart.back().IsFake()))
|
||||
{
|
||||
fakePath = resultPathPart;
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fakePath.empty() && !found)
|
||||
{
|
||||
found = true;
|
||||
resultPathPart = fakePath;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
LOG(LDEBUG, ("No shortest path found"));
|
||||
++m_stat.m_noShortestPathFound;
|
||||
resultPathPart.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(resultPath.size(), points.size() - 1, ());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PathsConnector::FindShortestPath(Graph::Edge const & from, Graph::Edge const & to,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, uint32_t maxPathLength,
|
||||
Graph::EdgeVector & path)
|
||||
{
|
||||
// TODO(mgsergio): Turn Dijkstra to A*.
|
||||
uint32_t const kLengthToleranceM = 10;
|
||||
|
||||
struct State
|
||||
{
|
||||
State(Graph::Edge const & e, uint32_t const s) : m_edge(e), m_score(s) {}
|
||||
|
||||
bool operator>(State const & o) const { return make_tuple(m_score, m_edge) > make_tuple(o.m_score, o.m_edge); }
|
||||
|
||||
Graph::Edge m_edge;
|
||||
uint32_t m_score;
|
||||
};
|
||||
|
||||
ASSERT(from.HasRealPart() && to.HasRealPart(), ());
|
||||
|
||||
priority_queue<State, vector<State>, greater<State>> q;
|
||||
map<Graph::Edge, uint32_t> scores;
|
||||
map<Graph::Edge, Graph::Edge> links;
|
||||
|
||||
q.emplace(from, 0);
|
||||
scores[from] = 0;
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const state = q.top();
|
||||
q.pop();
|
||||
|
||||
auto const & u = state.m_edge;
|
||||
// TODO(mgsergio): Unify names: use either score or distance.
|
||||
auto const us = state.m_score;
|
||||
|
||||
if (us > maxPathLength + kLengthToleranceM)
|
||||
continue;
|
||||
|
||||
if (us > scores[u])
|
||||
continue;
|
||||
|
||||
if (u == to)
|
||||
{
|
||||
for (auto e = u; e != from; e = links[e])
|
||||
path.push_back(e);
|
||||
path.push_back(from);
|
||||
reverse(begin(path), end(path));
|
||||
return true;
|
||||
}
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
m_graph.GetOutgoingEdges(u.GetEndJunction(), edges);
|
||||
for (auto const & e : edges)
|
||||
{
|
||||
if (!ConformLfrcnp(e, lowestFrcToNextPoint, 2 /* frcThreshold */, m_infoGetter))
|
||||
continue;
|
||||
// TODO(mgsergio): Use frc to filter edges.
|
||||
|
||||
// Only start and/or end of the route can be fake.
|
||||
// Routes made only of fake edges are no used to us.
|
||||
if (u.IsFake() && e.IsFake())
|
||||
continue;
|
||||
|
||||
auto const it = scores.find(e);
|
||||
auto const eScore = us + EdgeLength(e);
|
||||
if (it == end(scores) || it->second > eScore)
|
||||
{
|
||||
scores[e] = eScore;
|
||||
links[e] = u;
|
||||
q.emplace(e, eScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PathsConnector::ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, double distanceToNextPoint,
|
||||
Graph::EdgeVector & resultPath)
|
||||
|
||||
{
|
||||
ASSERT(!to.empty(), ());
|
||||
|
||||
if (auto const skip = PathOverlappingLen(from, to))
|
||||
{
|
||||
if (skip == -1)
|
||||
return false;
|
||||
copy(begin(from), end(from), back_inserter(resultPath));
|
||||
copy(begin(to) + skip, end(to), back_inserter(resultPath));
|
||||
return true;
|
||||
}
|
||||
|
||||
ASSERT(from.back() != to.front(), ());
|
||||
|
||||
Graph::EdgeVector shortestPath;
|
||||
auto const found = FindShortestPath(from.back(), to.front(), lowestFrcToNextPoint, distanceToNextPoint, shortestPath);
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
// Skip the last edge from |from| because it already took its place at begin(shortestPath).
|
||||
copy(begin(from), prev(end(from)), back_inserter(resultPath));
|
||||
copy(begin(shortestPath), end(shortestPath), back_inserter(resultPath));
|
||||
// Skip the first edge from |to| because it already took its place at prev(end(shortestPath)).
|
||||
copy(next(begin(to)), end(to), back_inserter(resultPath));
|
||||
|
||||
return found;
|
||||
}
|
||||
} // namespace openlr
|
||||
36
tools/openlr/paths_connector.hpp
Normal file
36
tools/openlr/paths_connector.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class PathsConnector
|
||||
{
|
||||
public:
|
||||
PathsConnector(double pathLengthTolerance, Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat);
|
||||
|
||||
bool ConnectCandidates(std::vector<LocationReferencePoint> const & points,
|
||||
std::vector<std::vector<Graph::EdgeVector>> const & lineCandidates,
|
||||
std::vector<Graph::EdgeVector> & resultPath);
|
||||
|
||||
private:
|
||||
bool FindShortestPath(Graph::Edge const & from, Graph::Edge const & to, FunctionalRoadClass lowestFrcToNextPoint,
|
||||
uint32_t maxPathLength, Graph::EdgeVector & path);
|
||||
|
||||
bool ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, double distanceToNextPoint,
|
||||
Graph::EdgeVector & resultPath);
|
||||
|
||||
double m_pathLengthTolerance;
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stat;
|
||||
};
|
||||
} // namespace openlr
|
||||
37
tools/openlr/road_info_getter.cpp
Normal file
37
tools/openlr/road_info_getter.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include "openlr/road_info_getter.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// RoadInfoGetter::RoadInfo ------------------------------------------------------------------------
|
||||
RoadInfoGetter::RoadInfo::RoadInfo(FeatureType & ft)
|
||||
: m_hwClass(ftypes::GetHighwayClass(feature::TypesHolder(ft)))
|
||||
, m_link(ftypes::IsLinkChecker::Instance()(ft))
|
||||
, m_oneWay(ftypes::IsOneWayChecker::Instance()(ft))
|
||||
, m_isRoundabout(ftypes::IsRoundAboutChecker::Instance()(ft))
|
||||
{}
|
||||
|
||||
// RoadInfoGetter ----------------------------------------------------------------------------------
|
||||
RoadInfoGetter::RoadInfoGetter(DataSource const & dataSource) : m_dataSource(dataSource) {}
|
||||
|
||||
RoadInfoGetter::RoadInfo RoadInfoGetter::Get(FeatureID const & fid)
|
||||
{
|
||||
auto it = m_cache.find(fid);
|
||||
if (it != end(m_cache))
|
||||
return it->second;
|
||||
|
||||
FeaturesLoaderGuard g(m_dataSource, fid.m_mwmId);
|
||||
auto ft = g.GetOriginalFeatureByIndex(fid.m_index);
|
||||
CHECK(ft, ());
|
||||
|
||||
RoadInfo info(*ft);
|
||||
it = m_cache.emplace(fid, info).first;
|
||||
|
||||
return it->second;
|
||||
}
|
||||
} // namespace openlr
|
||||
41
tools/openlr/road_info_getter.hpp
Normal file
41
tools/openlr/road_info_getter.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
class Classificator;
|
||||
class DataSource;
|
||||
|
||||
namespace feature
|
||||
{
|
||||
class TypesHolder;
|
||||
}
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class RoadInfoGetter final
|
||||
{
|
||||
public:
|
||||
struct RoadInfo
|
||||
{
|
||||
explicit RoadInfo(FeatureType & ft);
|
||||
|
||||
ftypes::HighwayClass m_hwClass = ftypes::HighwayClass::Undefined;
|
||||
bool m_link = false;
|
||||
bool m_oneWay = false;
|
||||
bool m_isRoundabout = false;
|
||||
};
|
||||
|
||||
explicit RoadInfoGetter(DataSource const & dataSource);
|
||||
|
||||
RoadInfo Get(FeatureID const & fid);
|
||||
|
||||
private:
|
||||
DataSource const & m_dataSource;
|
||||
std::map<FeatureID, RoadInfo> m_cache;
|
||||
};
|
||||
} // namespace openlr
|
||||
751
tools/openlr/router.cpp
Normal file
751
tools/openlr/router.cpp
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
#include "openlr/router.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
|
||||
#include "routing/features_road_graph.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
|
||||
using boost::make_transform_iterator;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int const kFRCThreshold = 2;
|
||||
size_t const kMaxRoadCandidates = 20;
|
||||
double const kDistanceAccuracyM = 1000;
|
||||
double const kEps = 1e-9;
|
||||
double const kBearingDist = 25;
|
||||
|
||||
int const kNumBuckets = 256;
|
||||
double const kAnglesInBucket = 360.0 / kNumBuckets;
|
||||
|
||||
uint32_t Bearing(m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
auto const angle = location::AngleToBearing(math::RadToDeg(ang::AngleTo(a, b)));
|
||||
CHECK_LESS_OR_EQUAL(angle, 360, ("Angle should be less than or equal to 360."));
|
||||
CHECK_GREATER_OR_EQUAL(angle, 0, ("Angle should be greater than or equal to 0"));
|
||||
return math::Clamp(angle / kAnglesInBucket, 0.0, 255.0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Router::Vertex::Score ---------------------------------------------------------------------------
|
||||
void Router::Vertex::Score::AddFakePenalty(double p, bool partOfReal)
|
||||
{
|
||||
m_penalty += (partOfReal ? kFakeCoeff : kTrueFakeCoeff) * p;
|
||||
}
|
||||
|
||||
void Router::Vertex::Score::AddBearingPenalty(int expected, int actual)
|
||||
{
|
||||
ASSERT_LESS(expected, kNumBuckets, ());
|
||||
ASSERT_GREATER_OR_EQUAL(expected, 0, ());
|
||||
|
||||
ASSERT_LESS(actual, kNumBuckets, ());
|
||||
ASSERT_GREATER_OR_EQUAL(actual, 0, ());
|
||||
|
||||
int const diff = abs(expected - actual);
|
||||
double angle = math::DegToRad(std::min(diff, kNumBuckets - diff) * kAnglesInBucket);
|
||||
m_penalty += kBearingErrorCoeff * angle * kBearingDist;
|
||||
}
|
||||
|
||||
bool Router::Vertex::Score::operator<(Score const & rhs) const
|
||||
{
|
||||
auto const ls = GetScore();
|
||||
auto const rs = rhs.GetScore();
|
||||
if (ls != rs)
|
||||
return ls < rs;
|
||||
|
||||
if (m_distance != rhs.m_distance)
|
||||
return m_distance < rhs.m_distance;
|
||||
return m_penalty < rhs.m_penalty;
|
||||
}
|
||||
|
||||
bool Router::Vertex::Score::operator==(Score const & rhs) const
|
||||
{
|
||||
return m_distance == rhs.m_distance && m_penalty == rhs.m_penalty;
|
||||
}
|
||||
|
||||
// Router::Vertex ----------------------------------------------------------------------------------
|
||||
Router::Vertex::Vertex(geometry::PointWithAltitude const & junction, geometry::PointWithAltitude const & stageStart,
|
||||
double stageStartDistance, size_t stage, bool bearingChecked)
|
||||
: m_junction(junction)
|
||||
, m_stageStart(stageStart)
|
||||
, m_stageStartDistance(stageStartDistance)
|
||||
, m_stage(stage)
|
||||
, m_bearingChecked(bearingChecked)
|
||||
{}
|
||||
|
||||
bool Router::Vertex::operator<(Vertex const & rhs) const
|
||||
{
|
||||
if (m_junction != rhs.m_junction)
|
||||
return m_junction < rhs.m_junction;
|
||||
if (m_stageStart != rhs.m_stageStart)
|
||||
return m_stageStart < rhs.m_stageStart;
|
||||
if (m_stageStartDistance != rhs.m_stageStartDistance)
|
||||
return m_stageStartDistance < rhs.m_stageStartDistance;
|
||||
if (m_stage != rhs.m_stage)
|
||||
return m_stage < rhs.m_stage;
|
||||
return m_bearingChecked < rhs.m_bearingChecked;
|
||||
}
|
||||
|
||||
bool Router::Vertex::operator==(Vertex const & rhs) const
|
||||
{
|
||||
return m_junction == rhs.m_junction && m_stageStart == rhs.m_stageStart &&
|
||||
m_stageStartDistance == rhs.m_stageStartDistance && m_stage == rhs.m_stage &&
|
||||
m_bearingChecked == rhs.m_bearingChecked;
|
||||
}
|
||||
|
||||
// Router::Edge ------------------------------------------------------------------------------------
|
||||
Router::Edge::Edge(Vertex const & u, Vertex const & v, routing::Edge const & raw, bool isSpecial)
|
||||
: m_u(u)
|
||||
, m_v(v)
|
||||
, m_raw(raw)
|
||||
, m_isSpecial(isSpecial)
|
||||
{}
|
||||
|
||||
// static
|
||||
Router::Edge Router::Edge::MakeNormal(Vertex const & u, Vertex const & v, routing::Edge const & raw)
|
||||
{
|
||||
return Edge(u, v, raw, false /* isSpecial */);
|
||||
}
|
||||
|
||||
// static
|
||||
Router::Edge Router::Edge::MakeSpecial(Vertex const & u, Vertex const & v)
|
||||
{
|
||||
return Edge(u, v, routing::Edge::MakeFake(u.m_junction, v.m_junction), true /* isSpecial */);
|
||||
}
|
||||
|
||||
std::pair<m2::PointD, m2::PointD> Router::Edge::ToPair() const
|
||||
{
|
||||
auto const & e = m_raw;
|
||||
return std::make_pair(e.GetStartJunction().GetPoint(), e.GetEndJunction().GetPoint());
|
||||
}
|
||||
|
||||
std::pair<m2::PointD, m2::PointD> Router::Edge::ToPairRev() const
|
||||
{
|
||||
auto const & e = m_raw;
|
||||
return std::make_pair(e.GetEndJunction().GetPoint(), e.GetStartJunction().GetPoint());
|
||||
}
|
||||
|
||||
// Router::Router ----------------------------------------------------------------------------------
|
||||
Router::Router(routing::FeaturesRoadGraph & graph, RoadInfoGetter & roadInfoGetter)
|
||||
: m_graph(graph)
|
||||
, m_roadInfoGetter(roadInfoGetter)
|
||||
{}
|
||||
|
||||
bool Router::Go(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM, Path & path)
|
||||
{
|
||||
if (!Init(points, positiveOffsetM, negativeOffsetM))
|
||||
return false;
|
||||
return FindPath(path);
|
||||
}
|
||||
|
||||
bool Router::Init(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(points.size(), 2, ());
|
||||
|
||||
m_points = points;
|
||||
m_positiveOffsetM = positiveOffsetM;
|
||||
m_negativeOffsetM = negativeOffsetM;
|
||||
|
||||
m_graph.ResetFakes();
|
||||
|
||||
m_pivots.clear();
|
||||
for (size_t i = 1; i + 1 < m_points.size(); ++i)
|
||||
{
|
||||
m_pivots.emplace_back();
|
||||
auto & ps = m_pivots.back();
|
||||
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> vicinity;
|
||||
m_graph.FindClosestEdges(
|
||||
mercator::RectByCenterXYAndSizeInMeters(m_points[i].m_point, routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, vicinity);
|
||||
for (auto const & v : vicinity)
|
||||
{
|
||||
auto const & e = v.first;
|
||||
ps.push_back(e.GetStartJunction().GetPoint());
|
||||
ps.push_back(e.GetEndJunction().GetPoint());
|
||||
}
|
||||
|
||||
if (ps.empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pivots.push_back({m_points.back().m_point});
|
||||
CHECK_EQUAL(m_pivots.size() + 1, m_points.size(), ());
|
||||
|
||||
{
|
||||
m_sourceJunction = geometry::PointWithAltitude(m_points.front().m_point, 0 /* altitude */);
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> sourceVicinity;
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(m_sourceJunction.GetPoint(),
|
||||
routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, sourceVicinity);
|
||||
m_graph.AddFakeEdges(m_sourceJunction, sourceVicinity);
|
||||
}
|
||||
|
||||
{
|
||||
m_targetJunction = geometry::PointWithAltitude(m_points.back().m_point, 0 /* altitude */);
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> targetVicinity;
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(m_targetJunction.GetPoint(),
|
||||
routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, targetVicinity);
|
||||
m_graph.AddFakeEdges(m_targetJunction, targetVicinity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Router::FindPath(Path & path)
|
||||
{
|
||||
using State = std::pair<Vertex::Score, Vertex>;
|
||||
std::priority_queue<State, std::vector<State>, std::greater<State>> queue;
|
||||
std::map<Vertex, Vertex::Score> scores;
|
||||
Links links;
|
||||
|
||||
auto const pushVertex =
|
||||
[&queue, &scores, &links](Vertex const & u, Vertex const & v, Vertex::Score const & vertexScore, Edge const & e)
|
||||
{
|
||||
if ((scores.count(v) == 0 || scores[v].GetScore() > vertexScore.GetScore() + kEps) && u != v)
|
||||
{
|
||||
scores[v] = vertexScore;
|
||||
links[v] = std::make_pair(u, e);
|
||||
queue.emplace(vertexScore, v);
|
||||
}
|
||||
};
|
||||
|
||||
Vertex const s(m_sourceJunction, m_sourceJunction, 0 /* stageStartDistance */, 0 /* stage */,
|
||||
false /* bearingChecked */);
|
||||
CHECK(!NeedToCheckBearing(s, 0 /* distance */), ());
|
||||
|
||||
scores[s] = Vertex::Score();
|
||||
queue.emplace(scores[s], s);
|
||||
|
||||
double const piS = GetPotential(s);
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
auto const p = queue.top();
|
||||
queue.pop();
|
||||
|
||||
Vertex::Score const & su = p.first;
|
||||
Vertex const & u = p.second;
|
||||
|
||||
if (su != scores[u])
|
||||
continue;
|
||||
|
||||
if (IsFinalVertex(u))
|
||||
{
|
||||
std::vector<Edge> edges;
|
||||
auto cur = u;
|
||||
while (cur != s)
|
||||
{
|
||||
auto const & p = links[cur];
|
||||
|
||||
edges.push_back(p.second);
|
||||
cur = p.first;
|
||||
}
|
||||
reverse(edges.begin(), edges.end());
|
||||
return ReconstructPath(edges, path);
|
||||
}
|
||||
|
||||
size_t const stage = u.m_stage;
|
||||
double const distanceToNextPointM = m_points[stage].m_distanceToNextPointM;
|
||||
double const piU = GetPotential(u);
|
||||
double const ud = su.GetDistance() + piS - piU; // real distance to u
|
||||
|
||||
CHECK_LESS(stage, m_pivots.size(), ());
|
||||
|
||||
// max(kDistanceAccuracyM, m_distanceToNextPointM) is added here
|
||||
// to throw out quite long paths.
|
||||
if (ud > u.m_stageStartDistance + distanceToNextPointM + std::max(kDistanceAccuracyM, distanceToNextPointM))
|
||||
continue;
|
||||
|
||||
if (NearNextStage(u, piU) && !u.m_bearingChecked)
|
||||
{
|
||||
Vertex v = u;
|
||||
|
||||
Vertex::Score sv = su;
|
||||
if (u.m_junction != u.m_stageStart)
|
||||
{
|
||||
int const expected = m_points[stage].m_bearing;
|
||||
int const actual = Bearing(u.m_stageStart.GetPoint(), u.m_junction.GetPoint());
|
||||
sv.AddBearingPenalty(expected, actual);
|
||||
}
|
||||
v.m_bearingChecked = true;
|
||||
|
||||
pushVertex(u, v, sv, Edge::MakeSpecial(u, v));
|
||||
}
|
||||
|
||||
if (MayMoveToNextStage(u, piU))
|
||||
{
|
||||
Vertex v(u.m_junction, u.m_junction, ud /* stageStartDistance */, stage + 1, false /* bearingChecked */);
|
||||
double const piV = GetPotential(v);
|
||||
|
||||
Vertex::Score sv = su;
|
||||
sv.AddDistance(std::max(piV - piU, 0.0));
|
||||
sv.AddIntermediateErrorPenalty(mercator::DistanceOnEarth(v.m_junction.GetPoint(), m_points[v.m_stage].m_point));
|
||||
|
||||
if (IsFinalVertex(v))
|
||||
{
|
||||
int const expected = m_points.back().m_bearing;
|
||||
int const actual = GetReverseBearing(u, links);
|
||||
sv.AddBearingPenalty(expected, actual);
|
||||
}
|
||||
|
||||
pushVertex(u, v, sv, Edge::MakeSpecial(u, v));
|
||||
}
|
||||
|
||||
ForEachEdge(u, true /* outgoing */, m_points[stage].m_lfrcnp, [&](routing::Edge const & edge)
|
||||
{
|
||||
Vertex v = u;
|
||||
v.m_junction = edge.GetEndJunction();
|
||||
|
||||
double const piV = GetPotential(v);
|
||||
|
||||
Vertex::Score sv = su;
|
||||
double const w = GetWeight(edge);
|
||||
sv.AddDistance(std::max(w + piV - piU, 0.0));
|
||||
|
||||
double const vd = ud + w; // real distance to v
|
||||
if (NeedToCheckBearing(v, vd))
|
||||
{
|
||||
CHECK(!NeedToCheckBearing(u, ud), ());
|
||||
|
||||
double const delta = vd - v.m_stageStartDistance - kBearingDist;
|
||||
auto const p = PointAtSegment(edge.GetStartJunction().GetPoint(), edge.GetEndJunction().GetPoint(), delta);
|
||||
if (v.m_stageStart.GetPoint() != p)
|
||||
{
|
||||
int const expected = m_points[stage].m_bearing;
|
||||
int const actual = Bearing(v.m_stageStart.GetPoint(), p);
|
||||
sv.AddBearingPenalty(expected, actual);
|
||||
}
|
||||
v.m_bearingChecked = true;
|
||||
}
|
||||
|
||||
if (vd > v.m_stageStartDistance + distanceToNextPointM)
|
||||
sv.AddDistanceErrorPenalty(std::min(vd - v.m_stageStartDistance - distanceToNextPointM, w));
|
||||
|
||||
if (edge.IsFake())
|
||||
sv.AddFakePenalty(w, edge.HasRealPart());
|
||||
|
||||
pushVertex(u, v, sv, Edge::MakeNormal(u, v, edge));
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Router::NeedToCheckBearing(Vertex const & u, double distanceM) const
|
||||
{
|
||||
if (IsFinalVertex(u) || u.m_bearingChecked)
|
||||
return false;
|
||||
return distanceM >= u.m_stageStartDistance + kBearingDist;
|
||||
}
|
||||
|
||||
double Router::GetPotential(Vertex const & u) const
|
||||
{
|
||||
if (IsFinalVertex(u))
|
||||
return 0.0;
|
||||
|
||||
CHECK_LESS(u.m_stage, m_pivots.size(), ());
|
||||
|
||||
auto const & pivots = m_pivots[u.m_stage];
|
||||
CHECK(!pivots.empty(), ("Empty list of pivots"));
|
||||
|
||||
double potential = std::numeric_limits<double>::max();
|
||||
|
||||
auto const & point = u.m_junction.GetPoint();
|
||||
for (auto const & pivot : pivots)
|
||||
potential = std::min(potential, mercator::DistanceOnEarth(pivot, point));
|
||||
return potential;
|
||||
}
|
||||
|
||||
bool Router::NearNextStage(Vertex const & u, double pi) const
|
||||
{
|
||||
return u.m_stage < m_pivots.size() && pi < kEps;
|
||||
}
|
||||
|
||||
bool Router::MayMoveToNextStage(Vertex const & u, double pi) const
|
||||
{
|
||||
return NearNextStage(u, pi) && u.m_bearingChecked;
|
||||
}
|
||||
|
||||
uint32_t Router::GetReverseBearing(Vertex const & u, Links const & links) const
|
||||
{
|
||||
m2::PointD const a = u.m_junction.GetPoint();
|
||||
m2::PointD b = m2::PointD::Zero();
|
||||
|
||||
Vertex curr = u;
|
||||
double passed = 0;
|
||||
bool found = false;
|
||||
while (true)
|
||||
{
|
||||
auto const it = links.find(curr);
|
||||
if (it == links.end())
|
||||
break;
|
||||
|
||||
auto const & p = it->second;
|
||||
auto const & prev = p.first;
|
||||
auto const & edge = p.second.m_raw;
|
||||
|
||||
if (prev.m_stage != curr.m_stage)
|
||||
break;
|
||||
|
||||
double const weight = GetWeight(edge);
|
||||
|
||||
if (passed + weight >= kBearingDist)
|
||||
{
|
||||
double const delta = kBearingDist - passed;
|
||||
b = PointAtSegment(edge.GetEndJunction().GetPoint(), edge.GetStartJunction().GetPoint(), delta);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
passed += weight;
|
||||
curr = prev;
|
||||
}
|
||||
if (!found)
|
||||
b = curr.m_junction.GetPoint();
|
||||
return Bearing(a, b);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Router::ForEachEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn)
|
||||
{
|
||||
routing::IRoadGraph::EdgeListT edges;
|
||||
if (outgoing)
|
||||
GetOutgoingEdges(u.m_junction, edges);
|
||||
else
|
||||
GetIngoingEdges(u.m_junction, edges);
|
||||
for (auto const & edge : edges)
|
||||
{
|
||||
if (!ConformLfrcnp(edge, restriction, kFRCThreshold, m_roadInfoGetter))
|
||||
continue;
|
||||
fn(edge);
|
||||
}
|
||||
}
|
||||
|
||||
void Router::GetOutgoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges)
|
||||
{
|
||||
GetEdges(u, &routing::IRoadGraph::GetRegularOutgoingEdges, &routing::IRoadGraph::GetFakeOutgoingEdges,
|
||||
m_outgoingCache, edges);
|
||||
}
|
||||
|
||||
void Router::GetIngoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges)
|
||||
{
|
||||
GetEdges(u, &routing::IRoadGraph::GetRegularIngoingEdges, &routing::IRoadGraph::GetFakeIngoingEdges, m_ingoingCache,
|
||||
edges);
|
||||
}
|
||||
|
||||
void Router::GetEdges(geometry::PointWithAltitude const & u, RoadGraphEdgesGetter getRegular,
|
||||
RoadGraphEdgesGetter getFake, EdgeCacheT & cache, routing::IRoadGraph::EdgeListT & edges)
|
||||
{
|
||||
auto const it = cache.find(u);
|
||||
if (it == cache.end())
|
||||
{
|
||||
auto & es = cache[u];
|
||||
(m_graph.*getRegular)(u, es);
|
||||
edges.append(es.begin(), es.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const & es = it->second;
|
||||
edges.append(es.begin(), es.end());
|
||||
}
|
||||
(m_graph.*getFake)(u, edges);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Router::ForEachNonFakeEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn)
|
||||
{
|
||||
ForEachEdge(u, outgoing, restriction, [&fn](routing::Edge const & edge)
|
||||
{
|
||||
if (!edge.IsFake())
|
||||
fn(edge);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Router::ForEachNonFakeClosestEdge(Vertex const & u, FunctionalRoadClass const restriction, Fn && fn)
|
||||
{
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> vicinity;
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(u.m_junction.GetPoint(),
|
||||
routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, vicinity);
|
||||
|
||||
for (auto const & p : vicinity)
|
||||
{
|
||||
auto const & edge = p.first;
|
||||
if (edge.IsFake())
|
||||
continue;
|
||||
if (!ConformLfrcnp(edge, restriction, kFRCThreshold, m_roadInfoGetter))
|
||||
continue;
|
||||
fn(edge);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
size_t Router::FindPrefixLengthToConsume(It b, It const e, double lengthM)
|
||||
{
|
||||
size_t n = 0;
|
||||
while (b != e && lengthM > 0.0)
|
||||
{
|
||||
// Need p here to prolongate lifetime of (*b) if iterator
|
||||
// dereferencing returns a temprorary object instead of a
|
||||
// reference.
|
||||
auto const & p = *b;
|
||||
auto const & u = p.first;
|
||||
auto const & v = p.second;
|
||||
double const len = mercator::DistanceOnEarth(u, v);
|
||||
if (2 * lengthM < len)
|
||||
break;
|
||||
|
||||
lengthM -= len;
|
||||
++n;
|
||||
++b;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
double Router::GetCoverage(m2::PointD const & u, m2::PointD const & v, It b, It e)
|
||||
{
|
||||
double const kEps = 1e-5;
|
||||
double const kLengthThresholdM = 1;
|
||||
|
||||
m2::PointD const uv = v - u;
|
||||
double const sqlen = u.SquaredLength(v);
|
||||
|
||||
if (mercator::DistanceOnEarth(u, v) < kLengthThresholdM)
|
||||
return 0;
|
||||
|
||||
std::vector<std::pair<double, double>> covs;
|
||||
for (; b != e; ++b)
|
||||
{
|
||||
auto const s = b->m_u.m_junction.GetPoint();
|
||||
auto const t = b->m_v.m_junction.GetPoint();
|
||||
if (!m2::IsPointOnSegmentEps(s, u, v, kEps) || !m2::IsPointOnSegmentEps(t, u, v, kEps))
|
||||
continue;
|
||||
|
||||
if (DotProduct(uv, t - s) < -kEps)
|
||||
continue;
|
||||
|
||||
double const sp = DotProduct(uv, s - u) / sqlen;
|
||||
double const tp = DotProduct(uv, t - u) / sqlen;
|
||||
|
||||
double const start = math::Clamp(std::min(sp, tp), 0.0, 1.0);
|
||||
double const finish = math::Clamp(std::max(sp, tp), 0.0, 1.0);
|
||||
covs.emplace_back(start, finish);
|
||||
}
|
||||
|
||||
sort(covs.begin(), covs.end());
|
||||
|
||||
double coverage = 0;
|
||||
|
||||
size_t i = 0;
|
||||
while (i != covs.size())
|
||||
{
|
||||
size_t j = i;
|
||||
|
||||
double const first = covs[i].first;
|
||||
double last = covs[i].second;
|
||||
while (j != covs.size() && covs[j].first <= last)
|
||||
{
|
||||
last = std::max(last, covs[j].second);
|
||||
++j;
|
||||
}
|
||||
|
||||
coverage += last - first;
|
||||
i = j;
|
||||
}
|
||||
|
||||
CHECK_LESS_OR_EQUAL(coverage, 1.0 + kEps, ());
|
||||
|
||||
return coverage;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
double Router::GetMatchingScore(m2::PointD const & u, m2::PointD const & v, It b, It e)
|
||||
{
|
||||
double const kEps = 1e-5;
|
||||
|
||||
double const len = mercator::DistanceOnEarth(u, v);
|
||||
|
||||
m2::PointD const uv = v - u;
|
||||
|
||||
double cov = 0;
|
||||
for (; b != e; ++b)
|
||||
{
|
||||
// Need p here to prolongate lifetime of (*b) if iterator
|
||||
// dereferencing returns a temprorary object instead of a
|
||||
// reference.
|
||||
auto const & p = *b;
|
||||
auto const & s = p.first;
|
||||
auto const & t = p.second;
|
||||
if (!m2::IsPointOnSegmentEps(s, u, v, kEps) || !m2::IsPointOnSegmentEps(t, u, v, kEps))
|
||||
break;
|
||||
|
||||
m2::PointD const st = t - s;
|
||||
if (DotProduct(uv, st) < -kEps)
|
||||
break;
|
||||
|
||||
cov += mercator::DistanceOnEarth(s, t);
|
||||
}
|
||||
|
||||
return len == 0 ? 0 : math::Clamp(cov / len, 0.0, 1.0);
|
||||
}
|
||||
|
||||
template <typename It, typename Fn>
|
||||
void Router::ForStagePrefix(It b, It e, size_t stage, Fn && fn)
|
||||
{
|
||||
while (b != e && b->m_raw.IsFake() && b->m_u.m_stage == stage && b->m_v.m_stage == stage)
|
||||
++b;
|
||||
if (b != e && !b->m_raw.IsFake())
|
||||
fn(b);
|
||||
}
|
||||
|
||||
bool Router::ReconstructPath(std::vector<Edge> & edges, Path & path)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(m_points.size(), 2, ());
|
||||
|
||||
using EdgeIt = std::vector<Edge>::iterator;
|
||||
using EdgeItRev = std::vector<Edge>::reverse_iterator;
|
||||
|
||||
double const kFakeCoverageThreshold = 0.5;
|
||||
|
||||
base::EraseIf(edges, [](auto && e) { return e.IsSpecial(); });
|
||||
|
||||
{
|
||||
auto toPair = [](auto && e) { return e.ToPair(); };
|
||||
size_t const n = FindPrefixLengthToConsume(make_transform_iterator(edges.begin(), toPair),
|
||||
make_transform_iterator(edges.end(), toPair), m_positiveOffsetM);
|
||||
CHECK_LESS_OR_EQUAL(n, edges.size(), ());
|
||||
edges.erase(edges.begin(), edges.begin() + n);
|
||||
}
|
||||
|
||||
{
|
||||
auto toPairRev = [](auto && e) { return e.ToPairRev(); };
|
||||
size_t const n = FindPrefixLengthToConsume(make_transform_iterator(edges.rbegin(), toPairRev),
|
||||
make_transform_iterator(edges.rend(), toPairRev), m_negativeOffsetM);
|
||||
CHECK_LESS_OR_EQUAL(n, edges.size(), ());
|
||||
edges.erase(edges.begin() + edges.size() - n, edges.end());
|
||||
}
|
||||
|
||||
double frontEdgeScore = -1.0;
|
||||
routing::Edge frontEdge;
|
||||
ForStagePrefix(edges.begin(), edges.end(), 0, [&](EdgeIt e)
|
||||
{
|
||||
ForEachNonFakeEdge(e->m_u, false /* outgoing */, m_points[0].m_lfrcnp, [&](routing::Edge const & edge)
|
||||
{
|
||||
auto toPairRev = [](auto && e) { return e.ToPairRev(); };
|
||||
double const score = GetMatchingScore(edge.GetEndJunction().GetPoint(), edge.GetStartJunction().GetPoint(),
|
||||
make_transform_iterator(EdgeItRev(e), toPairRev),
|
||||
make_transform_iterator(edges.rend(), toPairRev));
|
||||
if (score > frontEdgeScore)
|
||||
{
|
||||
frontEdgeScore = score;
|
||||
frontEdge = edge.GetReverseEdge();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
double backEdgeScore = -1.0;
|
||||
routing::Edge backEdge;
|
||||
ForStagePrefix(edges.rbegin(), edges.rend(), m_points.size() - 2 /* stage */, [&](EdgeItRev e)
|
||||
{
|
||||
ForEachNonFakeEdge(e->m_v, true /* outgoing */, m_points[m_points.size() - 2].m_lfrcnp,
|
||||
[&](routing::Edge const & edge)
|
||||
{
|
||||
auto toPair = [](auto && e) { return e.ToPair(); };
|
||||
double const score =
|
||||
GetMatchingScore(edge.GetStartJunction().GetPoint(), edge.GetEndJunction().GetPoint(),
|
||||
make_transform_iterator(e.base(), toPair), make_transform_iterator(edges.end(), toPair));
|
||||
if (score > backEdgeScore)
|
||||
{
|
||||
backEdgeScore = score;
|
||||
backEdge = edge;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
path.clear();
|
||||
for (auto const & e : edges)
|
||||
if (!e.IsFake())
|
||||
path.push_back(e.m_raw);
|
||||
|
||||
if (frontEdgeScore >= kFakeCoverageThreshold && !path.empty() && path.front() != frontEdge)
|
||||
path.insert(path.begin(), frontEdge);
|
||||
|
||||
if (backEdgeScore >= kFakeCoverageThreshold && !path.empty() && path.back() != backEdge)
|
||||
path.insert(path.end(), backEdge);
|
||||
|
||||
if (path.empty())
|
||||
{
|
||||
// This is the case for routes from fake edges only.
|
||||
FindSingleEdgeApproximation(edges, path);
|
||||
}
|
||||
|
||||
return !path.empty();
|
||||
}
|
||||
|
||||
void Router::FindSingleEdgeApproximation(std::vector<Edge> const & edges, Path & path)
|
||||
{
|
||||
double const kCoverageThreshold = 0.5;
|
||||
|
||||
CHECK(all_of(edges.begin(), edges.end(), [](auto && e) { return e.IsFake(); }), ());
|
||||
|
||||
double expectedLength = 0;
|
||||
for (auto const & edge : edges)
|
||||
expectedLength += GetWeight(edge);
|
||||
|
||||
if (expectedLength < kEps)
|
||||
return;
|
||||
|
||||
double bestCoverage = -1.0;
|
||||
routing::Edge bestEdge;
|
||||
|
||||
auto checkEdge = [&](routing::Edge const & edge)
|
||||
{
|
||||
double const weight = GetWeight(edge);
|
||||
double const fraction =
|
||||
GetCoverage(edge.GetStartJunction().GetPoint(), edge.GetEndJunction().GetPoint(), edges.begin(), edges.end());
|
||||
double const coverage = weight * fraction;
|
||||
if (coverage >= bestCoverage)
|
||||
{
|
||||
bestCoverage = coverage;
|
||||
bestEdge = edge;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const & edge : edges)
|
||||
{
|
||||
auto const & u = edge.m_u;
|
||||
auto const & v = edge.m_v;
|
||||
CHECK_EQUAL(u.m_stage, v.m_stage, ());
|
||||
auto const stage = u.m_stage;
|
||||
|
||||
ForEachNonFakeClosestEdge(u, m_points[stage].m_lfrcnp, checkEdge);
|
||||
ForEachNonFakeClosestEdge(v, m_points[stage].m_lfrcnp, checkEdge);
|
||||
}
|
||||
|
||||
if (bestCoverage >= expectedLength * kCoverageThreshold)
|
||||
path = {bestEdge};
|
||||
}
|
||||
} // namespace openlr
|
||||
222
tools/openlr/router.hpp
Normal file
222
tools/openlr/router.hpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/way_point.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
{
|
||||
class FeaturesRoadGraph;
|
||||
}
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class RoadInfoGetter;
|
||||
|
||||
class Router final
|
||||
{
|
||||
public:
|
||||
Router(routing::FeaturesRoadGraph & graph, RoadInfoGetter & roadInfoGetter);
|
||||
|
||||
bool Go(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM, Path & path);
|
||||
|
||||
private:
|
||||
struct Vertex final
|
||||
{
|
||||
class Score final
|
||||
{
|
||||
public:
|
||||
// A weight for total length of true fake edges.
|
||||
static int const kTrueFakeCoeff = 10;
|
||||
|
||||
// A weight for total length of fake edges that are parts of some
|
||||
// real edges.
|
||||
static constexpr double kFakeCoeff = 0.001;
|
||||
|
||||
// A weight for passing too far from pivot points.
|
||||
static int const kIntermediateErrorCoeff = 3;
|
||||
|
||||
// A weight for excess of distance limit.
|
||||
static int const kDistanceErrorCoeff = 3;
|
||||
|
||||
// A weight for deviation from bearing.
|
||||
static int const kBearingErrorCoeff = 5;
|
||||
|
||||
void AddDistance(double p) { m_distance += p; }
|
||||
void AddFakePenalty(double p, bool partOfReal);
|
||||
void AddIntermediateErrorPenalty(double p) { m_penalty += kIntermediateErrorCoeff * p; }
|
||||
void AddDistanceErrorPenalty(double p) { m_penalty += kDistanceErrorCoeff * p; }
|
||||
void AddBearingPenalty(int expected, int actual);
|
||||
|
||||
double GetDistance() const { return m_distance; }
|
||||
double GetPenalty() const { return m_penalty; }
|
||||
double GetScore() const { return m_distance + m_penalty; }
|
||||
|
||||
bool operator<(Score const & rhs) const;
|
||||
bool operator>(Score const & rhs) const { return rhs < *this; }
|
||||
bool operator==(Score const & rhs) const;
|
||||
bool operator!=(Score const & rhs) const { return !(*this == rhs); }
|
||||
|
||||
private:
|
||||
// Reduced length of path in meters.
|
||||
double m_distance = 0.0;
|
||||
|
||||
double m_penalty = 0.0;
|
||||
};
|
||||
|
||||
Vertex() = default;
|
||||
Vertex(geometry::PointWithAltitude const & junction, geometry::PointWithAltitude const & stageStart,
|
||||
double stageStartDistance, size_t stage, bool bearingChecked);
|
||||
|
||||
bool operator<(Vertex const & rhs) const;
|
||||
bool operator==(Vertex const & rhs) const;
|
||||
bool operator!=(Vertex const & rhs) const { return !(*this == rhs); }
|
||||
|
||||
m2::PointD GetPoint() const { return m_junction.GetPoint(); }
|
||||
|
||||
geometry::PointWithAltitude m_junction;
|
||||
geometry::PointWithAltitude m_stageStart;
|
||||
double m_stageStartDistance = 0.0;
|
||||
size_t m_stage = 0;
|
||||
bool m_bearingChecked = false;
|
||||
};
|
||||
|
||||
friend std::string DebugPrint(Vertex const & u)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Vertex [ ";
|
||||
os << "junction: " << DebugPrint(u.m_junction) << ", ";
|
||||
os << "stageStart: " << DebugPrint(u.m_stageStart) << ", ";
|
||||
os << "stageStartDistance: " << u.m_stageStartDistance << ", ";
|
||||
os << "stage: " << u.m_stage << ", ";
|
||||
os << "bearingChecked: " << u.m_bearingChecked;
|
||||
os << " ]";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
struct Edge final
|
||||
{
|
||||
Edge() = default;
|
||||
Edge(Vertex const & u, Vertex const & v, routing::Edge const & raw, bool isSpecial);
|
||||
|
||||
static Edge MakeNormal(Vertex const & u, Vertex const & v, routing::Edge const & raw);
|
||||
static Edge MakeSpecial(Vertex const & u, Vertex const & v);
|
||||
|
||||
bool IsFake() const { return m_raw.IsFake(); }
|
||||
bool IsSpecial() const { return m_isSpecial; }
|
||||
|
||||
std::pair<m2::PointD, m2::PointD> ToPair() const;
|
||||
std::pair<m2::PointD, m2::PointD> ToPairRev() const;
|
||||
|
||||
Vertex m_u;
|
||||
Vertex m_v;
|
||||
routing::Edge m_raw;
|
||||
bool m_isSpecial = false;
|
||||
};
|
||||
|
||||
friend std::string DebugPrint(Edge const & edge)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Edge [ ";
|
||||
os << "u: " << DebugPrint(edge.m_u) << ", ";
|
||||
os << "v: " << DebugPrint(edge.m_v) << ", ";
|
||||
os << "raw: " << DebugPrint(edge.m_raw) << ", ";
|
||||
os << "isSpecial: " << edge.m_isSpecial;
|
||||
os << " ]";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
using Links = std::map<Vertex, std::pair<Vertex, Edge>>;
|
||||
|
||||
using RoadGraphEdgesGetter = void (routing::IRoadGraph::*)(geometry::PointWithAltitude const & junction,
|
||||
routing::IRoadGraph::EdgeListT & edges) const;
|
||||
|
||||
bool Init(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM);
|
||||
bool FindPath(Path & path);
|
||||
|
||||
// Returns true if the bearing should be checked for |u|, if the
|
||||
// real passed distance from the source vertex is |distanceM|.
|
||||
bool NeedToCheckBearing(Vertex const & u, double distanceM) const;
|
||||
|
||||
double GetPotential(Vertex const & u) const;
|
||||
|
||||
// Returns true if |u| is located near portal to the next stage.
|
||||
// |pi| is the potential of |u|.
|
||||
bool NearNextStage(Vertex const & u, double pi) const;
|
||||
|
||||
// Returns true if it's possible to move to the next stage from |u|.
|
||||
// |pi| is the potential of |u|.
|
||||
bool MayMoveToNextStage(Vertex const & u, double pi) const;
|
||||
|
||||
// Returns true if |u| is a final vertex and the router may stop now.
|
||||
bool IsFinalVertex(Vertex const & u) const { return u.m_stage == m_pivots.size(); }
|
||||
|
||||
double GetWeight(routing::Edge const & e) const
|
||||
{
|
||||
return mercator::DistanceOnEarth(e.GetStartJunction().GetPoint(), e.GetEndJunction().GetPoint());
|
||||
}
|
||||
|
||||
double GetWeight(Edge const & e) const { return GetWeight(e.m_raw); }
|
||||
|
||||
uint32_t GetReverseBearing(Vertex const & u, Links const & links) const;
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn);
|
||||
|
||||
void GetOutgoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges);
|
||||
void GetIngoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges);
|
||||
|
||||
using EdgeCacheT = std::map<geometry::PointWithAltitude, routing::IRoadGraph::EdgeListT>;
|
||||
void GetEdges(geometry::PointWithAltitude const & u, RoadGraphEdgesGetter getRegular, RoadGraphEdgesGetter getFake,
|
||||
EdgeCacheT & cache, routing::IRoadGraph::EdgeListT & edges);
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachNonFakeEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn);
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachNonFakeClosestEdge(Vertex const & u, FunctionalRoadClass const restriction, Fn && fn);
|
||||
|
||||
template <typename It>
|
||||
size_t FindPrefixLengthToConsume(It b, It const e, double lengthM);
|
||||
|
||||
// Finds all edges that are on (u, v) and have the same direction as
|
||||
// (u, v). Then, computes the fraction of the union of these edges
|
||||
// to the total length of (u, v).
|
||||
template <typename It>
|
||||
double GetCoverage(m2::PointD const & u, m2::PointD const & v, It b, It e);
|
||||
|
||||
// Finds the longest prefix of [b, e) that covers edge (u, v).
|
||||
// Returns the fraction of the coverage to the length of the (u, v).
|
||||
template <typename It>
|
||||
double GetMatchingScore(m2::PointD const & u, m2::PointD const & v, It b, It e);
|
||||
|
||||
// Finds the longest prefix of fake edges of [b, e) that have the
|
||||
// same stage as |stage|. If the prefix exists, passes its bounding
|
||||
// iterator to |fn|.
|
||||
template <typename It, typename Fn>
|
||||
void ForStagePrefix(It b, It e, size_t stage, Fn && fn);
|
||||
|
||||
bool ReconstructPath(std::vector<Edge> & edges, Path & path);
|
||||
|
||||
void FindSingleEdgeApproximation(std::vector<Edge> const & edges, Path & path);
|
||||
|
||||
routing::FeaturesRoadGraph & m_graph;
|
||||
EdgeCacheT m_outgoingCache, m_ingoingCache;
|
||||
RoadInfoGetter & m_roadInfoGetter;
|
||||
|
||||
std::vector<WayPoint> m_points;
|
||||
double m_positiveOffsetM = 0.0;
|
||||
double m_negativeOffsetM = 0.0;
|
||||
std::vector<std::vector<m2::PointD>> m_pivots;
|
||||
geometry::PointWithAltitude m_sourceJunction;
|
||||
geometry::PointWithAltitude m_targetJunction;
|
||||
};
|
||||
} // namespace openlr
|
||||
325
tools/openlr/score_candidate_paths_getter.cpp
Normal file
325
tools/openlr/score_candidate_paths_getter.cpp
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
#include "openlr/score_candidate_paths_getter.hpp"
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/score_candidate_points_getter.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace scpg
|
||||
{
|
||||
int constexpr kNumBuckets = 256;
|
||||
double constexpr kAnglesInBucket = 360.0 / kNumBuckets;
|
||||
|
||||
double ToAngleInDeg(uint32_t angleInBuckets)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(angleInBuckets, 255, ());
|
||||
return math::Clamp(kAnglesInBucket * static_cast<double>(angleInBuckets), 0.0, 360.0);
|
||||
}
|
||||
|
||||
uint32_t BearingInDeg(m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
auto const angle = location::AngleToBearing(math::RadToDeg(ang::AngleTo(a, b)));
|
||||
CHECK(0.0 <= angle && angle <= 360.0, (angle));
|
||||
return angle;
|
||||
}
|
||||
|
||||
double DifferenceInDeg(double a1, double a2)
|
||||
{
|
||||
auto const diff = 180.0 - abs(abs(a1 - a2) - 180.0);
|
||||
CHECK(0.0 <= diff && diff <= 180.0, (diff));
|
||||
return diff;
|
||||
}
|
||||
|
||||
void EdgeSortUniqueByStartAndEndPoints(Graph::EdgeListT & edges)
|
||||
{
|
||||
base::SortUnique(edges,
|
||||
[](Edge const & e1, Edge const & e2)
|
||||
{
|
||||
if (e1.GetStartPoint() != e2.GetStartPoint())
|
||||
return e1.GetStartPoint() < e2.GetStartPoint();
|
||||
return e1.GetEndPoint() < e2.GetEndPoint();
|
||||
}, [](Edge const & e1, Edge const & e2)
|
||||
{ return e1.GetStartPoint() == e2.GetStartPoint() && e1.GetEndPoint() == e2.GetEndPoint(); });
|
||||
}
|
||||
} // namespace scpg
|
||||
|
||||
// ScoreCandidatePathsGetter::Link ----------------------------------------------------------------------
|
||||
Graph::Edge ScoreCandidatePathsGetter::Link::GetStartEdge() const
|
||||
{
|
||||
auto * start = this;
|
||||
while (start->m_parent)
|
||||
start = start->m_parent.get();
|
||||
|
||||
return start->m_edge;
|
||||
}
|
||||
|
||||
bool ScoreCandidatePathsGetter::Link::IsJunctionInPath(geometry::PointWithAltitude const & j) const
|
||||
{
|
||||
for (auto * l = this; l; l = l->m_parent.get())
|
||||
{
|
||||
if (l->m_edge.GetEndJunction().GetPoint() == j.GetPoint())
|
||||
{
|
||||
LOG(LDEBUG, ("A loop detected, skipping..."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ScoreCandidatePathsGetter ----------------------------------------------------------------------------
|
||||
bool ScoreCandidatePathsGetter::GetLineCandidatesForPoints(vector<LocationReferencePoint> const & points,
|
||||
LinearSegmentSource source,
|
||||
vector<ScorePathVec> & lineCandidates)
|
||||
{
|
||||
CHECK_GREATER(points.size(), 1, ());
|
||||
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
if (i != points.size() - 1 && points[i].m_distanceToNextPoint == 0)
|
||||
{
|
||||
LOG(LINFO, ("Distance to next point is zero. Skipping the whole segment"));
|
||||
++m_stats.m_zeroDistToNextPointCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
lineCandidates.emplace_back();
|
||||
auto const isLastPoint = i == points.size() - 1;
|
||||
double const distanceToNextPointM = (isLastPoint ? points[i - 1] : points[i]).m_distanceToNextPoint;
|
||||
|
||||
ScoreEdgeVec edgesCandidates;
|
||||
m_pointsGetter.GetEdgeCandidates(mercator::FromLatLon(points[i].m_latLon), isLastPoint, edgesCandidates);
|
||||
|
||||
GetLineCandidates(points[i], source, isLastPoint, distanceToNextPointM, edgesCandidates, lineCandidates.back());
|
||||
|
||||
if (lineCandidates.back().empty())
|
||||
{
|
||||
LOG(LINFO, ("No candidate lines found for point", points[i].m_latLon, "Giving up"));
|
||||
++m_stats.m_noCandidateFound;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_EQUAL(lineCandidates.size(), points.size(), ());
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScoreCandidatePathsGetter::GetAllSuitablePaths(ScoreEdgeVec const & startLines, LinearSegmentSource source,
|
||||
bool isLastPoint, double bearDistM,
|
||||
FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
double distanceToNextPointM, vector<shared_ptr<Link>> & allPaths)
|
||||
{
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
|
||||
queue<shared_ptr<Link>> q;
|
||||
|
||||
for (auto const & e : startLines)
|
||||
{
|
||||
Score roadScore = 0; // Score based on functional road class and form of way.
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag &&
|
||||
!PassesRestrictionV3(e.m_edge, functionalRoadClass, formOfWay, m_infoGetter, roadScore))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
q.push(make_shared<Link>(nullptr /* parent */, e.m_edge, 0 /* distanceM */, e.m_score, roadScore));
|
||||
}
|
||||
|
||||
// Filling |allPaths| staring from |startLines| which have passed functional road class
|
||||
// and form of way restrictions. All paths in |allPaths| are shorter then |bearDistM| plus
|
||||
// one segment length.
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const u = q.front();
|
||||
q.pop();
|
||||
|
||||
auto const & currentEdge = u->m_edge;
|
||||
auto const currentEdgeLen = EdgeLength(currentEdge);
|
||||
|
||||
// The path from the start of the openlr segment to the finish to the openlr segment should be
|
||||
// much shorter then the distance of any connection of openlr segment.
|
||||
// This condition should be checked because otherwise in rare case |q| may be overfilled.
|
||||
if (u->m_distanceM > distanceToNextPointM)
|
||||
continue;
|
||||
|
||||
if (u->m_distanceM + currentEdgeLen >= bearDistM)
|
||||
{
|
||||
allPaths.emplace_back(std::move(u));
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK_LESS(u->m_distanceM + currentEdgeLen, bearDistM, ());
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(currentEdge.GetEndJunction(), edges);
|
||||
else
|
||||
m_graph.GetIngoingEdges(currentEdge.GetStartJunction(), edges);
|
||||
|
||||
// It's possible that road edges are duplicated a lot of times. It's a error but
|
||||
// a mapper may do that. To prevent a combinatorial explosion while matching
|
||||
// duplicated edges should be removed.
|
||||
scpg::EdgeSortUniqueByStartAndEndPoints(edges);
|
||||
|
||||
for (auto const & e : edges)
|
||||
{
|
||||
CHECK(!e.IsFake(), ());
|
||||
|
||||
if (EdgesAreAlmostEqual(e.GetReverseEdge(), currentEdge))
|
||||
continue;
|
||||
|
||||
CHECK(currentEdge.HasRealPart(), ());
|
||||
|
||||
Score roadScore = 0;
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag &&
|
||||
!PassesRestrictionV3(e, functionalRoadClass, formOfWay, m_infoGetter, roadScore))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (u->IsJunctionInPath(e.GetEndJunction()))
|
||||
continue;
|
||||
|
||||
// Road score for a path is minimum value of score of segments based on functional road class
|
||||
// of the segments and form of way of the segments.
|
||||
q.emplace(
|
||||
make_shared<Link>(u, e, u->m_distanceM + currentEdgeLen, u->m_pointScore, min(roadScore, u->m_minRoadScore)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreCandidatePathsGetter::GetBestCandidatePaths(vector<shared_ptr<Link>> const & allPaths,
|
||||
LinearSegmentSource source, bool isLastPoint,
|
||||
uint32_t requiredBearing, double bearDistM,
|
||||
m2::PointD const & startPoint, ScorePathVec & candidates)
|
||||
{
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
CHECK_LESS_OR_EQUAL(requiredBearing, 255, ());
|
||||
|
||||
multiset<CandidatePath, greater<>> candidatePaths;
|
||||
|
||||
BearingPointsSelector pointsSelector(static_cast<uint32_t>(bearDistM), isLastPoint);
|
||||
for (auto const & link : allPaths)
|
||||
{
|
||||
auto const bearStartPoint = pointsSelector.GetStartPoint(link->GetStartEdge());
|
||||
|
||||
// Number of edges counting from the last one to check bearing on. According to OpenLR spec
|
||||
// we have to check bearing on a point that is no longer than 25 meters traveling down the path.
|
||||
// But sometimes we may skip the best place to stop and generate a candidate. So we check several
|
||||
// edges before the last one to avoid such a situation. Number of iterations is taken
|
||||
// by intuition.
|
||||
// Example:
|
||||
// o -------- o { Partners segment. }
|
||||
// o ------- o --- o { Our candidate. }
|
||||
// ^ 25m
|
||||
// ^ This one may be better than
|
||||
// ^ this one.
|
||||
// So we want to check them all.
|
||||
uint32_t traceBackIterationsLeft = 3;
|
||||
for (auto part = link; part; part = part->m_parent)
|
||||
{
|
||||
if (traceBackIterationsLeft == 0)
|
||||
break;
|
||||
|
||||
--traceBackIterationsLeft;
|
||||
|
||||
// Note. No information about bearing if source == LinearSegmentSource::FromCoordinatesTag.
|
||||
Score bearingScore = 0;
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag)
|
||||
{
|
||||
if (!GetBearingScore(pointsSelector, *part, bearStartPoint, requiredBearing, bearingScore))
|
||||
continue;
|
||||
}
|
||||
candidatePaths.emplace(part, part->m_pointScore, part->m_minRoadScore, bearingScore);
|
||||
}
|
||||
}
|
||||
|
||||
size_t constexpr kMaxCandidates = 7;
|
||||
vector<CandidatePath> paths;
|
||||
copy_n(candidatePaths.begin(), min(static_cast<size_t>(kMaxCandidates), candidatePaths.size()), back_inserter(paths));
|
||||
|
||||
for (auto const & path : paths)
|
||||
{
|
||||
Graph::EdgeVector edges;
|
||||
for (auto * p = path.m_path.get(); p; p = p->m_parent.get())
|
||||
edges.push_back(p->m_edge);
|
||||
|
||||
if (!isLastPoint)
|
||||
reverse(edges.begin(), edges.end());
|
||||
|
||||
candidates.emplace_back(path.GetScore(), std::move(edges));
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreCandidatePathsGetter::GetLineCandidates(openlr::LocationReferencePoint const & p, LinearSegmentSource source,
|
||||
bool isLastPoint, double distanceToNextPointM,
|
||||
ScoreEdgeVec const & edgeCandidates, ScorePathVec & candidates)
|
||||
{
|
||||
double constexpr kDefaultBearDistM = 25.0;
|
||||
double const bearDistM = min(kDefaultBearDistM, distanceToNextPointM);
|
||||
|
||||
ScoreEdgeVec const & startLines = edgeCandidates;
|
||||
LOG(LDEBUG, ("Listing start lines:"));
|
||||
for (auto const & e : startLines)
|
||||
LOG(LDEBUG, (LogAs2GisPath(e.m_edge)));
|
||||
|
||||
auto const startPoint = mercator::FromLatLon(p.m_latLon);
|
||||
|
||||
vector<shared_ptr<Link>> allPaths;
|
||||
GetAllSuitablePaths(startLines, source, isLastPoint, bearDistM, p.m_functionalRoadClass, p.m_formOfWay,
|
||||
distanceToNextPointM, allPaths);
|
||||
|
||||
GetBestCandidatePaths(allPaths, source, isLastPoint, p.m_bearing, bearDistM, startPoint, candidates);
|
||||
// Sorting by increasing order.
|
||||
sort(candidates.begin(), candidates.end(),
|
||||
[](ScorePath const & s1, ScorePath const & s2) { return s1.m_score > s2.m_score; });
|
||||
LOG(LDEBUG, (candidates.size(), "Candidate paths found for point:", p.m_latLon));
|
||||
}
|
||||
|
||||
bool ScoreCandidatePathsGetter::GetBearingScore(BearingPointsSelector const & pointsSelector,
|
||||
ScoreCandidatePathsGetter::Link const & part,
|
||||
m2::PointD const & bearStartPoint, uint32_t requiredBearing,
|
||||
Score & score)
|
||||
{
|
||||
auto const bearEndPoint = pointsSelector.GetEndPoint(part.m_edge, part.m_distanceM);
|
||||
|
||||
auto const bearingDeg = scpg::BearingInDeg(bearStartPoint, bearEndPoint);
|
||||
double const requiredBearingDeg = scpg::ToAngleInDeg(requiredBearing);
|
||||
double const angleDeviationDeg = scpg::DifferenceInDeg(bearingDeg, requiredBearingDeg);
|
||||
|
||||
// If the bearing according to osm segments (|bearingDeg|) is significantly different
|
||||
// from the bearing set in openlr (|requiredBearingDeg|) the candidate should be skipped.
|
||||
double constexpr kMinAngleDeviationDeg = 50.0;
|
||||
if (angleDeviationDeg > kMinAngleDeviationDeg)
|
||||
return false;
|
||||
|
||||
double constexpr kMaxScoreForBearing = 60.0;
|
||||
double constexpr kAngleDeviationFactor = 1.0 / 4.3;
|
||||
score = static_cast<Score>(kMaxScoreForBearing / (1.0 + angleDeviationDeg * kAngleDeviationFactor));
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace openlr
|
||||
119
tools/openlr/score_candidate_paths_getter.hpp
Normal file
119
tools/openlr/score_candidate_paths_getter.hpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class ScoreCandidatePointsGetter;
|
||||
|
||||
class ScoreCandidatePathsGetter
|
||||
{
|
||||
public:
|
||||
ScoreCandidatePathsGetter(ScoreCandidatePointsGetter & pointsGetter, Graph & graph, RoadInfoGetter & infoGetter,
|
||||
v2::Stats & stat)
|
||||
: m_pointsGetter(pointsGetter)
|
||||
, m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stats(stat)
|
||||
{}
|
||||
|
||||
bool GetLineCandidatesForPoints(std::vector<LocationReferencePoint> const & points, LinearSegmentSource source,
|
||||
std::vector<ScorePathVec> & lineCandidates);
|
||||
|
||||
private:
|
||||
struct Link
|
||||
{
|
||||
Link(std::shared_ptr<Link> const & parent, Graph::Edge const & edge, double distanceM, Score pointScore,
|
||||
Score rfcScore)
|
||||
: m_parent(parent)
|
||||
, m_edge(edge)
|
||||
, m_distanceM(distanceM)
|
||||
, m_pointScore(pointScore)
|
||||
, m_minRoadScore(rfcScore)
|
||||
{
|
||||
CHECK(!edge.IsFake(), ("Edge should not be fake:", edge));
|
||||
}
|
||||
|
||||
bool IsJunctionInPath(geometry::PointWithAltitude const & j) const;
|
||||
|
||||
Graph::Edge GetStartEdge() const;
|
||||
|
||||
std::shared_ptr<Link> const m_parent;
|
||||
Graph::Edge const m_edge;
|
||||
double const m_distanceM;
|
||||
Score const m_pointScore;
|
||||
// Minimum score of segments of the path going along |m_parent| based on functional road class
|
||||
// and form of way.
|
||||
Score const m_minRoadScore;
|
||||
};
|
||||
|
||||
struct CandidatePath
|
||||
{
|
||||
CandidatePath() = default;
|
||||
|
||||
CandidatePath(std::shared_ptr<Link> const path, Score pointScore, Score rfcScore, Score bearingScore)
|
||||
: m_path(path)
|
||||
, m_pointScore(pointScore)
|
||||
, m_roadScore(rfcScore)
|
||||
, m_bearingScore(bearingScore)
|
||||
{}
|
||||
|
||||
bool operator>(CandidatePath const & o) const { return GetScore() > o.GetScore(); }
|
||||
|
||||
Score GetScore() const { return m_pointScore + m_roadScore + m_bearingScore; }
|
||||
|
||||
std::shared_ptr<Link> m_path = nullptr;
|
||||
Score m_pointScore = 0;
|
||||
Score m_roadScore = 0;
|
||||
Score m_bearingScore = 0;
|
||||
};
|
||||
|
||||
// Note: In all methods below if |isLastPoint| is true than algorithm should
|
||||
// calculate all parameters (such as bearing, distance to next point, etc.)
|
||||
// relative to the last point.
|
||||
// o ----> o ----> o <---- o.
|
||||
// 1 2 3 4
|
||||
// ^ isLastPoint = true.
|
||||
// To calculate bearing for points 1 to 3 one have to go beardist from
|
||||
// previous point to the next one (eg. from 1 to 2 and from 2 to 3).
|
||||
// For point 4 one have to go from 4 to 3 reversing directions. And
|
||||
// distance-to-next point is taken from point 3. You can learn more in
|
||||
// TomTom OpenLR spec.
|
||||
|
||||
/// \brief Fills |allPaths| with paths near start or finish point starting from |startLines|.
|
||||
/// To extract a path from |allPaths| a item from |allPaths| should be taken,
|
||||
/// then should be taken the member |m_parent| of the item and so on till the beginning.
|
||||
void GetAllSuitablePaths(ScoreEdgeVec const & startLines, LinearSegmentSource source, bool isLastPoint,
|
||||
double bearDistM, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
double distanceToNextPointM, std::vector<std::shared_ptr<Link>> & allPaths);
|
||||
|
||||
void GetBestCandidatePaths(std::vector<std::shared_ptr<Link>> const & allPaths, LinearSegmentSource source,
|
||||
bool isLastPoint, uint32_t requiredBearing, double bearDistM,
|
||||
m2::PointD const & startPoint, ScorePathVec & candidates);
|
||||
|
||||
void GetLineCandidates(openlr::LocationReferencePoint const & p, LinearSegmentSource source, bool isLastPoint,
|
||||
double distanceToNextPointM, ScoreEdgeVec const & edgeCandidates, ScorePathVec & candidates);
|
||||
|
||||
bool GetBearingScore(BearingPointsSelector const & pointsSelector, ScoreCandidatePathsGetter::Link const & part,
|
||||
m2::PointD const & bearStartPoint, uint32_t requiredBearing, Score & score);
|
||||
|
||||
ScoreCandidatePointsGetter & m_pointsGetter;
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stats;
|
||||
};
|
||||
} // namespace openlr
|
||||
137
tools/openlr/score_candidate_points_getter.cpp
Normal file
137
tools/openlr/score_candidate_points_getter.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#include "openlr/score_candidate_points_getter.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
#include "routing/routing_helpers.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/feature_decl.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
using namespace routing;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Ends of segments and intermediate points of segments are considered only within this radius.
|
||||
double const kRadius = 30.0;
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
void ScoreCandidatePointsGetter::GetJunctionPointCandidates(m2::PointD const & p, bool isLastPoint,
|
||||
ScoreEdgeVec & edgeCandidates)
|
||||
{
|
||||
ScorePointVec pointCandidates;
|
||||
auto const selectCandidates = [&p, &pointCandidates, this](FeatureType & ft)
|
||||
{
|
||||
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
if (ft.GetGeomType() != feature::GeomType::Line || !routing::IsRoad(feature::TypesHolder(ft)))
|
||||
return;
|
||||
|
||||
ft.ForEachPoint([&p, &pointCandidates, this](m2::PointD const & candidate)
|
||||
{
|
||||
if (mercator::DistanceOnEarth(p, candidate) < kRadius)
|
||||
pointCandidates.emplace_back(GetScoreByDistance(p, candidate), candidate);
|
||||
}, scales::GetUpperScale());
|
||||
};
|
||||
|
||||
m_dataSource.ForEachInRect(selectCandidates, mercator::RectByCenterXYAndSizeInMeters(p, kRadius),
|
||||
scales::GetUpperScale());
|
||||
|
||||
base::SortUnique(pointCandidates);
|
||||
std::reverse(pointCandidates.begin(), pointCandidates.end());
|
||||
|
||||
pointCandidates.resize(std::min(m_maxJunctionCandidates, pointCandidates.size()));
|
||||
|
||||
for (auto const & pc : pointCandidates)
|
||||
{
|
||||
Graph::EdgeListT edges;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(geometry::PointWithAltitude(pc.m_point, 0 /* altitude */), edges);
|
||||
else
|
||||
m_graph.GetIngoingEdges(geometry::PointWithAltitude(pc.m_point, 0 /* altitude */), edges);
|
||||
|
||||
for (auto const & e : edges)
|
||||
edgeCandidates.emplace_back(pc.m_score, e);
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreCandidatePointsGetter::EnrichWithProjectionPoints(m2::PointD const & p, ScoreEdgeVec & edgeCandidates)
|
||||
{
|
||||
m_graph.ResetFakes();
|
||||
|
||||
std::vector<std::pair<Graph::Edge, geometry::PointWithAltitude>> vicinities;
|
||||
m_graph.FindClosestEdges(p, static_cast<uint32_t>(m_maxProjectionCandidates), vicinities);
|
||||
for (auto const & v : vicinities)
|
||||
{
|
||||
auto const & edge = v.first;
|
||||
auto const & proj = v.second;
|
||||
|
||||
CHECK(edge.HasRealPart(), ());
|
||||
CHECK(!edge.IsFake(), ());
|
||||
|
||||
if (mercator::DistanceOnEarth(p, proj.GetPoint()) >= kRadius)
|
||||
continue;
|
||||
|
||||
edgeCandidates.emplace_back(GetScoreByDistance(p, proj.GetPoint()), edge);
|
||||
}
|
||||
}
|
||||
|
||||
bool ScoreCandidatePointsGetter::IsJunction(m2::PointD const & p)
|
||||
{
|
||||
Graph::EdgeListT outgoing;
|
||||
m_graph.GetRegularOutgoingEdges(geometry::PointWithAltitude(p, 0 /* altitude */), outgoing);
|
||||
|
||||
Graph::EdgeListT ingoing;
|
||||
m_graph.GetRegularIngoingEdges(geometry::PointWithAltitude(p, 0 /* altitude */), ingoing);
|
||||
|
||||
// Note. At mwm borders the size of |ids| may be bigger than two in case of straight
|
||||
// road because of road feature duplication at borders.
|
||||
std::set<std::pair<uint32_t, uint32_t>> ids;
|
||||
for (auto const & e : outgoing)
|
||||
ids.insert(std::make_pair(e.GetFeatureId().m_index, e.GetSegId()));
|
||||
|
||||
for (auto const & e : ingoing)
|
||||
ids.insert(std::make_pair(e.GetFeatureId().m_index, e.GetSegId()));
|
||||
|
||||
// Size of |ids| is number of different pairs of (feature id, segment id) starting from
|
||||
// |p| plus going to |p|. The size 0, 1 or 2 is considered |p| is not a junction of roads.
|
||||
// If the size is 3 or more it means |p| is an intersection of 3 or more roads.
|
||||
return ids.size() >= 3;
|
||||
}
|
||||
|
||||
Score ScoreCandidatePointsGetter::GetScoreByDistance(m2::PointD const & point, m2::PointD const & candidate)
|
||||
{
|
||||
// Maximum possible score for the distance between an openlr segment ends and an osm segments.
|
||||
Score constexpr kMaxScoreForDist = 70;
|
||||
// If the distance between an openlr segments end and an osm segments end is less or equal
|
||||
// |kMaxScoreDistM| the point gets |kMaxScoreForDist| score.
|
||||
double constexpr kMaxScoreDistM = 5.0;
|
||||
// According to the standard openlr edge should be started from a junction. Despite the fact
|
||||
// that openlr and osm are based on different graphs, the score of junction should be increased.
|
||||
double const junctionFactor = IsJunction(candidate) ? 1.1 : 1.0;
|
||||
|
||||
double const distM = mercator::DistanceOnEarth(point, candidate);
|
||||
double const score = distM <= kMaxScoreDistM
|
||||
? kMaxScoreForDist * junctionFactor
|
||||
: static_cast<double>(kMaxScoreForDist) * junctionFactor / (1.0 + distM - kMaxScoreDistM);
|
||||
|
||||
CHECK_GREATER_OR_EQUAL(score, 0.0, ());
|
||||
CHECK_LESS_OR_EQUAL(score, static_cast<double>(kMaxScoreForDist) * junctionFactor, ());
|
||||
return static_cast<Score>(score);
|
||||
}
|
||||
} // namespace openlr
|
||||
48
tools/openlr/score_candidate_points_getter.hpp
Normal file
48
tools/openlr/score_candidate_points_getter.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class ScoreCandidatePointsGetter
|
||||
{
|
||||
public:
|
||||
ScoreCandidatePointsGetter(size_t maxJunctionCandidates, size_t maxProjectionCandidates,
|
||||
DataSource const & dataSource, Graph & graph)
|
||||
: m_maxJunctionCandidates(maxJunctionCandidates)
|
||||
, m_maxProjectionCandidates(maxProjectionCandidates)
|
||||
, m_dataSource(dataSource)
|
||||
, m_graph(graph)
|
||||
{}
|
||||
|
||||
void GetEdgeCandidates(m2::PointD const & p, bool isLastPoint, ScoreEdgeVec & edges)
|
||||
{
|
||||
GetJunctionPointCandidates(p, isLastPoint, edges);
|
||||
EnrichWithProjectionPoints(p, edges);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetJunctionPointCandidates(m2::PointD const & p, bool isLastPoint, ScoreEdgeVec & edgeCandidates);
|
||||
void EnrichWithProjectionPoints(m2::PointD const & p, ScoreEdgeVec & edgeCandidates);
|
||||
|
||||
/// \returns true if |p| is a junction and false otherwise.
|
||||
bool IsJunction(m2::PointD const & p);
|
||||
Score GetScoreByDistance(m2::PointD const & point, m2::PointD const & candidate);
|
||||
|
||||
size_t const m_maxJunctionCandidates;
|
||||
size_t const m_maxProjectionCandidates;
|
||||
|
||||
DataSource const & m_dataSource;
|
||||
Graph & m_graph;
|
||||
};
|
||||
} // namespace openlr
|
||||
349
tools/openlr/score_paths_connector.cpp
Normal file
349
tools/openlr/score_paths_connector.cpp
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#include "openlr/score_paths_connector.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/stl_iterator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class EdgeContainer
|
||||
{
|
||||
public:
|
||||
explicit EdgeContainer(Graph const & graph) : m_graph(graph) {}
|
||||
|
||||
void ProcessEdge(Graph::Edge const & edge)
|
||||
{
|
||||
CHECK(!edge.IsFake(), ());
|
||||
|
||||
feature::TypesHolder types;
|
||||
m_graph.GetFeatureTypes(edge.GetFeatureId(), types);
|
||||
|
||||
ftypes::HighwayClass const hwClass = ftypes::GetHighwayClass(types);
|
||||
if (m_minHwClass == ftypes::HighwayClass::Undefined)
|
||||
{
|
||||
m_minHwClass = hwClass;
|
||||
m_maxHwClass = m_minHwClass;
|
||||
m_oneWay.m_field = ftypes::IsOneWayChecker::Instance()(types);
|
||||
m_roundabout.m_field = ftypes::IsRoundAboutChecker::Instance()(types);
|
||||
m_link.m_field = ftypes::IsLinkChecker::Instance()(types);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(hwClass != ftypes::HighwayClass::Undefined, (edge));
|
||||
|
||||
m_minHwClass = static_cast<ftypes::HighwayClass>(min(base::Underlying(m_minHwClass), base::Underlying(hwClass)));
|
||||
m_maxHwClass = static_cast<ftypes::HighwayClass>(max(base::Underlying(m_maxHwClass), base::Underlying(hwClass)));
|
||||
|
||||
if (m_oneWay.m_allEqual && m_oneWay.m_field != ftypes::IsOneWayChecker::Instance()(types))
|
||||
m_oneWay.m_allEqual = false;
|
||||
|
||||
if (m_roundabout.m_allEqual && m_roundabout.m_field != ftypes::IsRoundAboutChecker::Instance()(types))
|
||||
m_roundabout.m_allEqual = false;
|
||||
|
||||
if (m_link.m_allEqual && m_link.m_field != ftypes::IsLinkChecker::Instance()(types))
|
||||
m_link.m_allEqual = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t GetHighwayClassDiff() const
|
||||
{
|
||||
CHECK_NOT_EQUAL(m_minHwClass, ftypes::HighwayClass::Undefined, ());
|
||||
CHECK_GREATER_OR_EQUAL(m_maxHwClass, m_minHwClass, ());
|
||||
|
||||
uint8_t const hwClassDiff = static_cast<uint8_t>(m_maxHwClass) - static_cast<uint8_t>(m_minHwClass);
|
||||
return hwClassDiff;
|
||||
}
|
||||
|
||||
bool AreAllOneWaysEqual() const { return m_oneWay.m_allEqual; }
|
||||
bool AreAllRoundaboutEqual() const { return m_roundabout.m_allEqual; }
|
||||
bool AreAllLinksEqual() const { return m_link.m_allEqual; }
|
||||
|
||||
private:
|
||||
struct Field
|
||||
{
|
||||
bool m_field = false;
|
||||
bool m_allEqual = true;
|
||||
};
|
||||
|
||||
Graph const & m_graph;
|
||||
|
||||
ftypes::HighwayClass m_minHwClass = ftypes::HighwayClass::Undefined;
|
||||
ftypes::HighwayClass m_maxHwClass = ftypes::HighwayClass::Undefined;
|
||||
Field m_oneWay;
|
||||
Field m_roundabout;
|
||||
Field m_link;
|
||||
};
|
||||
|
||||
/// \returns true if |path| may be used as a candidate. In that case |lenScore| is filled
|
||||
/// with score of this candidate based on length. The closer length of the |path| to
|
||||
/// |distanceToNextPoint| the more score.
|
||||
bool ValidatePathByLength(Graph::EdgeVector const & path, double distanceToNextPoint, LinearSegmentSource source,
|
||||
Score & lenScore)
|
||||
{
|
||||
CHECK(!path.empty(), ());
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
|
||||
Score const kMaxScoreForRouteLen = 110;
|
||||
|
||||
double pathLen = 0.0;
|
||||
for (auto const & e : path)
|
||||
pathLen += EdgeLength(e);
|
||||
|
||||
// 0 <= |pathDiffRatio| <= 1. The more pathDiffRatio the closer |distanceToNextPoint| and |pathLen|.
|
||||
double const pathDiffRatio = 1.0 - abs(distanceToNextPoint - pathLen) / max(distanceToNextPoint, pathLen);
|
||||
|
||||
bool const shortPath = path.size() <= 2;
|
||||
double const kMinValidPathDiffRation = source == LinearSegmentSource::FromLocationReferenceTag ? 0.6 : 0.25;
|
||||
if (pathDiffRatio <= kMinValidPathDiffRation && !shortPath)
|
||||
return false;
|
||||
|
||||
lenScore = static_cast<Score>(kMaxScoreForRouteLen * (pathDiffRatio - kMinValidPathDiffRation) /
|
||||
(1.0 - kMinValidPathDiffRation));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AreEdgesEqualWithoutAltitude(Graph::Edge const & e1, Graph::Edge const & e2)
|
||||
{
|
||||
return make_tuple(e1.GetType(), e1.GetFeatureId(), e1.IsForward(), e1.GetSegId(), e1.GetStartPoint(),
|
||||
e1.GetEndPoint()) == make_tuple(e2.GetType(), e2.GetFeatureId(), e2.IsForward(), e2.GetSegId(),
|
||||
e2.GetStartPoint(), e2.GetEndPoint());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ScorePathsConnector::ScorePathsConnector(Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat)
|
||||
: m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stat(stat)
|
||||
{}
|
||||
|
||||
bool ScorePathsConnector::FindBestPath(vector<LocationReferencePoint> const & points,
|
||||
vector<vector<ScorePath>> const & lineCandidates, LinearSegmentSource source,
|
||||
vector<Graph::EdgeVector> & resultPath)
|
||||
{
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
CHECK_GREATER_OR_EQUAL(points.size(), 2, ());
|
||||
|
||||
resultPath.resize(points.size() - 1);
|
||||
|
||||
for (size_t i = 1; i < points.size(); ++i)
|
||||
{
|
||||
// @TODO It's possible that size of |points| is more then two. In that case two or more edges
|
||||
// should be approximated with routes. If so only |toCandidates| which may be reached from
|
||||
// |fromCandidates| should be used for the next edge.
|
||||
|
||||
auto const & point = points[i - 1];
|
||||
auto const distanceToNextPoint = static_cast<double>(point.m_distanceToNextPoint);
|
||||
auto const & fromCandidates = lineCandidates[i - 1];
|
||||
auto const & toCandidates = lineCandidates[i];
|
||||
auto & resultPathPart = resultPath[i - 1];
|
||||
|
||||
vector<ScorePath> result;
|
||||
for (size_t fromInd = 0; fromInd < fromCandidates.size(); ++fromInd)
|
||||
{
|
||||
for (size_t toInd = 0; toInd < toCandidates.size(); ++toInd)
|
||||
{
|
||||
Graph::EdgeVector path;
|
||||
if (!ConnectAdjacentCandidateLines(fromCandidates[fromInd].m_path, toCandidates[toInd].m_path, source,
|
||||
point.m_lfrcnp, distanceToNextPoint, path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Score pathLenScore = 0;
|
||||
if (!ValidatePathByLength(path, distanceToNextPoint, source, pathLenScore))
|
||||
continue;
|
||||
|
||||
auto const score =
|
||||
pathLenScore + GetScoreForUniformity(path) + fromCandidates[fromInd].m_score + toCandidates[toInd].m_score;
|
||||
result.emplace_back(score, std::move(path));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const & p : result)
|
||||
CHECK(!p.m_path.empty(), ());
|
||||
|
||||
if (result.empty())
|
||||
{
|
||||
LOG(LINFO, ("No shortest path found"));
|
||||
++m_stat.m_noShortestPathFound;
|
||||
resultPathPart.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const it = max_element(result.cbegin(), result.cend(),
|
||||
[](ScorePath const & o1, ScorePath const & o2) { return o1.m_score < o2.m_score; });
|
||||
|
||||
// Note. In case of source == LinearSegmentSource::FromCoordinatesTag there is less
|
||||
// information about a openlr segment so less score is collected.
|
||||
Score const kMinValidScore = source == LinearSegmentSource::FromLocationReferenceTag ? 240 : 165;
|
||||
if (it->m_score < kMinValidScore)
|
||||
{
|
||||
LOG(LINFO, ("The shortest path found but it is not good. The best score:", it->m_score));
|
||||
++m_stat.m_notEnoughScore;
|
||||
return false;
|
||||
}
|
||||
|
||||
resultPathPart = it->m_path;
|
||||
LOG(LDEBUG, ("Best score:", it->m_score, "resultPathPart.size():", resultPathPart.size()));
|
||||
}
|
||||
|
||||
CHECK_EQUAL(resultPath.size(), points.size() - 1, ());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScorePathsConnector::FindShortestPath(Graph::Edge const & from, Graph::Edge const & to, LinearSegmentSource source,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, uint32_t maxPathLength,
|
||||
Graph::EdgeVector & path)
|
||||
{
|
||||
double constexpr kLengthToleranceFactor = 1.1;
|
||||
uint32_t constexpr kMinLengthTolerance = 20;
|
||||
uint32_t const lengthToleranceM =
|
||||
max(static_cast<uint32_t>(kLengthToleranceFactor * maxPathLength), kMinLengthTolerance);
|
||||
|
||||
struct State
|
||||
{
|
||||
State(Graph::Edge const & e, uint32_t const s) : m_edge(e), m_score(s) {}
|
||||
|
||||
bool operator>(State const & o) const { return tie(m_score, m_edge) > tie(o.m_score, o.m_edge); }
|
||||
|
||||
Graph::Edge m_edge;
|
||||
uint32_t m_score;
|
||||
};
|
||||
|
||||
CHECK(from.HasRealPart() && to.HasRealPart(), ());
|
||||
|
||||
priority_queue<State, vector<State>, greater<>> q;
|
||||
map<Graph::Edge, double> scores;
|
||||
map<Graph::Edge, Graph::Edge> links;
|
||||
|
||||
q.emplace(from, 0);
|
||||
scores[from] = 0;
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const state = q.top();
|
||||
q.pop();
|
||||
|
||||
auto const & stateEdge = state.m_edge;
|
||||
// TODO(mgsergio): Unify names: use either score or distance.
|
||||
auto const stateScore = state.m_score;
|
||||
|
||||
if (stateScore > maxPathLength + lengthToleranceM)
|
||||
continue;
|
||||
|
||||
if (stateScore > scores[stateEdge])
|
||||
continue;
|
||||
|
||||
if (AreEdgesEqualWithoutAltitude(stateEdge, to))
|
||||
{
|
||||
for (auto edge = stateEdge; edge != from; edge = links[edge])
|
||||
path.emplace_back(edge);
|
||||
path.emplace_back(from);
|
||||
reverse(begin(path), end(path));
|
||||
return true;
|
||||
}
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
m_graph.GetOutgoingEdges(stateEdge.GetEndJunction(), edges);
|
||||
for (auto const & edge : edges)
|
||||
{
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag &&
|
||||
!ConformLfrcnpV3(edge, lowestFrcToNextPoint, m_infoGetter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK(!stateEdge.IsFake(), ());
|
||||
CHECK(!edge.IsFake(), ());
|
||||
|
||||
auto const it = scores.find(edge);
|
||||
auto const eScore = stateScore + EdgeLength(edge);
|
||||
if (it == end(scores) || it->second > eScore)
|
||||
{
|
||||
scores[edge] = eScore;
|
||||
links[edge] = stateEdge;
|
||||
q.emplace(edge, eScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScorePathsConnector::ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
LinearSegmentSource source,
|
||||
FunctionalRoadClass lowestFrcToNextPoint,
|
||||
double distanceToNextPoint, Graph::EdgeVector & resultPath)
|
||||
{
|
||||
CHECK(!to.empty(), ());
|
||||
|
||||
if (auto const skip = PathOverlappingLen(from, to))
|
||||
{
|
||||
if (skip == -1)
|
||||
return false;
|
||||
|
||||
resultPath.insert(resultPath.end(), from.cbegin(), from.cend());
|
||||
resultPath.insert(resultPath.end(), to.cbegin() + skip, to.cend());
|
||||
return true;
|
||||
}
|
||||
|
||||
CHECK_NOT_EQUAL(from, to, ());
|
||||
|
||||
Graph::EdgeVector shortestPath;
|
||||
auto const found = FindShortestPath(from.back(), to.front(), source, lowestFrcToNextPoint,
|
||||
static_cast<uint32_t>(ceil(distanceToNextPoint)), shortestPath);
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
CHECK_EQUAL(from.back(), shortestPath.front(), ());
|
||||
resultPath.insert(resultPath.end(), from.cbegin(), prev(from.cend()));
|
||||
resultPath.insert(resultPath.end(), shortestPath.cbegin(), shortestPath.cend());
|
||||
|
||||
CHECK(AreEdgesEqualWithoutAltitude(to.front(), shortestPath.back()), (to.front(), shortestPath.back()));
|
||||
resultPath.insert(resultPath.end(), next(to.begin()), to.end());
|
||||
|
||||
return !resultPath.empty();
|
||||
}
|
||||
|
||||
Score ScorePathsConnector::GetScoreForUniformity(Graph::EdgeVector const & path)
|
||||
{
|
||||
EdgeContainer edgeContainer(m_graph);
|
||||
for (auto const & edge : path)
|
||||
edgeContainer.ProcessEdge(edge);
|
||||
|
||||
auto const hwClassDiff = edgeContainer.GetHighwayClassDiff();
|
||||
Score constexpr kScoreForTheSameHwClass = 40;
|
||||
Score constexpr kScoreForNeighboringHwClasses = 15;
|
||||
Score const hwClassScore = hwClassDiff == 0 ? kScoreForTheSameHwClass
|
||||
: hwClassDiff == 1 ? kScoreForNeighboringHwClasses
|
||||
: 0;
|
||||
|
||||
Score constexpr kScoreForOneWayOnly = 17;
|
||||
Score constexpr kScoreForRoundaboutOnly = 18;
|
||||
Score constexpr kScoreForLinkOnly = 10;
|
||||
Score const allEqualScore = (edgeContainer.AreAllOneWaysEqual() ? kScoreForOneWayOnly : 0) +
|
||||
(edgeContainer.AreAllRoundaboutEqual() ? kScoreForRoundaboutOnly : 0) +
|
||||
(edgeContainer.AreAllLinksEqual() ? kScoreForLinkOnly : 0);
|
||||
|
||||
return hwClassScore + allEqualScore;
|
||||
}
|
||||
} // namespace openlr
|
||||
41
tools/openlr/score_paths_connector.hpp
Normal file
41
tools/openlr/score_paths_connector.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class ScorePathsConnector
|
||||
{
|
||||
public:
|
||||
ScorePathsConnector(Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat);
|
||||
|
||||
/// \brief Connects |lineCandidates| and fills |resultPath| with the path with maximum score
|
||||
/// if there's a good enough.
|
||||
/// \returns true if the best path is found and false otherwise.
|
||||
bool FindBestPath(std::vector<LocationReferencePoint> const & points,
|
||||
std::vector<std::vector<ScorePath>> const & lineCandidates, LinearSegmentSource source,
|
||||
std::vector<Graph::EdgeVector> & resultPath);
|
||||
|
||||
private:
|
||||
bool FindShortestPath(Graph::Edge const & from, Graph::Edge const & to, LinearSegmentSource source,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, uint32_t maxPathLength, Graph::EdgeVector & path);
|
||||
|
||||
bool ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
LinearSegmentSource source, FunctionalRoadClass lowestFrcToNextPoint,
|
||||
double distanceToNextPoint, Graph::EdgeVector & resultPath);
|
||||
|
||||
Score GetScoreForUniformity(Graph::EdgeVector const & path);
|
||||
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stat;
|
||||
};
|
||||
} // namespace openlr
|
||||
52
tools/openlr/score_types.hpp
Normal file
52
tools/openlr/score_types.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
using Edge = routing::Edge;
|
||||
using EdgeVector = routing::RoadGraphBase::EdgeVector;
|
||||
|
||||
using Score = uint32_t;
|
||||
|
||||
struct ScorePoint
|
||||
{
|
||||
ScorePoint() = default;
|
||||
ScorePoint(Score score, m2::PointD const & point) : m_score(score), m_point(point) {}
|
||||
|
||||
bool operator<(ScorePoint const & o) const { return std::tie(m_score, m_point) < std::tie(o.m_score, o.m_point); }
|
||||
|
||||
bool operator==(ScorePoint const & o) const { return m_score == o.m_score && m_point == o.m_point; }
|
||||
|
||||
Score m_score = 0;
|
||||
m2::PointD m_point;
|
||||
};
|
||||
|
||||
using ScorePointVec = std::vector<ScorePoint>;
|
||||
|
||||
struct ScoreEdge
|
||||
{
|
||||
ScoreEdge(Score score, Edge const & edge) : m_score(score), m_edge(edge) {}
|
||||
|
||||
Score m_score;
|
||||
Edge m_edge;
|
||||
};
|
||||
|
||||
using ScoreEdgeVec = std::vector<ScoreEdge>;
|
||||
|
||||
struct ScorePath
|
||||
{
|
||||
ScorePath(Score score, EdgeVector && path) : m_score(score), m_path(std::move(path)) {}
|
||||
|
||||
Score m_score;
|
||||
EdgeVector m_path;
|
||||
};
|
||||
|
||||
using ScorePathVec = std::vector<ScorePath>;
|
||||
} // namespace openlr
|
||||
48
tools/openlr/stats.hpp
Normal file
48
tools/openlr/stats.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/cache_line_size.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
struct alignas(kCacheLineSize) Stats
|
||||
{
|
||||
void Add(Stats const & s)
|
||||
{
|
||||
m_routesHandled += s.m_routesHandled;
|
||||
m_routesFailed += s.m_routesFailed;
|
||||
m_noCandidateFound += s.m_noCandidateFound;
|
||||
m_noShortestPathFound += s.m_noShortestPathFound;
|
||||
m_notEnoughScore += s.m_notEnoughScore;
|
||||
m_wrongOffsets += s.m_wrongOffsets;
|
||||
m_zeroDistToNextPointCount += s.m_zeroDistToNextPointCount;
|
||||
}
|
||||
|
||||
void Report() const
|
||||
{
|
||||
LOG(LINFO, ("Total routes handled:", m_routesHandled));
|
||||
LOG(LINFO, ("Failed:", m_routesFailed));
|
||||
LOG(LINFO, ("No candidate lines:", m_noCandidateFound));
|
||||
LOG(LINFO, ("Wrong distance to next point:", m_zeroDistToNextPointCount));
|
||||
LOG(LINFO, ("Not enough score for shortest path:", m_notEnoughScore));
|
||||
LOG(LINFO, ("Wrong offsets:", m_wrongOffsets));
|
||||
LOG(LINFO, ("No shortest path:", m_noShortestPathFound));
|
||||
}
|
||||
|
||||
uint32_t m_routesHandled = 0;
|
||||
uint32_t m_routesFailed = 0;
|
||||
uint32_t m_noCandidateFound = 0;
|
||||
uint32_t m_noShortestPathFound = 0;
|
||||
uint32_t m_notEnoughScore = 0;
|
||||
uint32_t m_wrongOffsets = 0;
|
||||
// Number of zeroed distance-to-next point values in the input.
|
||||
uint32_t m_zeroDistToNextPointCount = 0;
|
||||
};
|
||||
} // namespace v2
|
||||
} // namespace openlr
|
||||
26
tools/openlr/way_point.hpp
Normal file
26
tools/openlr/way_point.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
struct WayPoint final
|
||||
{
|
||||
explicit WayPoint(openlr::LocationReferencePoint const & lrp)
|
||||
: m_point(mercator::FromLatLon(lrp.m_latLon))
|
||||
, m_distanceToNextPointM(lrp.m_distanceToNextPoint)
|
||||
, m_bearing(lrp.m_bearing)
|
||||
, m_lfrcnp(lrp.m_functionalRoadClass)
|
||||
{}
|
||||
|
||||
m2::PointD m_point;
|
||||
double m_distanceToNextPointM = 0.0;
|
||||
uint8_t m_bearing = 0;
|
||||
openlr::FunctionalRoadClass m_lfrcnp = openlr::FunctionalRoadClass::NotAValue;
|
||||
};
|
||||
} // namespace openlr
|
||||
Loading…
Add table
Add a link
Reference in a new issue