Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View 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)

View file

@ -0,0 +1,8 @@
#pragma once
#include <cstddef>
namespace openlr
{
size_t constexpr kCacheLineSize = 64;
} // namespace openlr

View 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

View 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

View 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

View 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

View 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

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

View 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

View 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

View file

@ -0,0 +1,5 @@
project(openlr_match_quality)
if (NOT SKIP_QT_GUI)
add_subdirectory(openlr_assessment_tool)
endif()

View file

@ -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
)

View file

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

View file

@ -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();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Data file:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View 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

View 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

View 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

View 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

View 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
)

View 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;
}

View 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
)

View 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

View 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

View 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

View 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

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

View 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

View 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

View 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

View 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

View 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

View 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

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

View 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