New cpp folder structure

Signed-off-by: Alexander Borsuk <me@alex.bio>
This commit is contained in:
Alexander Borsuk
2025-07-17 22:35:52 +03:00
committed by Konstantin Pastbin
parent c9cbb64f12
commit 76ffc99abd
2390 changed files with 345 additions and 339 deletions

11
tools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,11 @@
add_subdirectory(openlr)
add_subdirectory(poly_borders)
add_subdirectory(track_analyzing)
omim_add_tool_subdirectory(feature_list)
omim_add_tool_subdirectory(topography_generator)
omim_add_tool_subdirectory(track_generator)
if (NOT SKIP_QT_GUI)
omim_add_tool_subdirectory(skin_generator)
endif()

View File

@@ -0,0 +1,13 @@
project(feature_list_generator)
set(SRC feature_list.cpp)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
mwm_diff
generator
search
platform
indexer
)

View File

@@ -0,0 +1,390 @@
#include "generator/utils.hpp"
#include "search/search_quality/helpers.hpp"
#include "search/categories_cache.hpp"
#include "search/engine.hpp"
#include "search/locality_finder.hpp"
#include "search/reverse_geocoder.hpp"
#include "storage/country_info_getter.hpp"
#include "storage/storage.hpp"
#include "storage/storage_defines.hpp"
#include "indexer/classificator.hpp"
#include "indexer/classificator_loader.hpp"
#include "indexer/data_source.hpp"
#include "indexer/feature.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/map_object.hpp"
#include "indexer/map_style_reader.hpp"
#include "platform/platform_tests_support/helpers.hpp"
#include "platform/local_country_file_utils.hpp"
#include "platform/platform.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <iostream>
#include <limits>
#include <map>
#include <sstream>
#include <vector>
using namespace std;
class ClosestPoint
{
m2::PointD const & m_center;
m2::PointD m_best;
double m_distance = numeric_limits<double>::max();
public:
explicit ClosestPoint(m2::PointD const & center) : m_center(center), m_best(0, 0) {}
m2::PointD GetBest() const { return m_best; }
void operator()(m2::PointD const & point)
{
double distance = m_center.SquaredLength(point);
if (distance < m_distance)
{
m_distance = distance;
m_best = point;
}
}
};
m2::PointD FindCenter(FeatureType & f)
{
ClosestPoint closest(f.GetLimitRect(FeatureType::BEST_GEOMETRY).Center());
if (f.GetGeomType() == feature::GeomType::Area)
{
f.ForEachTriangle([&closest](m2::PointD const & p1, m2::PointD const & p2,
m2::PointD const & p3) { closest((p1 + p2 + p3) / 3); },
FeatureType::BEST_GEOMETRY);
}
else
{
f.ForEachPoint(closest, FeatureType::BEST_GEOMETRY);
}
return closest.GetBest();
}
size_t const kLangCount = StringUtf8Multilang::GetSupportedLanguages().size();
string GetReadableType(FeatureType & f)
{
string result;
f.ForEachType([&](uint32_t type)
{
if (ftypes::IsPoiChecker::Instance()(type) || ftypes::IsPlaceChecker::Instance()(type))
result = classif().GetReadableObjectName(type);
});
return result;
}
string GetWheelchairType(FeatureType & f)
{
static const uint32_t wheelchair = classif().GetTypeByPath({"wheelchair"});
string result;
f.ForEachType([&result](uint32_t type)
{
if (ftype::Trunc(type, 1) == wheelchair)
{
string fullName = classif().GetReadableObjectName(type);
auto pos = fullName.find("-");
if (pos != string::npos)
result = fullName.substr(pos + 1);
}
});
return result;
}
bool HasAtm(FeatureType & f)
{
static const uint32_t atm = classif().GetTypeByPath({"amenity", "atm"});
bool result = false;
f.ForEachType([&result](uint32_t type) {
if (type == atm)
result = true;
});
return result;
}
string BuildUniqueId(ms::LatLon const & coords, string const & name)
{
ostringstream ss;
ss << strings::to_string_dac(coords.m_lat, 6) << ','
<< strings::to_string_dac(coords.m_lon, 6) << ',' << name;
uint32_t hash = 0;
for (char const c : ss.str())
hash = hash * 101 + c;
return strings::to_string(hash);
}
void AppendNames(FeatureType & f, vector<string> & columns)
{
vector<string> names(kLangCount);
f.GetNames().ForEach([&names](int8_t code, string_view name) { names[code] = name; });
columns.insert(columns.end(), next(names.begin()), names.end());
}
void PrintAsCSV(vector<string> const & columns, char const delimiter, ostream & out)
{
bool first = true;
for (string value : columns)
{
// Newlines are hard to process, replace them with spaces. And trim the
// string.
replace(value.begin(), value.end(), '\r', ' ');
replace(value.begin(), value.end(), '\n', ' ');
strings::Trim(value);
if (first)
first = false;
else
out << delimiter;
bool needsQuotes = value.find('"') != string::npos || value.find(delimiter) != string::npos;
if (!needsQuotes)
{
out << value;
}
else
{
size_t pos = 0;
while ((pos = value.find('"', pos)) != string::npos)
{
value.insert(pos, 1, '"');
pos += 2;
}
out << '"' << value << '"';
}
}
out << endl;
}
class Processor
{
search::ReverseGeocoder m_geocoder;
base::Cancellable m_cancellable;
search::CitiesBoundariesTable m_boundariesTable;
search::VillagesCache m_villagesCache;
search::LocalityFinder m_finder;
public:
explicit Processor(DataSource const & dataSource)
: m_geocoder(dataSource)
, m_boundariesTable(dataSource)
, m_villagesCache(m_cancellable)
, m_finder(dataSource, m_boundariesTable, m_villagesCache)
{
m_boundariesTable.Load();
}
void ClearCache() { m_villagesCache.Clear(); }
void operator()(FeatureType & f, map<uint32_t, base::GeoObjectId> const & ft2osm)
{
Process(f, ft2osm);
}
void Process(FeatureType & f, map<uint32_t, base::GeoObjectId> const & ft2osm)
{
f.ParseHeader2();
string const & category = GetReadableType(f);
auto const & meta = f.GetMetadata();
auto const metaOperator = meta.Get(feature::Metadata::FMD_OPERATOR);
auto const & osmIt = ft2osm.find(f.GetID().m_index);
if ((!f.HasName() && metaOperator.empty()) ||
(f.GetGeomType() == feature::GeomType::Line && category != "highway-pedestrian") ||
category.empty())
{
return;
}
m2::PointD const & center = FindCenter(f);
ms::LatLon const & ll = mercator::ToLatLon(center);
osm::MapObject obj;
obj.SetFromFeatureType(f);
string_view city;
m_finder.GetLocality(center, [&city](search::LocalityItem const & item)
{
item.GetSpecifiedOrDefaultName(StringUtf8Multilang::kDefaultCode, city);
});
string const & mwmName = f.GetID().GetMwmName();
string name(f.GetName(StringUtf8Multilang::kDefaultCode));
if (name.empty())
{
name = f.GetReadableName();
if (name.empty())
name = metaOperator;
}
string osmId = osmIt != ft2osm.cend() ? to_string(osmIt->second.GetEncodedId()) : "";
string const & uid = BuildUniqueId(ll, name);
string const & lat = strings::to_string_dac(ll.m_lat, 6);
string const & lon = strings::to_string_dac(ll.m_lon, 6);
search::ReverseGeocoder::Address addr;
string addrStreet = "";
string addrHouse = "";
double constexpr kDistanceThresholdMeters = 0.5;
if (m_geocoder.GetExactAddress(f, addr))
{
addrStreet = addr.GetStreetName();
addrHouse = addr.GetHouseNumber();
}
else
{
m_geocoder.GetNearbyAddress(center, addr);
if (addr.GetDistance() < kDistanceThresholdMeters)
{
addrStreet = addr.GetStreetName();
addrHouse = addr.GetHouseNumber();
}
}
string const phone(meta.Get(feature::Metadata::FMD_PHONE_NUMBER));
string const website(meta.Get(feature::Metadata::FMD_WEBSITE));
string const contact_facebook(meta.Get(feature::Metadata::FMD_CONTACT_FACEBOOK));
string const contact_instagram(meta.Get(feature::Metadata::FMD_CONTACT_INSTAGRAM));
string const contact_twitter(meta.Get(feature::Metadata::FMD_CONTACT_TWITTER));
string const contact_vk(meta.Get(feature::Metadata::FMD_CONTACT_VK));
string const contact_line(meta.Get(feature::Metadata::FMD_CONTACT_LINE));
string const contact_fediverse(meta.Get(feature::Metadata::FMD_CONTACT_FEDIVERSE));
string const contact_bluesky(meta.Get(feature::Metadata::FMD_CONTACT_BLUESKY));
string const stars(meta.Get(feature::Metadata::FMD_STARS));
string const internet(meta.Get(feature::Metadata::FMD_INTERNET));
string const denomination(meta.Get(feature::Metadata::FMD_DENOMINATION));
string const wheelchair(GetWheelchairType(f));
string const opening_hours(meta.Get(feature::Metadata::FMD_OPEN_HOURS));
string const check_date(meta.Get(feature::Metadata::FMD_CHECK_DATE));
string const check_date_opening_hours(meta.Get(feature::Metadata::FMD_CHECK_DATE_OPEN_HOURS));
string const wikipedia(meta.Get(feature::Metadata::FMD_WIKIPEDIA));
string const wikimedia_commons(meta.Get(feature::Metadata::FMD_WIKIMEDIA_COMMONS));
string const panoramax(meta.Get(feature::Metadata::FMD_PANORAMAX));
string const floor(meta.Get(feature::Metadata::FMD_LEVEL));
string const fee = category.ends_with("-fee") ? "yes" : "";
string const atm = HasAtm(f) ? "yes" : "";
vector<string> columns = {
osmId, uid, lat, lon, mwmName, category, name, std::string(city),
addrStreet, addrHouse, phone, website, stars, std::string(metaOperator), internet,
denomination, wheelchair, opening_hours, check_date, check_date_opening_hours, wikipedia, floor, fee, atm, contact_facebook,
contact_instagram, contact_twitter, contact_vk, contact_line, contact_fediverse, contact_bluesky, wikimedia_commons, panoramax};
AppendNames(f, columns);
PrintAsCSV(columns, ';', cout);
}
};
void PrintHeader()
{
vector<string> columns = {"id", "old_id", "lat", "lon", "mwm",
"category", "name", "city", "street", "house",
"phone", "website", "cuisines", "stars", "operator",
"internet", "denomination", "wheelchair", "opening_hours", "check_date", "check_date_opening_hours", "wikipedia",
"floor", "fee", "atm", "contact_facebook", "contact_instagram",
"contact_twitter", "contact_vk", "contact_line", "contact_fediverse", "contact_bluesky", "wikimedia_commons", "panoramax"};
// Append all supported name languages in order.
for (uint8_t idx = 1; idx < kLangCount; idx++)
columns.push_back("name_" + string(StringUtf8Multilang::GetLangByCode(idx)));
PrintAsCSV(columns, ';', cout);
}
bool ParseFeatureIdToOsmIdMapping(string const & path, map<uint32_t, base::GeoObjectId> & mapping)
{
return generator::ForEachOsmId2FeatureId(
path, [&](auto const & compositeId, uint32_t const featureId) {
mapping[featureId] = compositeId.m_mainId;
});
}
void DidDownload(storage::CountryId const & /* countryId */,
shared_ptr<platform::LocalCountryFile> const & /* localFile */)
{
}
bool WillDelete(storage::CountryId const & /* countryId */,
shared_ptr<platform::LocalCountryFile> const & /* localFile */)
{
return false;
}
int main(int argc, char ** argv)
{
platform::tests_support::ChangeMaxNumberOfOpenFiles(search::search_quality::kMaxOpenFiles);
if (argc <= 1)
{
LOG(LERROR, ("Usage:", argc == 1 ? argv[0] : "feature_list",
"<mwm_path> [<data_path>] [<mwm_prefix>]"));
return 1;
}
Platform & pl = GetPlatform();
pl.SetWritableDirForTests(argv[1]);
string countriesFile = COUNTRIES_FILE;
if (argc > 2)
{
pl.SetResourceDir(argv[2]);
countriesFile = base::JoinPath(argv[2], COUNTRIES_FILE);
}
storage::Storage storage(countriesFile, argv[1]);
storage.Init(&DidDownload, &WillDelete);
auto infoGetter = storage::CountryInfoReader::CreateCountryInfoGetter(pl);
infoGetter->SetAffiliations(storage.GetAffiliations());
GetStyleReader().SetCurrentStyle(MapStyleMerged);
classificator::Load();
FrozenDataSource dataSource;
vector<platform::LocalCountryFile> mwms;
platform::FindAllLocalMapsAndCleanup(numeric_limits<int64_t>::max() /* the latest version */,
mwms);
for (auto & mwm : mwms)
{
mwm.SyncWithDisk();
auto const & p = dataSource.RegisterMap(mwm);
CHECK_EQUAL(MwmSet::RegResult::Success, p.second, ("Could not register map", mwm));
MwmSet::MwmId const & id = p.first;
CHECK(id.IsAlive(), ("Mwm is not alive?", mwm));
}
Processor doProcess(dataSource);
PrintHeader();
vector<shared_ptr<MwmInfo>> mwmInfos;
dataSource.GetMwmsInfo(mwmInfos);
for (auto const & mwmInfo : mwmInfos)
{
if (mwmInfo->GetType() != MwmInfo::COUNTRY)
continue;
if (argc > 3 && !(mwmInfo->GetCountryName() + DATA_FILE_EXTENSION).starts_with(argv[3]))
continue;
LOG(LINFO, ("Processing", mwmInfo->GetCountryName()));
string osmToFeatureFile = base::JoinPath(
argv[1], mwmInfo->GetCountryName() + DATA_FILE_EXTENSION + OSM2FEATURE_FILE_EXTENSION);
map<uint32_t, base::GeoObjectId> featureIdToOsmId;
ParseFeatureIdToOsmIdMapping(osmToFeatureFile, featureIdToOsmId);
MwmSet::MwmId mwmId(mwmInfo);
FeaturesLoaderGuard loader(dataSource, mwmId);
for (uint32_t ftIndex = 0; ftIndex < loader.GetNumFeatures(); ftIndex++)
{
if (auto ft = loader.GetFeatureByIndex(static_cast<uint32_t>(ftIndex)))
doProcess.Process(*ft, featureIdToOsmId);
}
doProcess.ClearCache();
}
return 0;
}

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

View File

@@ -0,0 +1,288 @@
#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,127 @@
#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,86 @@
#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,44 @@
#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,180 @@
#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,49 @@
#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

90
tools/openlr/graph.cpp Normal file
View File

@@ -0,0 +1,90 @@
#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

61
tools/openlr/graph.hpp Normal file
View File

@@ -0,0 +1,61 @@
#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

238
tools/openlr/helpers.cpp Normal file
View File

@@ -0,0 +1,238 @@
#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::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:
// Note. HighwayClass::Trunk means motorway, motorway_link, trunk or trunk_link.
return hwClass == HighwayClass::Trunk ? optional<Score>(kMaxScoreForFrc) : nullopt;
case FunctionalRoadClass::FRC1:
return (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

73
tools/openlr/helpers.hpp Normal file
View File

@@ -0,0 +1,73 @@
#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,504 @@
#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,61 @@
#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,407 @@
#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 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,29 @@
#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,46 @@
#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,40 @@
#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,52 @@
#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,71 @@
#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,558 @@
#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(const QModelIndex & parent) const
{
return static_cast<int>(m_segments.size());
}
int TrafficMode::columnCount(const QModelIndex & parent) const { return 4; }
QVariant TrafficMode::data(const QModelIndex & index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= rowCount())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
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,125 @@
#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(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex & index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(QModelIndex const & index) const Q_DECL_OVERRIDE;
bool IsBuildingPath() const { return m_buildingPath; }
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,82 @@
#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,50 @@
#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,54 @@
#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,362 @@
#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,293 @@
#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 <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, std::string(".*\\") + 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/file_name_utils.hpp"
#include "base/checked_cast.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,233 @@
#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,38 @@
#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,41 @@
#include "openlr/road_info_getter.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature.hpp"
#include "indexer/data_source.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,42 @@
#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

772
tools/openlr/router.cpp Normal file
View File

@@ -0,0 +1,772 @@
#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

229
tools/openlr/router.hpp Normal file
View File

@@ -0,0 +1,229 @@
#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 const int 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 const int kIntermediateErrorCoeff = 3;
// A weight for excess of distance limit.
static const int kDistanceErrorCoeff = 3;
// A weight for deviation from bearing.
static const int 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,341 @@
#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,124 @@
#pragma once
#include "openlr/helpers.hpp"
#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 "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,140 @@
#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,50 @@
#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,365 @@
#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 <string>
#include <queue>
#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,44 @@
#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,58 @@
#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,27 @@
#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

View File

@@ -0,0 +1,17 @@
project(poly_borders)
set(
SRC
borders_data.cpp
borders_data.hpp
help_structures.cpp
help_structures.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME} generator)
omim_add_test_subdirectory(poly_borders_tests)
omim_add_tool_subdirectory(poly_borders_tool)

View File

@@ -0,0 +1,455 @@
#include "poly_borders/borders_data.hpp"
#include "poly_borders/help_structures.hpp"
#include "generator/borders.hpp"
#include "generator/routing_city_boundaries_processor.hpp"
#include "platform/platform.hpp"
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
#include "geometry/region2d.hpp"
#include "base/assert.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include "base/thread_pool_computational.hpp"
#include <algorithm>
#include <iostream>
#include <tuple>
namespace
{
using namespace poly_borders;
void PrintWithSpaces(std::string const & str, size_t maxN)
{
std::cout << str;
if (maxN <= str.size())
return;
maxN -= str.size();
for (size_t i = 0; i < maxN; ++i)
std::cout << " ";
}
std::string RemoveIndexFromMwmName(std::string const & mwmName)
{
auto const pos = mwmName.find(BordersData::kBorderExtension);
CHECK_NOT_EQUAL(pos, std::string::npos, ());
auto const end = mwmName.begin() + pos + BordersData::kBorderExtension.size();
std::string result(mwmName.cbegin(), end);
return result;
}
bool IsReversedIntervals(size_t fromDst, size_t toDst, size_t fromSrc, size_t toSrc)
{
return (fromDst > toDst) != (fromSrc > toSrc);
}
std::vector<m2::PointD> AppendPointsWithAnyDirection(std::vector<MarkedPoint> const & copyFrom,
size_t from, size_t to)
{
std::vector<m2::PointD> result;
if (from > to)
std::swap(from, to);
for (size_t k = from; k <= to; ++k)
result.emplace_back(copyFrom[k].m_point);
return result;
}
double AbsAreaDiff(std::vector<m2::PointD> const & firstPolygon,
std::vector<m2::PointD> const & secondPolygon)
{
auto const firstArea = generator::AreaOnEarth(firstPolygon);
auto const secondArea = generator::AreaOnEarth(secondPolygon);
return std::abs(secondArea - firstArea);
}
bool NeedReplace(std::vector<m2::PointD> const & curSubpolygon,
std::vector<m2::PointD> const & anotherSubpolygon)
{
auto const areaDiff = AbsAreaDiff(curSubpolygon, anotherSubpolygon);
double constexpr kMaxAreaDiffMetersSquared = 20000.0;
if (areaDiff > kMaxAreaDiffMetersSquared)
return false;
if (AlmostEqualAbs(areaDiff, 0.0, BordersData::kEqualityEpsilon))
return false;
// We know that |curSize| is always greater than 1, because we construct it such way, but we know
// nothing about |anotherSize|, and we do not want to replace current subpolygon of several points
// with a subpolygon consisting of one point or consisting of too many points.
CHECK_GREATER(curSubpolygon.size(), 1, ());
return 1 < anotherSubpolygon.size() && anotherSubpolygon.size() < 10;
}
bool ShouldLog(size_t i, size_t n)
{
return (i % 100 == 0) || (i + 1 == n);
}
void Append(size_t from, size_t to, bool reversed, std::vector<MarkedPoint> const & copyFrom,
std::vector<MarkedPoint> & copyTo)
{
if (!reversed)
{
for (size_t k = from; k <= to; ++k)
copyTo.emplace_back(copyFrom[k].m_point);
}
else
{
size_t k = to;
while (k >= from)
{
copyTo.emplace_back(copyFrom[k].m_point);
if (k == 0)
break;
--k;
}
}
}
m2::RectD BoundingBox(std::vector<MarkedPoint> const & points)
{
m2::RectD rect;
for (auto const & point : points)
rect.Add(point.m_point);
return rect;
}
void SwapIfNeeded(size_t & a, size_t & b)
{
if (a > b)
std::swap(a, b);
}
} // namespace
namespace poly_borders
{
// BordersData -------------------------------------------------------------------------------------
void BordersData::Init(std::string const & bordersDir)
{
LOG(LINFO, ("Borders path:", bordersDir));
// key - coordinates
// value - {border idx, point idx}
std::unordered_map<int64_t, std::vector<std::pair<size_t, size_t>>> index;
std::vector<std::string> files;
Platform::GetFilesByExt(bordersDir, kBorderExtension, files);
LOG(LINFO, ("Start reading data from .poly files."));
size_t prevIndex = 0;
for (auto const & file : files)
{
auto const fullPath = base::JoinPath(bordersDir, file);
size_t polygonId = 1;
borders::PolygonsList borders;
borders::LoadBorders(fullPath, borders);
for (auto & region : borders)
{
auto & points = region.MutableData();
m_duplicatedPointsCount += RemoveDuplicatingPointImpl(points);
CHECK_GREATER(points.size(), 1, (fullPath));
// Some mwms have several polygons. For example, for Japan_Kanto_Tokyo that has 2 polygons we
// will write 2 files:
// Japan_Kanto_Tokyo.poly1
// Japan_Kanto_Tokyo.poly2
auto const fileCopy = file + std::to_string(polygonId);
m_indexToPolyFileName[prevIndex] = fileCopy;
m_polyFileNameToIndex[fileCopy] = prevIndex++;
size_t const borderIdx = m_bordersPolygons.size();
for (size_t i = 0; i < points.size(); ++i)
index[PointToInt64Obsolete(points[i], kPointCoordBits)].emplace_back(borderIdx, i);
++polygonId;
m_bordersPolygons.emplace_back(region.GetRect(), points);
}
}
for (auto const & [_, v] : index)
{
for (size_t i = 0; i < v.size() - 1; ++i)
for (size_t j = i + 1; j < v.size(); ++j)
{
m_bordersPolygons[v[i].first].m_points[v[i].second].AddLink(v[j].first, v[j].second);
m_bordersPolygons[v[j].first].m_points[v[j].second].AddLink(v[i].first, v[i].second);
}
}
LOG(LINFO, ("Removed:", m_duplicatedPointsCount, "from input data."));
}
void BordersData::DumpPolyFiles(std::string const & targetDir)
{
size_t n = m_bordersPolygons.size();
for (size_t i = 0; i < n; )
{
// Russia_Moscow.poly1 -> Russia_Moscow.poly
auto name = RemoveIndexFromMwmName(m_indexToPolyFileName.at(i));
size_t j = i + 1;
while (j < n && name == RemoveIndexFromMwmName(m_indexToPolyFileName.at(j)))
++j;
std::vector<m2::RegionD> regions;
for (; i < j; i++)
{
if (ShouldLog(i, n))
LOG(LINFO, ("Dumping poly files:", i + 1, "/", n));
m2::RegionD region;
for (auto const & markedPoint : m_bordersPolygons[i].m_points)
region.AddPoint(markedPoint.m_point);
regions.emplace_back(std::move(region));
}
CHECK(strings::ReplaceFirst(name, kBorderExtension, ""), (name));
borders::DumpBorderToPolyFile(targetDir, name, regions);
}
}
size_t BordersData::RemoveDuplicatePoints()
{
size_t count = 0;
for (auto & polygon : m_bordersPolygons)
count += RemoveDuplicatingPointImpl(polygon.m_points);
return count;
}
void BordersData::PrintDiff()
{
using Info = std::tuple<double, std::string, size_t, size_t>;
std::set<Info> info;
size_t allNumberBeforeCount = 0;
size_t maxMwmNameLength = 0;
for (size_t i = 0; i < m_bordersPolygons.size(); ++i)
{
auto const & mwmName = m_indexToPolyFileName[i];
auto const all = static_cast<int32_t>(m_bordersPolygons[i].m_points.size());
auto const allBefore = static_cast<int32_t>(m_prevCopy[i].m_points.size());
CHECK_GREATER_OR_EQUAL(allBefore, all, ());
m_removedPointsCount += allBefore - all;
allNumberBeforeCount += allBefore;
double area = 0.0;
double constexpr kAreaEpsMetersSqr = 1e-4;
if (m_additionalAreaMetersSqr[i] >= kAreaEpsMetersSqr)
area = m_additionalAreaMetersSqr[i];
maxMwmNameLength = std::max(maxMwmNameLength, mwmName.size());
info.emplace(area, mwmName, allBefore, all);
}
for (auto const & [area, name, allBefore, all] : info)
{
size_t diff = allBefore - all;
PrintWithSpaces(name, maxMwmNameLength + 1);
PrintWithSpaces("-" + std::to_string(diff) + " points", 17);
std::cout << " total changed area: " << area << " m^2" << std::endl;
}
CHECK_NOT_EQUAL(allNumberBeforeCount, 0, ("Empty input?"));
std::cout << "Number of removed points: " << m_removedPointsCount << std::endl
<< "Removed duplicate point: " << m_duplicatedPointsCount << std::endl
<< "Total removed points: " << m_removedPointsCount + m_duplicatedPointsCount
<< std::endl;
std::cout << "Points number before processing: " << allNumberBeforeCount << ", remove( "
<< static_cast<double>(m_removedPointsCount + m_duplicatedPointsCount) /
allNumberBeforeCount * 100.0
<< "% )" << std::endl;
}
void BordersData::RemoveEmptySpaceBetweenBorders()
{
LOG(LINFO, ("Start removing empty space between borders."));
for (size_t curBorderId = 0; curBorderId < m_bordersPolygons.size(); ++curBorderId)
{
LOG(LDEBUG, ("Get:", m_indexToPolyFileName[curBorderId]));
if (ShouldLog(curBorderId, m_bordersPolygons.size()))
LOG(LINFO, ("Removing empty spaces:", curBorderId + 1, "/", m_bordersPolygons.size()));
auto & curPolygon = m_bordersPolygons[curBorderId];
for (size_t curPointId = 0; curPointId < curPolygon.m_points.size(); ++curPointId)
{
if (curPolygon.IsFrozen(curPointId, curPointId) || !HasLinkAt(curBorderId, curPointId))
continue;
size_t constexpr kMaxLookAhead = 5;
for (size_t shift = 1; shift <= kMaxLookAhead; ++shift)
{
if (TryToReplace(curBorderId, curPointId /* curLeftPointId */,
curPointId + shift /* curRightPointId */) == base::ControlFlow::Break)
{
break;
}
}
}
}
DoReplace();
}
base::ControlFlow BordersData::TryToReplace(size_t curBorderId, size_t & curLeftPointId,
size_t curRightPointId)
{
auto & curPolygon = m_bordersPolygons[curBorderId];
if (curRightPointId >= curPolygon.m_points.size())
return base::ControlFlow::Break;
if (curPolygon.IsFrozen(curRightPointId, curRightPointId))
{
curLeftPointId = curRightPointId - 1;
return base::ControlFlow::Break;
}
auto & leftMarkedPoint = curPolygon.m_points[curLeftPointId];
auto & rightMarkedPoint = curPolygon.m_points[curRightPointId];
auto op = rightMarkedPoint.GetLink(curBorderId);
if (!op)
return base::ControlFlow::Continue;
Link rightLink = *op;
Link leftLink = *leftMarkedPoint.GetLink(curBorderId);
if (leftLink.m_borderId != rightLink.m_borderId)
return base::ControlFlow::Continue;
auto const anotherBorderId = leftLink.m_borderId;
auto const anotherLeftPointId = leftLink.m_pointId;
auto const anotherRightPointId = rightLink.m_pointId;
auto & anotherPolygon = m_bordersPolygons[anotherBorderId];
if (anotherPolygon.IsFrozen(std::min(anotherLeftPointId, anotherRightPointId),
std::max(anotherLeftPointId, anotherRightPointId)))
{
return base::ControlFlow::Continue;
}
auto const anotherSubpolygon =
AppendPointsWithAnyDirection(anotherPolygon.m_points, anotherLeftPointId, anotherRightPointId);
auto const curSubpolygon =
AppendPointsWithAnyDirection(curPolygon.m_points, curLeftPointId, curRightPointId);
if (!NeedReplace(curSubpolygon, anotherSubpolygon))
return base::ControlFlow::Break;
// We want to decrease the amount of points in polygons. So we will replace the greater amounts of
// points by smaller amounts of points.
bool const curLenIsLess = curSubpolygon.size() < anotherSubpolygon.size();
size_t dstFrom = curLenIsLess ? anotherLeftPointId : curLeftPointId;
size_t dstTo = curLenIsLess ? anotherRightPointId : curRightPointId;
size_t srcFrom = curLenIsLess ? curLeftPointId : anotherLeftPointId;
size_t srcTo = curLenIsLess ? curRightPointId : anotherRightPointId;
size_t const borderIdWhereAreaWillBeChanged = curLenIsLess ? anotherBorderId : curBorderId;
size_t const srcBorderId = curLenIsLess ? curBorderId : anotherBorderId;
bool const reversed = IsReversedIntervals(dstFrom, dstTo, srcFrom, srcTo);
m_additionalAreaMetersSqr[borderIdWhereAreaWillBeChanged] +=
AbsAreaDiff(curSubpolygon, anotherSubpolygon);
SwapIfNeeded(dstFrom, dstTo);
SwapIfNeeded(srcFrom, srcTo);
// Save info for |borderIdWhereAreaWillBeChanged| - where from it should gets info about
// replacement.
m_bordersPolygons[borderIdWhereAreaWillBeChanged].AddReplaceInfo(
dstFrom, dstTo, srcFrom, srcTo, srcBorderId, reversed);
// And say for |srcBorderId| that points in segment: [srcFrom, srcTo] are frozen and cannot
// be used anywhere (because we use them to replace points in segment: [dstFrom, dstTo]).
m_bordersPolygons[srcBorderId].MakeFrozen(srcFrom, srcTo);
CHECK_LESS(curLeftPointId, curRightPointId, ());
curLeftPointId = curRightPointId - 1;
return base::ControlFlow::Break;
}
void BordersData::DoReplace()
{
LOG(LINFO, ("Start replacing intervals."));
std::vector<Polygon> newMwmsPolygons;
for (size_t borderId = 0; borderId < m_bordersPolygons.size(); ++borderId)
{
if (ShouldLog(borderId, m_bordersPolygons.size()))
LOG(LINFO, ("Replacing:", borderId + 1, "/", m_bordersPolygons.size()));
auto & polygon = m_bordersPolygons[borderId];
newMwmsPolygons.emplace_back();
auto & newPolygon = newMwmsPolygons.back();
for (size_t i = 0; i < polygon.m_points.size(); ++i)
{
auto const replaceDataIter = polygon.FindReplaceData(i);
bool const noReplaceData = replaceDataIter == polygon.m_replaceData.cend();
if (noReplaceData)
{
newPolygon.m_points.emplace_back(polygon.m_points[i].m_point);
continue;
}
auto const srcBorderId = replaceDataIter->m_srcBorderId;
size_t const srcFrom = replaceDataIter->m_srcReplaceFrom;
size_t const srcTo = replaceDataIter->m_srcReplaceTo;
size_t const nextI = replaceDataIter->m_dstTo;
bool const reversed = replaceDataIter->m_reversed;
CHECK_EQUAL(i, replaceDataIter->m_dstFrom, ());
auto const & srcPolygon = m_bordersPolygons[srcBorderId];
Append(srcFrom, srcTo, reversed, srcPolygon.m_points, newPolygon.m_points);
polygon.m_replaceData.erase(replaceDataIter);
i = nextI - 1;
}
newPolygon.m_rect = BoundingBox(newPolygon.m_points);
}
m_prevCopy = std::move(m_bordersPolygons);
m_bordersPolygons = std::move(newMwmsPolygons);
RemoveDuplicatePoints();
}
Polygon const & BordersData::GetBordersPolygonByName(std::string const & name) const
{
auto id = m_polyFileNameToIndex.at(name);
return m_bordersPolygons.at(id);
}
bool BordersData::HasLinkAt(size_t curBorderId, size_t pointId)
{
auto & leftMarkedPoint = m_bordersPolygons[curBorderId].m_points[pointId];
return leftMarkedPoint.GetLink(curBorderId).has_value();
}
} // namespace poly_borders

View File

@@ -0,0 +1,74 @@
#pragma once
#include "poly_borders/help_structures.hpp"
#include "base/control_flow.hpp"
#include <map>
#include <string>
#include <vector>
namespace poly_borders
{
/// \note All methods of this class are not thread-safe except |MarkPoint()| method.
class BordersData
{
public:
inline static double const kEqualityEpsilon = 1.0E-7;
inline static std::string const kBorderExtension = ".poly";
void Init(std::string const & bordersDir);
void RemoveEmptySpaceBetweenBorders();
void DumpPolyFiles(std::string const & targetDir);
Polygon const & GetBordersPolygonByName(std::string const & name) const;
void PrintDiff();
private:
/// \brief Some polygons can have sequentially same points - duplicates. This method removes such
/// points and leaves only unique.
size_t RemoveDuplicatePoints();
template <class PointsT> static size_t RemoveDuplicatingPointImpl(PointsT & points)
{
auto const equalFn = [](auto const & p1, auto const & p2)
{
return p1.EqualDxDy(p2, kEqualityEpsilon);
};
auto const last = std::unique(points.begin(), points.end(), equalFn);
size_t count = std::distance(last, points.end());
points.erase(last, points.end());
while (points.size() > 1 && equalFn(points.front(), points.back()))
{
++count;
points.pop_back();
}
return count;
}
/// \brief Checks whether we can replace points from segment: [curLeftPointId, curRightPointId]
/// of |curBorderId| to points from another border in order to get rid of empty space
/// between curBorder and anotherBorder.
base::ControlFlow TryToReplace(size_t curBorderId, size_t & curLeftPointId,
size_t curRightPointId);
bool HasLinkAt(size_t curBorderId, size_t pointId);
/// \brief Replace points using |Polygon::ReplaceData| that is filled by
/// |RemoveEmptySpaceBetweenBorders()|.
void DoReplace();
size_t m_removedPointsCount = 0;
size_t m_duplicatedPointsCount = 0;
std::map<size_t, double> m_additionalAreaMetersSqr;
std::map<std::string, size_t> m_polyFileNameToIndex;
std::map<size_t, std::string> m_indexToPolyFileName;
std::vector<Polygon> m_bordersPolygons;
std::vector<Polygon> m_prevCopy;
};
} // namespace poly_borders

View File

@@ -0,0 +1,83 @@
#include "poly_borders/help_structures.hpp"
#include <algorithm>
#include <cmath>
#include <tuple>
namespace poly_borders
{
// Link --------------------------------------------------------------------------------------------
bool Link::operator<(Link const & rhs) const
{
return std::tie(m_borderId, m_pointId) < std::tie(rhs.m_borderId, rhs.m_pointId);
}
// ReplaceData -------------------------------------------------------------------------------------
bool ReplaceData::operator<(ReplaceData const & rhs) const
{
return std::tie(m_dstFrom, m_dstTo) < std::tie(rhs.m_dstFrom, rhs.m_dstTo);
}
// MarkedPoint -------------------------------------------------------------------------------------
void MarkedPoint::AddLink(size_t borderId, size_t pointId)
{
m_links.emplace(borderId, pointId);
}
std::optional<Link> MarkedPoint::GetLink(size_t curBorderId) const
{
if (m_links.size() != 1)
return std::nullopt;
size_t anotherBorderId = m_links.begin()->m_borderId;
if (anotherBorderId == curBorderId)
return std::nullopt;
return *m_links.begin();
}
// Polygon -----------------------------------------------------------------------------------------
void Polygon::MakeFrozen(size_t a, size_t b)
{
CHECK_LESS(a, b, ());
// Ends of intervals shouldn't be frozen, we freeze only inner points: (a, b).
// This condition is needed to drop such cases: a = x, b = x + 1, when
// a + 1 will be greater than b - 1.
if (b - a + 1 > 2)
m_replaced.AddInterval(a + 1, b - 1);
}
bool Polygon::IsFrozen(size_t a, size_t b) const
{
// We use LESS_OR_EQUAL because we want sometimes to check if
// point i (associated with interval: [i, i]) is frozen.
CHECK_LESS_OR_EQUAL(a, b, ());
return m_replaced.Intersects(a, b);
}
void Polygon::AddReplaceInfo(size_t dstFrom, size_t dstTo,
size_t srcFrom, size_t srcTo, size_t srcBorderId,
bool reversed)
{
CHECK_LESS_OR_EQUAL(dstFrom, dstTo, ());
CHECK_LESS(srcFrom, srcTo, ());
CHECK(!IsFrozen(dstFrom, dstTo), ());
MakeFrozen(dstFrom, dstTo);
m_replaceData.emplace(dstFrom, dstTo, srcFrom, srcTo, srcBorderId, reversed);
}
std::set<ReplaceData>::const_iterator Polygon::FindReplaceData(size_t index)
{
for (auto it = m_replaceData.cbegin(); it != m_replaceData.cend(); ++it)
{
if (it->m_dstFrom <= index && index <= it->m_dstTo)
return it;
}
return m_replaceData.cend();
}
} // namespace poly_borders

View File

@@ -0,0 +1,107 @@
#pragma once
#include "geometry/rect2d.hpp"
#include "base/non_intersecting_intervals.hpp"
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
namespace poly_borders
{
struct Link
{
inline static auto constexpr kInvalidId = std::numeric_limits<size_t>::max();
Link() = default;
Link(size_t borderId, size_t pointId) : m_borderId(borderId), m_pointId(pointId) {}
bool operator<(Link const & rhs) const;
size_t m_borderId = kInvalidId;
size_t m_pointId = kInvalidId;
};
/// \note Using next semantic here: [replaceFrom, replaceTo], [replaceFromSrc, replaceToSrc].
struct ReplaceData
{
ReplaceData(size_t replaceFrom, size_t replaceTo, size_t replaceFromSrc, size_t replaceToSrc,
size_t borderIdSrc, bool reversed)
: m_dstFrom(replaceFrom)
, m_dstTo(replaceTo)
, m_srcReplaceFrom(replaceFromSrc)
, m_srcReplaceTo(replaceToSrc)
, m_srcBorderId(borderIdSrc)
, m_reversed(reversed) {}
bool operator<(ReplaceData const & rhs) const;
size_t m_dstFrom;
size_t m_dstTo;
size_t m_srcReplaceFrom;
size_t m_srcReplaceTo;
size_t m_srcBorderId;
// If |m_srcReplaceFrom| was greater than |m_srcReplaceTo|.
bool m_reversed;
};
struct MarkedPoint
{
MarkedPoint() = default;
MarkedPoint(m2::PointD const & point) : m_point(point) {}
void AddLink(size_t borderId, size_t pointId);
std::optional<Link> GetLink(size_t curBorderId) const;
bool EqualDxDy(MarkedPoint const & p, double eps) const
{
return m_point.EqualDxDy(p.m_point, eps);
}
m2::PointD m_point;
std::set<Link> m_links;
};
struct Polygon
{
Polygon() = default;
Polygon(m2::RectD const & rect, std::vector<m2::PointD> const & points) : m_rect(rect)
{
m_points.assign(points.begin(), points.end());
}
Polygon(m2::RectD const & rect, std::vector<MarkedPoint> && points)
: m_rect(rect), m_points(std::move(points))
{
}
Polygon(Polygon &&) = default;
Polygon & operator=(Polygon &&) noexcept = default;
// [a, b]
// @{
void MakeFrozen(size_t a, size_t b);
bool IsFrozen(size_t a, size_t b) const;
// @}
// [replaceFrom, replaceTo], [replaceFromSrc, replaceToSrc]
void AddReplaceInfo(size_t replaceFrom, size_t replaceTo,
size_t replaceFromSrc, size_t replaceToSrc, size_t borderIdSrc,
bool reversed);
std::set<ReplaceData>::const_iterator FindReplaceData(size_t index);
m2::RectD m_rect;
std::vector<MarkedPoint> m_points;
base::NonIntersectingIntervals<size_t> m_replaced;
std::set<ReplaceData> m_replaceData;
};
} // namespace poly_borders

View File

@@ -0,0 +1,15 @@
project(poly_borders_tests)
set(SRC
mark_points_tests.cpp
remove_empty_spaces_tests.cpp
tools.cpp
tools.hpp
)
omim_add_test(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
poly_borders
platform_tests_support
)

View File

@@ -0,0 +1,231 @@
#include "poly_borders/poly_borders_tests/tools.hpp"
#include "testing/testing.hpp"
#include "poly_borders/borders_data.hpp"
#include "platform/platform_tests_support/scoped_dir.hpp"
#include "platform/platform_tests_support/scoped_file.hpp"
#include "platform/platform_tests_support/writable_dir_changer.hpp"
#include "platform/platform.hpp"
#include "base/assert.hpp"
#include <memory>
#include <string>
#include <vector>
using namespace platform::tests_support;
using namespace platform;
using namespace poly_borders;
using namespace std;
namespace
{
static string const kTestDir = "borders_poly_dir";
void TestMarked(Polygon const & polygon, size_t i)
{
TEST(!polygon.m_points[i].m_links.empty(), (i, "th point point must be marked."));
}
void TestNotMarked(Polygon const & polygon, size_t i)
{
TEST(polygon.m_points[i].m_links.empty(), (i, "th point must not be marked."));
}
void CheckByMask(Polygon const & polygons, vector<bool> markedMask)
{
CHECK_EQUAL(polygons.m_points.size(), markedMask.size(), ());
for (size_t i = 0; i < polygons.m_points.size(); ++i)
{
if (markedMask[i])
TestMarked(polygons, i);
else
TestNotMarked(polygons, i);
}
}
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_1)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(-1.0, -1.0);
m2::PointD b(-1.0, 1.0);
vector<vector<m2::PointD>> polygons1 = {
{a, b, {1.0, 1.0}, {1.0, -1.0}}
};
vector<vector<bool>> markedMask1 = {
{true, true, false, false}
};
vector<vector<m2::PointD>> polygons2 = {
{a, b, {2.0, 1.0}, {5.0, -1.0}}
};
vector<vector<bool>> markedMask2 = {
{true, true, false, false}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
bordersData.Init(bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon1, markedMask1[0]);
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon2, markedMask2[0]);
}
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_2)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
vector<vector<m2::PointD>> polygons1 = {
{{-1.0, -1.0}, {-1.0, 1.0}, {1.0, 1.0}, {1.0, -1.0}}
};
vector<vector<bool>> markedMask1 = {
{false, false, false, false}
};
vector<vector<m2::PointD>> polygons2 = {
{{-12.0, -1.0}, {-10.0, 1.0}, {2.0, 1.0}, {5.0, -1.0}}
};
vector<vector<bool>> markedMask2 = {
{false, false, false, false}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
bordersData.Init(bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon1, markedMask1[0]);
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon2, markedMask2[0]);
}
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_3)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(-2.0, 1.0);
m2::PointD b(0.0, 3.0);
m2::PointD c(3.0, -1.0);
m2::PointD d(-1.0, -3.0);
m2::PointD e(-4.0, 2.0);
m2::PointD f(-1.0, 4.0);
vector<vector<m2::PointD>> polygons1 = {
{a, b, c, {1.0, -3.0}, d}
};
vector<vector<bool>> markedMask1 = {
{true, true, true, false, true}
};
vector<vector<m2::PointD>> polygons2 = {
{b, f, {2.0, 5.0}, {6.0, 3.0}, c}
};
vector<vector<bool>> markedMask2 = {
{true, true, false, false, true}
};
vector<vector<m2::PointD>> polygons3 = {
{a, b, f, {-3.0, 4.0}, e}
};
vector<vector<bool>> markedMask3 = {
{true, true, true, false, true}
};
vector<vector<m2::PointD>> polygons4 = {
{a, e, {-3.0, -1.0}, d}
};
vector<vector<bool>> markedMask4 = {
{true, true, false, true}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Third", polygons3));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Fourth", polygons4));
BordersData bordersData;
bordersData.Init(bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon1, markedMask1[0]);
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon2, markedMask2[0]);
auto const & bordersPolygon3 = bordersData.GetBordersPolygonByName("Third" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon3, markedMask3[0]);
auto const & bordersPolygon4 = bordersData.GetBordersPolygonByName("Fourth" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon4, markedMask4[0]);
}
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_4)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(6.0, 2.0);
m2::PointD b(6.0, 4.0);
vector<vector<m2::PointD>> polygons1 = {
{{-2.0, -2.0}, {-2.0, 2.0}, {2.0, 2.0}, {2.0, -2.0}},
{{4.0, 2.0}, {4.0, 4.0}, a, b}
};
vector<vector<bool>> markedMask1 = {
{false, false, false, false},
{false, false, true, true}
};
vector<vector<m2::PointD>> polygons2 = {
{a, b, {8.0, 6.0}, {8.0, 0.0}}
};
vector<vector<bool>> markedMask2 = {
{true, true, false, false}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
bordersData.Init(bordersDir);
auto const & firstBordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
CheckByMask(firstBordersPolygon1, markedMask1[0]);
auto const & secondBordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "2");
CheckByMask(secondBordersPolygon1, markedMask1[1]);
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
CheckByMask(bordersPolygon2, markedMask2[0]);
}
} // namespace

View File

@@ -0,0 +1,268 @@
#include "poly_borders/poly_borders_tests/tools.hpp"
#include "testing/testing.hpp"
#include "poly_borders/borders_data.hpp"
#include "platform/platform_tests_support/scoped_dir.hpp"
#include "platform/platform_tests_support/scoped_file.hpp"
#include "geometry/point2d.hpp"
#include <set>
#include <string>
#include <vector>
namespace remove_empty_spaces_tests
{
using namespace platform::tests_support;
using namespace platform;
using namespace poly_borders;
using namespace std;
string const kTestDir = "borders_poly_dir";
auto constexpr kSmallShift = 1e-9;
auto constexpr kSmallPointShift = m2::PointD(kSmallShift, kSmallShift);
void Process(BordersData & bordersData, string const & bordersDir)
{
bordersData.Init(bordersDir);
bordersData.RemoveEmptySpaceBetweenBorders();
}
bool ConsistsOf(Polygon const & polygon, vector<m2::PointD> const & points)
{
CHECK_EQUAL(polygon.m_points.size(), points.size(), ());
set<size_t> used;
for (auto const & point : points)
{
for (size_t i = 0; i < polygon.m_points.size(); ++i)
{
static double constexpr kEps = 1e-5;
if (AlmostEqualAbs(point, polygon.m_points[i].m_point, kEps) &&
used.count(i) == 0)
{
used.emplace(i);
break;
}
}
}
return used.size() == points.size();
}
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_1)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(0.0, 0.0);
m2::PointD b(1.0, 0.0);
m2::PointD c(2.0, 0.0);
m2::PointD d(3.0, 0.0);
m2::PointD e(4.0, 0.0);
vector<vector<m2::PointD>> polygons1 = {
{a, b, c, d, e}
};
vector<vector<m2::PointD>> polygons2 = {
{a, b, c, d, e}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
Process(bordersData, bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon1, {a, b, c, d, e}), ());
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon2, {a, b, c, d, e}), ());
}
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_2)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(0.0, 0.0);
m2::PointD b(1.0, 0.0);
// We should make c.y small because in other case changed area
// will be so great, that point |c| will not be removed.
m2::PointD c(2.0, kSmallShift);
m2::PointD d(3.0, 0.0);
m2::PointD e(4.0, 0.0);
// Point |c| is absent from polygons2, algorithm should remove |c| from polygon1.
vector<vector<m2::PointD>> polygons1 = {
{a, b, c, d, e}
};
vector<vector<m2::PointD>> polygons2 = {
{a, b, d, e}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
Process(bordersData, bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon1, {a, b, d, e}), ());
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon2, {a, b, d, e}), ());
}
// Like |PolyBordersPostprocessor_RemoveEmptySpaces_2| but two points will be
// added instead of one.
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_3)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(0.0, 0.0);
m2::PointD b(1.0, 0.0);
// We should make c.y (and d.y) small because in other case changed area
// will be so great, that point |c| (|d|) will not be removed.
m2::PointD c(2.0, kSmallShift);
m2::PointD d(2.5, kSmallShift);
m2::PointD e(4.0, 0.0);
m2::PointD f(5.0, 0.0);
// Point |c| and |d| is absent from polygons2, algorithm should remove |c| from polygon1.
vector<vector<m2::PointD>> polygons1 = {
{a, b, c, d, e, f}
};
vector<vector<m2::PointD>> polygons2 = {
{a, b, e, f}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
Process(bordersData, bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon1, {a, b, e, f}), ());
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon2, {a, b, e, f}), ());
}
// Do not remove point |c| because changed area is too big.
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_4)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(0.0, 0.0);
m2::PointD b(1.0, 0.0);
m2::PointD c(2.0, 1.0);
m2::PointD d(4.0, 0.0);
m2::PointD e(5.0, 0.0);
vector<vector<m2::PointD>> polygons1 = {
{a, b, c, d, e}
};
vector<vector<m2::PointD>> polygons2 = {
{a, b, d, e}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
Process(bordersData, bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon1, {a, b, c, d, e}), ());
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon2, {a, b, d, e}), ());
}
// Replace {c1, d1, e1} -> {c2, d2}.
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_5)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(0.0, 0.0);
m2::PointD b(9.0, 0.0);
m2::PointD c1(2.0, 3.0);
m2::PointD d1(4.0, 4.0);
m2::PointD e1(d1 + kSmallPointShift + kSmallPointShift);
m2::PointD c2(c1 + kSmallPointShift);
m2::PointD d2(d1 + kSmallPointShift);
vector<vector<m2::PointD>> polygons1 = {
{a, c1, d1, e1, b}
};
vector<vector<m2::PointD>> polygons2 = {
{a, c2, d2, b}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
Process(bordersData, bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon1, {a, c2, d2, b}), ());
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon2, {a, c2, d2, b}), ());
}
// Removes duplicates.
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_6)
{
ScopedDir const scopedDir(kTestDir);
string const & bordersDir = scopedDir.GetFullPath();
m2::PointD a(0.0, 0.0);
m2::PointD b(1.0, 0.0);
m2::PointD c(2.0, 1.0);
m2::PointD d(4.0, 0.0);
m2::PointD e(5.0, 0.0);
vector<vector<m2::PointD>> polygons1 = {
{a, b, c, d, d, d, e, e, e}
};
vector<vector<m2::PointD>> polygons2 = {
{a, d, d, d, e}
};
vector<shared_ptr<ScopedFile>> files;
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
BordersData bordersData;
Process(bordersData, bordersDir);
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon1, {a, b, c, d, e}), ());
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
TEST(ConsistsOf(bordersPolygon2, {a, d, e}), ());
}
} // namespace remove_empty_spaces_tests

View File

@@ -0,0 +1,47 @@
#include "poly_borders/poly_borders_tests/tools.hpp"
#include "poly_borders/borders_data.hpp"
#include "generator/borders.hpp"
#include "geometry/region2d.hpp"
#include "base/file_name_utils.hpp"
#include <string>
#include <vector>
using namespace platform::tests_support;
namespace
{
std::vector<m2::RegionD> ConvertFromPointsVector(
std::vector<std::vector<m2::PointD>> const & polygons)
{
std::vector<m2::RegionD> res;
res.reserve(polygons.size());
for (auto const & polygon : polygons)
res.emplace_back(polygon);
return res;
}
} // namespace
namespace poly_borders
{
std::shared_ptr<ScopedFile> CreatePolyBorderFileByPolygon(
std::string const & relativeDirPath, std::string const & name,
std::vector<std::vector<m2::PointD>> const & polygons)
{
std::string path = base::JoinPath(relativeDirPath, name + BordersData::kBorderExtension);
auto file = std::make_shared<ScopedFile>(path, ScopedFile::Mode::Create);
auto const targetDir = base::GetDirectory(file->GetFullPath());
auto const regions = ConvertFromPointsVector(polygons);
borders::DumpBorderToPolyFile(targetDir, name, regions);
return file;
}
} // namespace poly_borders

View File

@@ -0,0 +1,17 @@
#pragma once
#include "platform/platform_tests_support/scoped_file.hpp"
#include "geometry/point2d.hpp"
#include <memory>
#include <string>
#include <vector>
namespace poly_borders
{
std::shared_ptr<platform::tests_support::ScopedFile>
CreatePolyBorderFileByPolygon(std::string const & relativeDirPath,
std::string const & name,
std::vector<std::vector<m2::PointD>> const & polygons);
} // namespace poly_borders

View File

@@ -0,0 +1,10 @@
project(poly_borders_tool)
set(SRC poly_borders_tool.cpp)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
poly_borders
gflags::gflags
)

View File

@@ -0,0 +1,63 @@
#include "poly_borders/borders_data.hpp"
#include "poly_borders/help_structures.hpp"
#include "platform/platform.hpp"
#include "base/assert.hpp"
#include "base/exception.hpp"
#include "base/logging.hpp"
#include <exception>
#include <gflags/gflags.h>
DEFINE_string(borders_path, "", "Path to directory with *.poly files.");
DEFINE_string(output_path, "", "Path to target directory where the output *.poly files will be placed.");
using namespace poly_borders;
int main(int argc, char ** argv)
{
gflags::ParseCommandLineFlags(&argc, &argv, true);
gflags::SetUsageMessage("\n\n\tThe tool is used to process *.poly borders files. We use such files\n"
"\tto cut the planet into mwms in generator. The problem is that we have\n"
"\tempty spaces between neighbouring borders. This tool creates new borders\n"
"\tbased on input data by removing points from borders in such a way that the\n"
"\tchanged area of each border will not be too large.\n"
"\tArguments:\n"
"\t\t--borders_path=/path/to/directory/with/borders\n"
"\t\t--output_path=/path/to/directory/where/new/borders/will/be/placed\n");
if (FLAGS_borders_path.empty() || FLAGS_output_path.empty())
{
gflags::ShowUsageWithFlags("poly_borders_postprocessor");
return 0;
}
CHECK_NOT_EQUAL(FLAGS_borders_path, FLAGS_output_path, ());
CHECK(Platform::IsDirectory(FLAGS_borders_path), ("Cannot find directory:", FLAGS_borders_path));
CHECK(Platform::IsDirectory(FLAGS_output_path), ("Cannot find directory:", FLAGS_output_path));
try
{
BordersData data;
data.Init(FLAGS_borders_path);
data.RemoveEmptySpaceBetweenBorders();
data.PrintDiff();
data.DumpPolyFiles(FLAGS_output_path);
}
catch (RootException const & e)
{
LOG(LERROR, ("Core exception:", e.Msg()));
}
catch (std::exception const & e)
{
LOG(LERROR, ("Std exception:", e.what()));
}
catch (...)
{
LOG(LERROR, ("Unknown exception."));
}
return 0;
}

View File

@@ -0,0 +1,17 @@
project(skin_generator_tool)
set(SRC
generator.cpp
generator.hpp
main.cpp
)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
geometry
gflags::gflags
Qt6::Xml
Qt6::Svg
Qt6::Widgets
)

View File

@@ -0,0 +1,257 @@
#include "generator.hpp"
#include "base/logging.hpp"
#include "base/math.hpp"
#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>
#include <QtXml/QDomElement>
#include <QtXml/QDomDocument>
#include <QtCore/QDir>
namespace tools
{
namespace
{
static constexpr double kLargeIconSize = 24.0; // Size of the -l SVG icons
static constexpr double kMediumIconSize = 18.0; // size of the -m SVG icons
struct GreaterHeight
{
bool operator() (SkinGenerator::SymbolInfo const & left,
SkinGenerator::SymbolInfo const & right) const
{
QString symbolIDleft = left.m_fullFileName.left(left.m_fullFileName.lastIndexOf("."));
QString symbolIDright = right.m_fullFileName.left(right.m_fullFileName.lastIndexOf("."));
if (left.m_size.height() == right.m_size.height()) {
return symbolIDleft > symbolIDright;
}
return (left.m_size.height() > right.m_size.height());
}
};
struct MaxDimensions
{
uint32_t & m_width;
uint32_t & m_height;
MaxDimensions(uint32_t & width, uint32_t & height)
: m_width(width), m_height(height)
{
m_width = 0;
m_height = 0;
}
void operator()(SkinGenerator::SymbolInfo const & info)
{
m_width = std::max(std::max(m_width, m_height), static_cast<uint32_t>(info.m_size.width()));
m_height = std::max(std::max(m_width, m_height), static_cast<uint32_t>(info.m_size.height()));
}
};
uint32_t NextPowerOf2(uint32_t n)
{
n = n - 1;
n |= (n >> 1);
n |= (n >> 2);
n |= (n >> 4);
n |= (n >> 8);
n |= (n >> 16);
return n + 1;
}
}
void SkinGenerator::ProcessSymbols(std::string const & svgDataDir,
std::string const & skinName,
std::vector<QSize> const & symbolSizes,
std::vector<std::string> const & suffixes)
{
for (size_t j = 0; j < symbolSizes.size(); ++j)
{
QDir dir(QString(svgDataDir.c_str()));
QStringList fileNames = dir.entryList(QDir::Files);
QDir pngDir(dir.absolutePath() + "/png");
fileNames += pngDir.entryList(QDir::Files);
// Separate page for symbols.
m_pages.emplace_back(SkinPageInfo());
SkinPageInfo & page = m_pages.back();
page.m_dir = skinName.substr(0, skinName.find_last_of("/") + 1);
page.m_suffix = suffixes[j];
page.m_fileName = page.m_dir + "symbols" + page.m_suffix;
for (int i = 0; i < fileNames.size(); ++i)
{
QString const & fileName = fileNames.at(i);
QString symbolID = fileName.left(fileName.lastIndexOf("."));
if (fileName.endsWith(".svg"))
{
QString fullFileName = QString(dir.absolutePath()) + "/" + fileName;
if (m_svgRenderer.load(fullFileName))
{
QSize svgSize = m_svgRenderer.defaultSize(); // Size of the SVG file
// Capping svg symbol to kLargeIconSize maximum, keeping aspect ratio
/*if (svgSize.width() > kLargeIconSize)
{
auto const h = static_cast<float>(svgSize.height()) * kLargeIconSize / svgSize.width();
svgSize.setHeight(static_cast<int>(h));
svgSize.setWidth(kLargeIconSize);
}
if (svgSize.height() > kLargeIconSize)
{
auto const w = static_cast<float>(svgSize.width()) * kLargeIconSize / svgSize.height();
svgSize.setWidth(static_cast<int>(w));
svgSize.setHeight(kLargeIconSize);
}*/
// Scale symbol to required size
QSize size = svgSize * (symbolSizes[j].width() / kMediumIconSize);
page.m_symbols.emplace_back(size + QSize(4, 4), fullFileName, symbolID);
}
}
else if (fileName.toLower().endsWith(".png"))
{
QString fullFileName = QString(pngDir.absolutePath()) + "/" + fileName;
QPixmap pix(fullFileName);
QSize s = pix.size();
page.m_symbols.emplace_back(s + QSize(4, 4), fullFileName, symbolID);
}
}
}
}
bool SkinGenerator::RenderPages(uint32_t maxSize)
{
for (auto & page : m_pages)
{
std::sort(page.m_symbols.begin(), page.m_symbols.end(), GreaterHeight());
MaxDimensions dim(page.m_width, page.m_height);
for_each(page.m_symbols.begin(), page.m_symbols.end(), dim);
page.m_width = NextPowerOf2(page.m_width);
page.m_height = NextPowerOf2(page.m_height);
// Packing until we find a suitable rect.
while (true)
{
page.m_packer = m2::Packer(page.m_width, page.m_height);
page.m_packer.addOverflowFn(std::bind(&SkinGenerator::MarkOverflow, this), 10);
m_overflowDetected = false;
for (auto & s : page.m_symbols)
{
s.m_handle = page.m_packer.pack(static_cast<uint32_t>(s.m_size.width()),
static_cast<uint32_t>(s.m_size.height()));
if (m_overflowDetected)
break;
}
if (m_overflowDetected)
{
// Enlarge packing area and try again.
if (page.m_width == page.m_height)
page.m_width *= 2;
else
page.m_height *= 2;
if (page.m_width > maxSize)
{
page.m_width = maxSize;
page.m_height *= 2;
if (page.m_height > maxSize)
return false;
}
continue;
}
break;
}
LOG(LINFO, ("Texture size =", page.m_width, "x", page.m_height));
std::vector<uchar> imgData(page.m_width * page.m_height * 4, 0);
QImage img(imgData.data(), page.m_width, page.m_height, QImage::Format_ARGB32);
QPainter painter(&img);
painter.setClipping(true);
for (auto const & s : page.m_symbols)
{
m2::RectU dstRect = page.m_packer.find(s.m_handle).second;
QRect dstRectQt(dstRect.minX(), dstRect.minY(), dstRect.SizeX(), dstRect.SizeY());
painter.fillRect(dstRectQt, QColor(0, 0, 0, 0));
painter.setClipRect(dstRect.minX() + 2, dstRect.minY() + 2, dstRect.SizeX() - 4, dstRect.SizeY() - 4);
QRect renderRect(dstRect.minX() + 2, dstRect.minY() + 2, dstRect.SizeX() - 4, dstRect.SizeY() - 4);
QString fullLowerCaseName = s.m_fullFileName.toLower();
if (fullLowerCaseName.endsWith(".svg"))
{
m_svgRenderer.load(s.m_fullFileName);
m_svgRenderer.render(&painter, renderRect);
}
else if (fullLowerCaseName.endsWith(".png"))
{
QPixmap pix(s.m_fullFileName);
painter.drawPixmap(renderRect, pix);
}
}
std::string s = page.m_fileName + ".png";
LOG(LINFO, ("saving skin image into: ", s));
img.save(s.c_str());
}
return true;
}
void SkinGenerator::MarkOverflow()
{
m_overflowDetected = true;
}
bool SkinGenerator::WriteToFileNewStyle(std::string const &skinName)
{
QDomDocument doc = QDomDocument("skin");
QDomElement rootElem = doc.createElement("root");
doc.appendChild(rootElem);
for (auto const & p : m_pages)
{
QDomElement fileNode = doc.createElement("file");
fileNode.setAttribute("width", p.m_width);
fileNode.setAttribute("height", p.m_height);
rootElem.appendChild(fileNode);
for (auto const & s : p.m_symbols)
{
m2::RectU r = p.m_packer.find(s.m_handle).second;
QDomElement symbol = doc.createElement("symbol");
symbol.setAttribute("minX", r.minX());
symbol.setAttribute("minY", r.minY());
symbol.setAttribute("maxX", r.maxX());
symbol.setAttribute("maxY", r.maxY());
symbol.setAttribute("name", s.m_symbolID.toLower());
fileNode.appendChild(symbol);
}
}
QFile file(QString(skinName.c_str()));
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate))
return false;
QTextStream ts(&file);
ts.setEncoding(QStringConverter::Utf8);
ts << doc.toString();
return true;
}
} // namespace tools

View File

@@ -0,0 +1,69 @@
#pragma once
#include "geometry/rect2d.hpp"
#include "geometry/packer.hpp"
#include "coding/writer.hpp"
#include "base/base.hpp"
#include <QtGui/QPainter>
#include <QtGui/QImage>
#include <QtCore/QFileInfo>
#include <QtCore/QSize>
#include <QtSvg/QSvgRenderer>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
class QImage;
namespace tools
{
class SkinGenerator
{
public:
struct SymbolInfo
{
QSize m_size;
QString m_fullFileName;
QString m_symbolID;
m2::Packer::handle_t m_handle = {};
SymbolInfo() {}
SymbolInfo(QSize size, QString const & fullFileName, QString const & symbolID)
: m_size(size), m_fullFileName(fullFileName), m_symbolID(symbolID)
{}
};
using TSymbols = std::vector<SymbolInfo>;
struct SkinPageInfo
{
TSymbols m_symbols;
uint32_t m_width = 0;
uint32_t m_height = 0;
std::string m_fileName;
std::string m_dir;
std::string m_suffix;
m2::Packer m_packer;
};
void ProcessSymbols(std::string const & symbolsDir, std::string const & skinName,
std::vector<QSize> const & symbolSizes,
std::vector<std::string> const & suffix);
bool RenderPages(uint32_t maxSize);
bool WriteToFileNewStyle(std::string const & skinName);
private:
QSvgRenderer m_svgRenderer;
using TSkinPages = std::vector<SkinPageInfo>;
TSkinPages m_pages;
bool m_overflowDetected;
void MarkOverflow();
};
} // namespace tools

View File

@@ -0,0 +1,66 @@
#include "generator.hpp"
#include "base/logging.hpp"
#include <iostream>
#include <QApplication>
#include <QtCore/QFile>
#include <QtCore/QString>
#include <QtCore/QHash>
#include <gflags/gflags.h>
DEFINE_string(fontFileName, "../../data/01_dejavusans.ttf", "path to TrueType font file");
DEFINE_string(symbolsFile, "../../data/results.unicode", "file with 2bytes symbols for which the skin should be generated");
DEFINE_string(symbolsDir, "../../data/styles/symbols", "directory with svg symbol files");
DEFINE_int32(symbolWidth, 24, "width of the rendered symbol");
DEFINE_int32(symbolHeight, 24, "height of the rendered symbol");
DEFINE_string(skinName, "../../data/basic", "prefix for the skin and skinImage file name");
DEFINE_string(skinSuffix, "mdpi", "suffix for skinName<suffix>.skn and symbols<suffix>.png");
DEFINE_int32(searchIconWidth, 24, "width of the search category icon");
DEFINE_int32(searchIconHeight, 24, "height of the search category icon");
DEFINE_int32(maxSize, 4096, "max width/height of output textures");
int main(int argc, char *argv[])
{
// Used to lock the hash seed, so the order of XML attributes is always the same.
QHashSeed::setDeterministicGlobalSeed();
try
{
gflags::ParseCommandLineFlags(&argc, &argv, true);
QApplication app(argc, argv);
tools::SkinGenerator gen;
std::vector<QSize> symbolSizes;
symbolSizes.emplace_back(QSize(FLAGS_symbolWidth, FLAGS_symbolHeight));
std::vector<std::string> suffixes;
suffixes.push_back(FLAGS_skinSuffix);
gen.ProcessSymbols(FLAGS_symbolsDir, FLAGS_skinName, symbolSizes, suffixes);
if (!gen.RenderPages(FLAGS_maxSize))
{
LOG(LINFO, ("Error: The texture is overflown."));
return 1;
}
QString newSkin(FLAGS_skinName.c_str());
newSkin.replace("basic", "symbols");
auto const filename = newSkin.toStdString() + FLAGS_skinSuffix + ".sdf";
if (!gen.WriteToFileNewStyle(filename))
{
std::cerr << "Could not write file" << filename << std::endl;
return -1;
}
std::cout << "Done" << std::endl;
return 0;
}
catch (std::exception const & e)
{
std::cerr << "Exception " << e.what() << std::endl;
return -1;
}
}

View File

@@ -0,0 +1,30 @@
project(topography_generator_tool)
set(SRC
filters_impl.cpp
filters_impl.hpp
generator.cpp
generator.hpp
isolines_utils.hpp
isolines_profile.hpp
main.cpp
marching_squares/contours_builder.cpp
marching_squares/contours_builder.hpp
marching_squares/marching_squares.hpp
marching_squares/square.hpp
tile_filter.hpp
utils/contours.hpp
utils/contours_serdes.hpp
utils/values_provider.hpp
)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
generator
storage
indexer
platform
coding
gflags::gflags
)

View File

@@ -0,0 +1,24 @@
#include "topography_generator/filters_impl.hpp"
namespace topography_generator
{
std::vector<double> CalculateGaussianLinearKernel(double standardDeviation, double radiusFactor)
{
auto const kernelRadius = static_cast<int64_t>(ceil(radiusFactor * standardDeviation));
auto const kernelSize = 2 * kernelRadius + 1;
std::vector<double> linearKernel(kernelSize, 0);
double sum = 1.0;
linearKernel[kernelRadius] = 1.0;
for (int64_t i = 1; i <= kernelRadius; ++i)
{
double const val = exp(-i * i / (2 * standardDeviation * standardDeviation));
linearKernel[kernelRadius - i] = linearKernel[kernelRadius + i] = val;
sum += 2.0 * val;
}
for (auto & val : linearKernel)
val /= sum;
return linearKernel;
}
} // namespace topography_generator

View File

@@ -0,0 +1,209 @@
#pragma once
#include "topography_generator/utils/values_provider.hpp"
#include "geometry/latlon.hpp"
#include <algorithm>
#include <vector>
namespace topography_generator
{
template <typename ValueType>
void GetExtendedTile(ms::LatLon const & leftBottom, size_t stepsInDegree,
size_t tileSize, size_t tileSizeExtension,
ValuesProvider<ValueType> & valuesProvider,
std::vector<ValueType> & extTileValues)
{
size_t const extendedTileSize = tileSize + 2 * tileSizeExtension;
extTileValues.resize(extendedTileSize * extendedTileSize);
double const step = 1.0 / stepsInDegree;
double const offset = step * tileSizeExtension;
// Store values from North to South.
ms::LatLon startPos = ms::LatLon(leftBottom.m_lat + 1.0 + offset,
leftBottom.m_lon - offset);
for (size_t i = 0; i < extendedTileSize; ++i)
{
for (size_t j = 0; j < extendedTileSize; ++j)
{
auto pos = ms::LatLon(startPos.m_lat - i * step,
startPos.m_lon + j * step);
auto val = valuesProvider.GetValue(pos);
if (val == valuesProvider.GetInvalidValue() &&
((i < tileSizeExtension) || (i >= tileSizeExtension + tileSize) ||
(j < tileSizeExtension) || (j >= tileSizeExtension + tileSize)))
{
auto const ni = std::max(std::min(i, tileSizeExtension + tileSize - 1), tileSizeExtension);
auto const nj = std::max(std::min(j, tileSizeExtension + tileSize - 1), tileSizeExtension);
auto npos = ms::LatLon(startPos.m_lat - ni * step, startPos.m_lon + nj * step);
val = valuesProvider.GetValue(npos);
}
extTileValues[i * extendedTileSize + j] = val;
}
}
}
template <typename ValueType>
void ProcessWithLinearKernel(std::vector<double> const & kernel, size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues,
ValueType invalidValue)
{
auto const kernelSize = kernel.size();
auto const kernelRadius = kernel.size() / 2;
CHECK_LESS_OR_EQUAL(kernelRadius, tileOffset, ());
CHECK_GREATER(tileSize, tileOffset * 2, ());
CHECK_EQUAL(dstValues.size(), tileSize * tileSize, ());
std::vector<ValueType> tempValues(tileSize, 0);
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
tempValues[j] = 0.0;
auto const origValue = srcValues[i * tileSize + j];
if (origValue == invalidValue)
{
tempValues[j] = invalidValue;
}
else
{
for (size_t k = 0; k < kernelSize; ++k)
{
size_t const srcIndex = i * tileSize + j - kernelRadius + k;
auto srcValue = srcValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
tempValues[j] += kernel[k] * srcValue;
}
}
}
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
dstValues[i * tileSize + j] = tempValues[j];
}
}
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
tempValues[i] = 0.0;
auto const origValue = dstValues[i * tileSize + j];
if (origValue == invalidValue)
{
tempValues[i] = invalidValue;
}
else
{
for (size_t k = 0; k < kernelSize; ++k)
{
size_t const srcIndex = (i - kernelRadius + k) * tileSize + j;
auto srcValue = dstValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
tempValues[i] += kernel[k] * srcValue;
}
}
}
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
dstValues[i * tileSize + j] = tempValues[i];
}
}
}
template <typename ValueType>
void ProcessWithSquareKernel(std::vector<double> const & kernel, size_t kernelSize,
size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues,
ValueType invalidValue)
{
CHECK_EQUAL(kernelSize * kernelSize, kernel.size(), ());
size_t const kernelRadius = kernelSize / 2;
CHECK_LESS_OR_EQUAL(kernelRadius, tileOffset, ());
CHECK_GREATER(tileSize, tileOffset * 2, ());
CHECK_EQUAL(dstValues.size(), tileSize * tileSize, ());
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
size_t const dstIndex = i * tileSize + j;
auto const origValue = srcValues[dstIndex];
if (origValue == invalidValue)
{
dstValues[dstIndex] = invalidValue;
}
else
{
dstValues[dstIndex] = 0;
for (size_t ki = 0; ki < kernelSize; ++ki)
{
for (size_t kj = 0; kj < kernelSize; ++kj)
{
size_t const srcIndex = (i - kernelRadius + ki) * tileSize + j - kernelRadius + kj;
auto const srcValue = srcValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
dstValues[dstIndex] += kernel[ki * kernelSize + kj] * srcValue;
}
}
}
}
}
}
template <typename ValueType>
void ProcessMedian(size_t kernelRadius, size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues,
ValueType invalidValue)
{
CHECK_LESS_OR_EQUAL(kernelRadius, tileOffset, ());
CHECK_GREATER(tileSize, tileOffset * 2, ());
CHECK_EQUAL(dstValues.size(), tileSize * tileSize, ());
size_t const kernelSize = kernelRadius * 2 + 1;
std::vector<ValueType> kernel(kernelSize * kernelSize);
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
size_t const dstIndex = i * tileSize + j;
auto const origValue = srcValues[dstIndex];
if (origValue == invalidValue)
{
dstValues[dstIndex] = invalidValue;
}
else
{
size_t const startI = i - kernelRadius;
size_t const startJ = j - kernelRadius;
for (size_t ki = 0; ki < kernelSize; ++ki)
{
for (size_t kj = 0; kj < kernelSize; ++kj)
{
size_t const srcIndex = (startI + ki) * tileSize + startJ + kj;
auto srcValue = srcValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
kernel[ki * kernelSize + kj] = srcValue;
}
}
std::sort(kernel.begin(), kernel.end());
dstValues[dstIndex] = kernel[kernelRadius];
}
}
}
}
// Calculate separable kernel for Gaussian blur. https://en.wikipedia.org/wiki/Gaussian_blur
std::vector<double> CalculateGaussianLinearKernel(double standardDeviation, double radiusFactor);
} // namespace topography_generator

View File

@@ -0,0 +1,771 @@
#include "topography_generator/generator.hpp"
#include "topography_generator/isolines_utils.hpp"
#include "topography_generator/marching_squares/marching_squares.hpp"
#include "topography_generator/utils/contours_serdes.hpp"
#include "platform/platform.hpp"
#include "generator/srtm_parser.hpp"
#include "geometry/mercator.hpp"
#include "base/thread_pool_computational.hpp"
#include <algorithm>
#include <cmath>
#include <fstream>
#include <set>
#include <vector>
namespace topography_generator
{
namespace
{
size_t constexpr kArcSecondsInDegree = 60 * 60;
int constexpr kAsterTilesLatTop = 60;
int constexpr kAsterTilesLatBottom = -60;
void MercatorRectToTilesRange(m2::RectD const & rect,
int & left, int & bottom, int & right, int & top)
{
auto const leftBottom = mercator::ToLatLon(rect.LeftBottom());
auto const rightTop = mercator::ToLatLon(rect.RightTop());
left = static_cast<int>(floor(leftBottom.m_lon));
bottom = static_cast<int>(floor(leftBottom.m_lat));
right = std::min(179, static_cast<int>(floor(rightTop.m_lon)));
top = std::min(89, static_cast<int>(floor(rightTop.m_lat)));
}
std::string GetTileProfilesDir(std::string const & tilesDir)
{
return base::JoinPath(tilesDir, "tiles_profiles");
}
std::string GetTileProfilesFilePath(int lat, int lon, std::string const & profilesDir)
{
return GetIsolinesFilePath(lat, lon, profilesDir) + ".profiles";
}
std::string GetTilesDir(std::string const & tilesDir, std::string const & profileName)
{
return base::JoinPath(tilesDir, profileName);
}
void AppendTileProfile(std::string const & fileName, std::string const & profileName)
{
std::ofstream fout(fileName, std::ios::app);
fout << profileName << std::endl;
}
bool LoadTileProfiles(std::string const & fileName, std::set<std::string> & profileNames)
{
std::ifstream fin(fileName);
if (!fin)
return false;
std::string line;
while (std::getline(fin, line))
{
if (!line.empty())
profileNames.insert(line);
}
return true;
}
class SrtmProvider : public ValuesProvider<Altitude>
{
public:
explicit SrtmProvider(std::string const & srtmDir):
m_srtmManager(srtmDir)
{}
void SetPrefferedTile(ms::LatLon const & pos)
{
m_preferredTile = &m_srtmManager.GetTile(pos);
m_leftBottomOfPreferredTile = {std::floor(pos.m_lat), std::floor(pos.m_lon)};
}
Altitude GetValue(ms::LatLon const & pos) override
{
auto const alt = GetValueImpl(pos);
if (IsValidAltitude(alt))
return alt;
return GetMedianValue(pos);
}
Altitude GetInvalidValue() const override { return kInvalidAltitude; }
static bool IsValidAltitude(Altitude alt)
{
return alt != kInvalidAltitude && alt > -435 && alt < 8850;
}
private:
Altitude GetValueImpl(ms::LatLon pos)
{
if (m_preferredTile != nullptr)
{
using mercator::kPointEqualityEps;
// Each SRTM tile overlaps the top row in the bottom tile and the right row in the left tile.
// Try to prevent loading a new tile if the position can be found in the loaded one.
auto const latDist = pos.m_lat - m_leftBottomOfPreferredTile.m_lat;
auto const lonDist = pos.m_lon - m_leftBottomOfPreferredTile.m_lon;
if (latDist > -kPointEqualityEps && latDist < 1.0 + kPointEqualityEps &&
lonDist > -kPointEqualityEps && lonDist < 1.0 + kPointEqualityEps)
{
if (latDist < 0.0)
pos.m_lat += kPointEqualityEps;
else if (latDist >= 1.0)
pos.m_lat -= kPointEqualityEps;
if (lonDist < 0.0)
pos.m_lon += kPointEqualityEps;
else if (lonDist >= 1.0)
pos.m_lon -= kPointEqualityEps;
return m_preferredTile->GetAltitude(pos);
}
}
return m_srtmManager.GetAltitude(pos);
}
Altitude GetMedianValue(ms::LatLon const & pos)
{
// Look around the position with invalid altitude
// and return median of surrounding valid altitudes.
double const step = 1.0 / kArcSecondsInDegree;
int const kMaxKernelRadius = 3;
std::vector<Altitude> kernel;
int kernelRadius = 0;
while (kernel.empty() && kernelRadius < kMaxKernelRadius)
{
++kernelRadius;
auto const kernelSize = static_cast<size_t>(kernelRadius * 2 + 1);
kernel.reserve(4 * (kernelSize - 1));
for (int i = -kernelRadius; i <= kernelRadius; ++i)
{
for (int j = -kernelRadius; j <= kernelRadius; ++j)
{
if (abs(i) != kernelRadius && abs(j) != kernelRadius)
continue;
auto const alt = GetValueImpl({pos.m_lat + i * step, pos.m_lon + j * step});
if (IsValidAltitude(alt))
kernel.push_back(alt);
}
}
}
if (kernel.empty())
{
LOG(LWARNING, ("Can't fix invalid value", GetValueImpl(pos), "at the position", pos));
return kInvalidAltitude;
}
std::nth_element(kernel.begin(), kernel.begin() + kernel.size() / 2, kernel.end());
return kernel[kernel.size() / 2];
}
generator::SrtmTileManager m_srtmManager;
generator::SrtmTile const * m_preferredTile = nullptr;
ms::LatLon m_leftBottomOfPreferredTile;
};
class RawAltitudesTile : public ValuesProvider<Altitude>
{
public:
RawAltitudesTile(std::vector<Altitude> const & values,
int leftLon, int bottomLat)
: m_values(values)
, m_leftLon(leftLon)
, m_bottomLat(bottomLat)
{}
/// @todo Should we use the same approach as in SrtmTile::GetTriangleHeight/GetBilinearHeight?
/// This function is used in ASTER filter only.
Altitude GetValue(ms::LatLon const & pos) override
{
double ln = pos.m_lon - m_leftLon;
double lt = pos.m_lat - m_bottomLat;
lt = 1 - lt; // From North to South, the same direction as inside the SRTM tiles.
auto const row = std::lround(kArcSecondsInDegree * lt);
auto const col = std::lround(kArcSecondsInDegree * ln);
auto const ix = row * (kArcSecondsInDegree + 1) + col;
CHECK(ix < m_values.size(), (pos));
return m_values[ix];
}
Altitude GetInvalidValue() const override { return kInvalidAltitude; }
private:
std::vector<Altitude> const & m_values;
int m_leftLon;
int m_bottomLat;
};
class SeamlessAltitudeProvider : public ValuesProvider<Altitude>
{
public:
using IsOnBorderFn = std::function<bool (ms::LatLon const & pos)>;
SeamlessAltitudeProvider(ValuesProvider<Altitude> & originalProvider,
ValuesProvider<Altitude> & filteredProvider,
IsOnBorderFn && isOnBorderFn)
: m_originalProvider(originalProvider)
, m_filteredProvider(filteredProvider)
, m_isOnBorderFn(std::move(isOnBorderFn))
{}
Altitude GetValue(ms::LatLon const & pos) override
{
if (m_isOnBorderFn(pos))
{
// Check that we have original neighboring tile, use filtered if haven't.
auto const alt = m_originalProvider.GetValue(pos);
if (alt != kInvalidAltitude)
return alt;
}
return m_filteredProvider.GetValue(pos);
}
Altitude GetInvalidValue() const override { return kInvalidAltitude; }
private:
ValuesProvider<Altitude> & m_originalProvider;
ValuesProvider<Altitude> & m_filteredProvider;
IsOnBorderFn m_isOnBorderFn;
};
class TileIsolinesTask
{
public:
TileIsolinesTask(int left, int bottom, int right, int top, std::string const & srtmDir,
TileIsolinesParams const * params, bool forceRegenerate)
: m_srtmDir(srtmDir)
, m_srtmProvider(srtmDir)
, m_params(params)
, m_forceRegenerate(forceRegenerate)
{
CHECK(params != nullptr, ());
Init(left, bottom, right, top);
}
TileIsolinesTask(int left, int bottom, int right, int top, std::string const & srtmDir,
TileIsolinesProfileParams const * profileParams, bool forceRegenerate)
: m_srtmDir(srtmDir)
, m_srtmProvider(srtmDir)
, m_profileParams(profileParams)
, m_forceRegenerate(forceRegenerate)
{
CHECK(profileParams != nullptr, ());
Init(left, bottom, right, top);
}
void Do()
{
for (int lat = m_bottom; lat <= m_top; ++lat)
{
for (int lon = m_left; lon <= m_right; ++lon)
ProcessTile(lat, lon);
}
}
private:
void Init(int left, int bottom, int right, int top)
{
CHECK(right >= -179 && right <= 180, (right));
CHECK(left >= -180 && left <= 179, (left));
CHECK(top >= -89 && top <= 90, (top));
CHECK(bottom >= -90 && bottom <= 89, (bottom));
m_left = left;
m_bottom = bottom;
m_right = right;
m_top = top;
}
void ProcessTile(int lat, int lon)
{
auto const tileName = GetIsolinesTileBase(lat, lon);
if (m_profileParams != nullptr)
{
auto const profilesPath = GetTileProfilesFilePath(lat, lon, m_profileParams->m_tilesProfilesDir);
if (!GetPlatform().IsFileExistsByFullPath(profilesPath))
{
LOG(LINFO, ("SRTM tile", tileName, "doesn't have profiles, skip processing."));
return;
}
}
auto const & pl = GetPlatform();
if (!pl.IsFileExistsByFullPath(base::JoinPath(m_srtmDir, tileName + ".hgt")) &&
!pl.IsFileExistsByFullPath(generator::SrtmTile::GetPath(m_srtmDir, tileName)))
{
LOG(LINFO, ("SRTM tile", tileName, "doesn't exist, skip processing."));
return;
}
std::ostringstream os;
os << tileName << " (" << lat << ", " << lon << ")";
m_debugId = os.str();
if (m_profileParams != nullptr)
{
auto const profilesPath = GetTileProfilesFilePath(lat, lon, m_profileParams->m_tilesProfilesDir);
std::set<std::string> profileNames;
CHECK(LoadTileProfiles(profilesPath, profileNames) && !profileNames.empty(), (tileName));
for (auto const & profileName : profileNames)
{
auto const & params = m_profileParams->m_profiles.at(profileName);
ProcessTile(lat, lon, tileName, profileName, params);
}
}
else
{
ProcessTile(lat, lon, tileName, "none", *m_params);
}
}
void ProcessTile(int lat, int lon, std::string const & tileName, std::string const & profileName,
TileIsolinesParams const & params)
{
auto const outFile = GetIsolinesFilePath(lat, lon, params.m_outputDir);
if (!m_forceRegenerate && GetPlatform().IsFileExistsByFullPath(outFile))
{
LOG(LINFO, ("Isolines for", tileName, ", profile", profileName,
"are ready, skip processing."));
return;
}
LOG(LINFO, ("Begin generating isolines for tile", tileName, ", profile", profileName));
m_srtmProvider.SetPrefferedTile({lat + 0.5, lon + 0.5});
Contours<Altitude> contours;
if (!params.m_filters.empty() && (lat >= kAsterTilesLatTop || lat < kAsterTilesLatBottom))
{
// Filter tiles converted from ASTER, cause they are noisy enough.
std::vector<Altitude> filteredValues = FilterTile(params.m_filters,
ms::LatLon(lat, lon),
kArcSecondsInDegree,
kArcSecondsInDegree + 1,
m_srtmProvider);
RawAltitudesTile filteredProvider(filteredValues, lon, lat);
GenerateSeamlessContours(lat, lon, params, filteredProvider, contours);
}
else
{
GenerateSeamlessContours(lat, lon, params, m_srtmProvider, contours);
}
LOG(LINFO, ("Isolines for tile", tileName, ", profile", profileName,
"min altitude", contours.m_minValue, "max altitude",
contours.m_maxValue, "invalid values count", contours.m_invalidValuesCount));
if (params.m_simplificationZoom > 0)
SimplifyContours(params.m_simplificationZoom, contours);
SaveContrours(outFile, std::move(contours));
LOG(LINFO, ("End generating isolines for tile", tileName, ", profile", profileName));
}
void GenerateSeamlessContours(int lat, int lon, TileIsolinesParams const & params,
ValuesProvider<Altitude> & altProvider,
Contours<Altitude> & contours)
{
// Avoid seam between SRTM and ASTER.
if ((lat == kAsterTilesLatTop) || (lat == kAsterTilesLatBottom - 1))
{
m_srtmProvider.SetPrefferedTile(ms::LatLon(lat == kAsterTilesLatTop ? lat - 0.5 : lat + 0.5, lon));
SeamlessAltitudeProvider seamlessAltProvider(m_srtmProvider, altProvider,
[](ms::LatLon const & pos)
{
// In case when two altitudes sources are used for altitudes extraction,
// for the same position on the border could be returned different altitudes.
// Force to use altitudes near the srtm/aster border from srtm source,
// it helps to avoid contours gaps due to different altitudes for equal positions.
return fabs(pos.m_lat - kAsterTilesLatTop) < mercator::kPointEqualityEps ||
fabs(pos.m_lat - kAsterTilesLatBottom) < mercator::kPointEqualityEps;
});
GenerateContours(lat, lon, params, seamlessAltProvider, contours);
}
else
{
GenerateContours(lat, lon, params, altProvider, contours);
}
}
void GenerateContours(int lat, int lon, TileIsolinesParams const & params,
ValuesProvider<Altitude> & altProvider, Contours<Altitude> & contours)
{
auto const leftBottom = ms::LatLon(lat, lon);
auto const rightTop = ms::LatLon(lat + 1.0, lon + 1.0);
auto const squaresStep = 1.0 / kArcSecondsInDegree * params.m_latLonStepFactor;
MarchingSquares<Altitude> squares(leftBottom, rightTop,
squaresStep, params.m_alitudesStep,
altProvider, m_debugId);
squares.GenerateContours(contours);
}
int m_left;
int m_bottom;
int m_right;
int m_top;
std::string m_srtmDir;
SrtmProvider m_srtmProvider;
TileIsolinesParams const * m_params = nullptr;
TileIsolinesProfileParams const * m_profileParams = nullptr;
bool m_forceRegenerate;
std::string m_debugId;
};
template <typename ParamsType>
void RunGenerateIsolinesTasks(int left, int bottom, int right, int top,
std::string const & srtmPath, ParamsType const & params,
long threadsCount, long maxCachedTilesPerThread,
bool forceRegenerate)
{
std::vector<std::unique_ptr<TileIsolinesTask>> tasks;
CHECK_GREATER(right, left, ());
CHECK_GREATER(top, bottom, ());
int tilesRowPerTask = top - bottom;
int tilesColPerTask = right - left;
if (tilesRowPerTask * tilesColPerTask <= static_cast<long>(threadsCount))
{
tilesRowPerTask = 1;
tilesColPerTask = 1;
}
else
{
while (tilesRowPerTask * tilesColPerTask > static_cast<long>(maxCachedTilesPerThread))
{
if (tilesRowPerTask > tilesColPerTask)
tilesRowPerTask = (tilesRowPerTask + 1) / 2;
else
tilesColPerTask = (tilesColPerTask + 1) / 2;
}
}
base::ComputationalThreadPool threadPool(threadsCount);
for (int lat = bottom; lat < top; lat += tilesRowPerTask)
{
int const topLat = std::min(lat + tilesRowPerTask - 1, top - 1);
for (int lon = left; lon < right; lon += tilesColPerTask)
{
int const rightLon = std::min(lon + tilesColPerTask - 1, right - 1);
auto task = std::make_unique<TileIsolinesTask>(lon, lat, rightLon, topLat, srtmPath, &params,
forceRegenerate);
threadPool.SubmitWork([task = std::move(task)](){ task->Do(); });
}
}
}
} // namespace
Generator::Generator(std::string const & srtmPath, long threadsCount,
long maxCachedTilesPerThread, bool forceRegenerate)
: m_threadsCount(threadsCount)
, m_maxCachedTilesPerThread(maxCachedTilesPerThread)
, m_srtmPath(srtmPath)
, m_forceRegenerate(forceRegenerate)
{}
void Generator::GenerateIsolines(int left, int bottom, int right, int top,
TileIsolinesParams const & params)
{
RunGenerateIsolinesTasks(left, bottom, right, top, m_srtmPath, params,
m_threadsCount, m_maxCachedTilesPerThread, m_forceRegenerate);
}
void Generator::GenerateIsolines(int left, int bottom, int right, int top,
std::string const & tilesProfilesDir)
{
TileIsolinesProfileParams params(m_profileToTileParams, tilesProfilesDir);
RunGenerateIsolinesTasks(left, bottom, right, top, m_srtmPath, params,
m_threadsCount, m_maxCachedTilesPerThread, m_forceRegenerate);
}
void Generator::GenerateIsolinesForCountries()
{
auto const & pl = GetPlatform();
if (!pl.IsFileExistsByFullPath(m_isolinesTilesOutDir) &&
!pl.MkDirRecursively(m_isolinesTilesOutDir))
{
LOG(LERROR, ("Can't create directory", m_isolinesTilesOutDir));
return;
}
std::set<std::string> checkedProfiles;
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const profileName = countryParams.second.m_profileName;
if (checkedProfiles.find(profileName) != checkedProfiles.end())
continue;
checkedProfiles.insert(profileName);
auto const profileTilesDir = GetTilesDir(m_isolinesTilesOutDir, profileName);
if (!pl.IsFileExistsByFullPath(profileTilesDir) &&
!pl.MkDirChecked(profileTilesDir))
{
LOG(LERROR, ("Can't create directory", profileTilesDir));
return;
}
}
auto const tmpTileProfilesDir = GetTileProfilesDir(m_isolinesTilesOutDir);
Platform::RmDirRecursively(tmpTileProfilesDir);
if (!pl.MkDirChecked(tmpTileProfilesDir))
{
LOG(LERROR, ("Can't create directory", tmpTileProfilesDir));
return;
}
m2::RectI boundingRect;
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const & countryId = countryParams.first;
auto const & params = countryParams.second;
auto const countryFile = GetIsolinesFilePath(countryId, m_isolinesCountriesOutDir);
if (!m_forceRegenerate && pl.IsFileExistsByFullPath(countryFile))
{
LOG(LINFO, ("Isolines for", countryId, "are ready, skip processing."));
continue;
}
m2::RectD countryRect;
std::vector<m2::RegionD> countryRegions;
GetCountryRegions(countryId, countryRect, countryRegions);
for (auto const & region : countryRegions)
{
countryRect = region.GetRect();
int left, bottom, right, top;
MercatorRectToTilesRange(countryRect, left, bottom, right, top);
boundingRect.Add(m2::PointI(left, bottom));
boundingRect.Add(m2::PointI(right, top));
for (int lat = bottom; lat <= top; ++lat)
{
for (int lon = left; lon <= right; ++lon)
{
if (params.NeedSkipTile(lat, lon))
continue;
auto const tileProfilesFilePath = GetTileProfilesFilePath(lat, lon, tmpTileProfilesDir);
AppendTileProfile(tileProfilesFilePath, params.m_profileName);
}
}
}
}
if (!boundingRect.IsValid())
return;
LOG(LINFO, ("Generate isolines for tiles rect", boundingRect));
GenerateIsolines(boundingRect.LeftBottom().x, boundingRect.LeftBottom().y,
boundingRect.RightTop().x + 1, boundingRect.RightTop().y + 1, tmpTileProfilesDir);
}
void Generator::PackIsolinesForCountry(storage::CountryId const & countryId,
IsolinesPackingParams const & params)
{
PackIsolinesForCountry(countryId, params, nullptr /*needSkipTileFn*/);
}
void Generator::PackIsolinesForCountry(storage::CountryId const & countryId,
IsolinesPackingParams const & params,
NeedSkipTileFn const & needSkipTileFn)
{
auto const outFile = GetIsolinesFilePath(countryId, params.m_outputDir);
if (!m_forceRegenerate && GetPlatform().IsFileExistsByFullPath(outFile))
{
LOG(LINFO, ("Isolines for", countryId, "are ready, skip processing."));
return;
}
// TODO : prepare simplified and filtered isolones for all geom levels here
// (ATM its the most detailed geom3 only) instead of in the generator
// to skip re-doing it for every maps gen. And it'll be needed anyway
// for the longer term vision to supply isolines in separately downloadable files.
LOG(LINFO, ("Begin packing isolines for country", countryId));
m2::RectD countryRect;
std::vector<m2::RegionD> countryRegions;
GetCountryRegions(countryId, countryRect, countryRegions);
int left, bottom, right, top;
MercatorRectToTilesRange(countryRect, left, bottom, right, top);
Contours<Altitude> countryIsolines;
countryIsolines.m_minValue = std::numeric_limits<Altitude>::max();
countryIsolines.m_maxValue = std::numeric_limits<Altitude>::min();
for (int lat = bottom; lat <= top; ++lat)
{
for (int lon = left; lon <= right; ++lon)
{
if (needSkipTileFn && needSkipTileFn(lat, lon))
continue;
Contours<Altitude> isolines;
auto const tileFilePath = GetIsolinesFilePath(lat, lon, params.m_isolinesTilesPath);
if (!LoadContours(tileFilePath, isolines))
continue;
LOG(LINFO, ("Begin packing isolines from tile", tileFilePath));
CropContours(countryRect, countryRegions, params.m_maxIsolineLength,
params.m_alitudesStepFactor, isolines);
// Simplification is done already while processing tiles in ProcessTile().
// But now a different country-specific simpificationZoom could be applied.
if (params.m_simplificationZoom > 0)
SimplifyContours(params.m_simplificationZoom, isolines);
countryIsolines.m_minValue = std::min(isolines.m_minValue, countryIsolines.m_minValue);
countryIsolines.m_maxValue = std::max(isolines.m_maxValue, countryIsolines.m_maxValue);
countryIsolines.m_valueStep = isolines.m_valueStep;
countryIsolines.m_invalidValuesCount += isolines.m_invalidValuesCount;
for (auto & levelIsolines : isolines.m_contours)
{
auto & dst = countryIsolines.m_contours[levelIsolines.first];
std::move(levelIsolines.second.begin(), levelIsolines.second.end(),
std::back_inserter(dst));
}
LOG(LINFO, ("End packing isolines from tile", tileFilePath));
}
}
LOG(LINFO, ("End packing isolines for country", countryId,
"min altitude", countryIsolines.m_minValue,
"max altitude", countryIsolines.m_maxValue));
SaveContrours(outFile, std::move(countryIsolines));
LOG(LINFO, ("Isolines saved to", outFile));
}
void Generator::PackIsolinesForCountries()
{
if (!GetPlatform().IsFileExistsByFullPath(m_isolinesCountriesOutDir) &&
!GetPlatform().MkDirRecursively(m_isolinesCountriesOutDir))
{
LOG(LERROR, ("Can't create directory", m_isolinesCountriesOutDir));
return;
}
base::ComputationalThreadPool threadPool(m_threadsCount);
size_t taskInd = 0;
size_t tasksCount = m_countriesToGenerate.m_countryParams.size();
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const & countryId = countryParams.first;
auto const & params = countryParams.second;
threadPool.SubmitWork([this, countryId, taskInd, tasksCount, params]()
{
LOG(LINFO, ("Begin task", taskInd, "/", tasksCount, countryId));
auto const & packingParams = m_profileToPackingParams.at(params.m_profileName);
PackIsolinesForCountry(countryId, packingParams,
[&params](int lat, int lon){ return params.NeedSkipTile(lat, lon); });
LOG(LINFO, ("End task", taskInd, "/", tasksCount, countryId));
});
++taskInd;
}
}
void Generator::InitCountryInfoGetter(std::string const & dataDir)
{
CHECK(m_infoReader == nullptr, ());
GetPlatform().SetResourceDir(dataDir);
m_infoReader = storage::CountryInfoReader::CreateCountryInfoReader(GetPlatform());
CHECK(m_infoReader, ());
}
void Generator::InitProfiles(std::string const & isolinesProfilesFileName,
std::string const & countriesToGenerateFileName,
std::string const & isolinesTilesOutDir,
std::string const & isolinesCountriesOutDir)
{
CHECK(Deserialize(isolinesProfilesFileName, m_profilesCollection), ());
CHECK(Deserialize(countriesToGenerateFileName, m_countriesToGenerate), ());
auto const & profiles = m_profilesCollection.m_profiles;
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const & params = countryParams.second;
CHECK(profiles.find(params.m_profileName) != profiles.end(),
("Unknown profile name", params.m_profileName));
}
m_isolinesTilesOutDir = isolinesTilesOutDir;
m_isolinesCountriesOutDir = isolinesCountriesOutDir;
for (auto const & profile : m_profilesCollection.m_profiles)
{
auto const & profileName = profile.first;
auto const & profileParams = profile.second;
TileIsolinesParams tileParams;
tileParams.m_outputDir = GetTilesDir(isolinesTilesOutDir, profileName);
tileParams.m_latLonStepFactor = profileParams.m_latLonStepFactor;
tileParams.m_alitudesStep = profileParams.m_alitudesStep;
tileParams.m_simplificationZoom = profileParams.m_simplificationZoom;
if (profileParams.m_medianFilterR > 0)
tileParams.m_filters.emplace_back(std::make_unique<MedianFilter<Altitude>>(profileParams.m_medianFilterR));
if (profileParams.m_gaussianFilterStDev > 0.0 && profileParams.m_gaussianFilterRFactor > 0)
{
tileParams.m_filters.emplace_back(
std::make_unique<GaussianFilter<Altitude>>(profileParams.m_gaussianFilterStDev,
profileParams.m_gaussianFilterRFactor));
}
m_profileToTileParams.emplace(profileName, std::move(tileParams));
IsolinesPackingParams packingParams;
packingParams.m_outputDir = isolinesCountriesOutDir;
packingParams.m_simplificationZoom = 0;
packingParams.m_alitudesStepFactor = 1;
packingParams.m_isolinesTilesPath = GetTilesDir(isolinesTilesOutDir, profileName);
packingParams.m_maxIsolineLength = profileParams.m_maxIsolinesLength;
m_profileToPackingParams.emplace(profileName, std::move(packingParams));
}
}
void Generator::GetCountryRegions(storage::CountryId const & countryId, m2::RectD & countryRect,
std::vector<m2::RegionD> & countryRegions)
{
countryRect = m_infoReader->GetLimitRectForLeaf(countryId);
size_t id;
for (id = 0; id < m_infoReader->GetCountries().size(); ++id)
{
if (m_infoReader->GetCountries().at(id).m_countryId == countryId)
break;
}
CHECK_LESS(id, m_infoReader->GetCountries().size(), ());
/// @todo Refactor using Memory[Mapped] reader for countries.
std::lock_guard guard(m_infoMutex);
m_infoReader->LoadRegionsFromDisk(id, countryRegions);
}
} // namespace topography_generator

View File

@@ -0,0 +1,103 @@
#pragma once
#include "topography_generator/isolines_utils.hpp"
#include "topography_generator/isolines_profile.hpp"
#include "topography_generator/tile_filter.hpp"
#include "storage/country_info_getter.hpp"
#include "geometry/rect2d.hpp"
#include "geometry/region2d.hpp"
#include <memory>
#include <string>
namespace topography_generator
{
struct TileIsolinesParams
{
Altitude m_alitudesStep = 10;
size_t m_latLonStepFactor = 1;
int m_simplificationZoom = 17; // Value == 0 disables simplification.
FiltersSequence<Altitude> m_filters;
std::string m_outputDir;
};
using ProfileToTileIsolinesParams = std::map<std::string, TileIsolinesParams>;
struct TileIsolinesProfileParams
{
TileIsolinesProfileParams(ProfileToTileIsolinesParams const & profiles,
std::string const & tilesProfilesDir)
: m_profiles(profiles)
, m_tilesProfilesDir(tilesProfilesDir)
{}
ProfileToTileIsolinesParams const & m_profiles;
std::string m_tilesProfilesDir;
};
struct IsolinesPackingParams
{
size_t m_maxIsolineLength = 1000;
int m_simplificationZoom = 17; // Value == 0 disables simplification.
size_t m_alitudesStepFactor = 1;
std::string m_isolinesTilesPath;
std::string m_outputDir;
};
using ProfileToIsolinesPackingParams = std::map<std::string, IsolinesPackingParams>;
class Generator
{
public:
Generator(std::string const & srtmPath, long threadsCount, long maxCachedTilesPerThread,
bool forceRegenerate);
void InitCountryInfoGetter(std::string const & dataDir);
void GenerateIsolines(int left, int bottom, int right, int top,
TileIsolinesParams const & params);
void PackIsolinesForCountry(storage::CountryId const & countryId,
IsolinesPackingParams const & params);
void InitProfiles(std::string const & isolinesProfilesFileName,
std::string const & countriesToGenerateFileName,
std::string const & isolinesTilesOutDir,
std::string const & isolinesCountriesOutDir);
void GenerateIsolinesForCountries();
void PackIsolinesForCountries();
private:
void GenerateIsolines(int left, int bottom, int right, int top,
std::string const & tilesProfilesDir);
using NeedSkipTileFn = std::function<bool(int lat, int lon)>;
void PackIsolinesForCountry(storage::CountryId const & countryId,
IsolinesPackingParams const & params,
NeedSkipTileFn const & needSkipTileFn);
void GetCountryRegions(storage::CountryId const & countryId, m2::RectD & countryRect,
std::vector<m2::RegionD> & countryRegions);
std::string m_isolinesTilesOutDir;
std::string m_isolinesCountriesOutDir;
CountriesToGenerate m_countriesToGenerate;
IsolinesProfilesCollection m_profilesCollection;
ProfileToTileIsolinesParams m_profileToTileParams;
ProfileToIsolinesPackingParams m_profileToPackingParams;
std::mutex m_infoMutex;
std::unique_ptr<storage::CountryInfoReader> m_infoReader;
// They can't be negative, it is done to avoid compiler warnings.
long m_threadsCount;
long m_maxCachedTilesPerThread;
std::string m_srtmPath;
bool m_forceRegenerate;
};
} // namespace topography_generator

View File

@@ -0,0 +1,139 @@
#pragma once
#include "topography_generator/isolines_utils.hpp"
#include "storage/storage_defines.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/serdes_json.hpp"
#include "base/visitor.hpp"
#include <map>
#include <string>
#include <unordered_set>
namespace topography_generator
{
struct IsolinesProfile
{
uint32_t m_alitudesStep = 10;
uint8_t m_latLonStepFactor = 1;
uint32_t m_maxIsolinesLength = 1000;
uint8_t m_simplificationZoom = 17; // Value == 0 disables simplification.
uint8_t m_medianFilterR = 1; // Value == 0 disables filter.
double m_gaussianFilterStDev = 2.0; // Value == 0.0 disables filter.
double m_gaussianFilterRFactor = 1.0; // Value == 0.0 disables filter.
DECLARE_VISITOR_AND_DEBUG_PRINT(IsolinesProfile, visitor(m_alitudesStep, "alitudesStep"),
visitor(m_latLonStepFactor, "latLonStepFactor"),
visitor(m_maxIsolinesLength, "maxIsolinesLength"),
visitor(m_simplificationZoom, "simplificationZoom"),
visitor(m_medianFilterR, "medianFilterR"),
visitor(m_gaussianFilterStDev, "gaussianFilterStDev"),
visitor(m_gaussianFilterRFactor, "gaussianFilterRFactor"))
};
struct IsolinesProfilesCollection
{
std::map<std::string, IsolinesProfile> m_profiles;
DECLARE_VISITOR_AND_DEBUG_PRINT(IsolinesProfilesCollection, visitor(m_profiles, "profiles"))
};
struct TileCoord
{
TileCoord() = default;
TileCoord(int bottomLat, int leftLon): m_leftLon(leftLon), m_bottomLat(bottomLat) {}
int32_t m_leftLon = 0;
int32_t m_bottomLat = 0;
bool operator==(TileCoord const & rhs) const
{
return m_leftLon == rhs.m_leftLon && m_bottomLat == rhs.m_bottomLat;
}
DECLARE_VISITOR_AND_DEBUG_PRINT(TileCoord, visitor(m_bottomLat, "bottomLat"),
visitor(m_leftLon, "leftLon"))
};
struct TileCoordHash
{
size_t operator()(TileCoord const & coord) const
{
return (static_cast<size_t>(coord.m_leftLon) << 32u) | static_cast<size_t>(coord.m_bottomLat);
}
};
struct CountryIsolinesParams
{
std::string m_profileName;
std::unordered_set<TileCoord, TileCoordHash> m_tileCoordsSubset;
bool m_tilesAreBanned;
bool NeedSkipTile(int lat, int lon) const
{
if (m_tileCoordsSubset.empty())
return false;
TileCoord coord(lat, lon);
auto const found = m_tileCoordsSubset.find(coord) != m_tileCoordsSubset.end();
return m_tilesAreBanned == found;
}
DECLARE_VISITOR_AND_DEBUG_PRINT(CountryIsolinesParams, visitor(m_profileName, "profileName"),
visitor(m_tileCoordsSubset, "tileCoordsSubset"),
visitor(m_tilesAreBanned, "tilesAreBanned"))
};
struct CountriesToGenerate
{
std::map<storage::CountryId, CountryIsolinesParams> m_countryParams;
DECLARE_VISITOR_AND_DEBUG_PRINT(CountriesToGenerate, visitor(m_countryParams, "countryParams"))
};
template<typename DataType>
bool Serialize(std::string const & fileName, DataType const & data)
{
try
{
FileWriter writer(fileName);
coding::SerializerJson<FileWriter> ser(writer);
ser(data);
return true;
}
catch (base::Json::Exception & ex)
{
LOG(LERROR, ("Serialization to json failed, file name", fileName, ", reason:", ex.Msg()));
}
catch (FileWriter::Exception const & ex)
{
LOG(LERROR, ("Can't write file", fileName, ", reason:", ex.Msg()));
}
return false;
}
template<typename DataType>
bool Deserialize(std::string const & fileName, DataType & data)
{
try
{
FileReader reader(fileName);
NonOwningReaderSource source(reader);
coding::DeserializerJson des(source);
des(data);
return true;
}
catch (base::Json::Exception & ex)
{
LOG(LERROR, ("Deserialization from json failed, file name", fileName, ", reason:", ex.Msg()));
}
catch (FileReader::Exception const & ex)
{
LOG(LERROR, ("Can't read file", fileName, ", reason:", ex.Msg()));
}
return false;
}
} // namespace topography_generator

View File

@@ -0,0 +1,35 @@
#pragma once
#include "generator/srtm_parser.hpp"
#include "storage/storage_defines.hpp"
#include "indexer/feature_altitude.hpp"
#include "base/file_name_utils.hpp"
#include <string>
namespace topography_generator
{
using Altitude = geometry::Altitude;
Altitude constexpr kInvalidAltitude = geometry::kInvalidAltitude;
constexpr char const * const kIsolinesExt = ".isolines";
inline std::string GetIsolinesTileBase(int bottomLat, int leftLon)
{
auto const centerPoint = ms::LatLon(bottomLat + 0.5, leftLon + 0.5);
return generator::SrtmTile::GetBase(centerPoint);
}
inline std::string GetIsolinesFilePath(int bottomLat, int leftLon, std::string const & dir)
{
auto const fileName = GetIsolinesTileBase(bottomLat, leftLon);
return base::JoinPath(dir, fileName + kIsolinesExt);
}
inline std::string GetIsolinesFilePath(storage::CountryId const & countryId, std::string const & dir)
{
return base::JoinPath(dir, countryId + kIsolinesExt);
}
} // namespace topography_generator

View File

@@ -0,0 +1,209 @@
#include "generator.hpp"
#include "generator/utils.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <gflags/gflags.h>
#include <cstdlib>
// Common option for all modes
DEFINE_bool(force, false, "Force to regenerate isolines for tiles and countries.");
// Options for automatic isolines generating mode.
DEFINE_string(profiles_path, "", "Automatic isolines generating mode. "
"Path to a json file with isolines profiles.");
DEFINE_string(countries_to_generate_path, "", "Automatic isolines generating mode. "
"Path to a json file with countries to generate.");
DEFINE_string(tiles_isolines_out_dir, "", "Automatic isolines generating mode. Path to output "
"intermediate directory with tiles isolines.");
DEFINE_string(countries_isolines_out_dir, "", "Automatic isolines generating mode. "
"Path to output directory with countries isolines.");
// Common option for automatic isolines generating mode and custom packing mode.
DEFINE_string(data_dir, "", "Path to data directory.");
// Common option for automatic isolines generating mode and custom generating mode.
DEFINE_string(srtm_path, "", "Path to srtm directory.");
DEFINE_uint64(threads, 4, "Number of threads.");
DEFINE_uint64(tiles_per_thread, 9, "Max cached tiles per thread");
// Common options for custom isolines generating and custom packing modes.
DEFINE_string(out_dir, "", "Path to output directory.");
DEFINE_uint64(simpl_zoom, 16, "Isolines simplification zoom.");
// Options for custom isolines packing mode.
DEFINE_string(countryId, "",
"Custom isolines packing mode. Pack isolines for countryId.");
DEFINE_string(isolines_path, "",
"Custom isolines packing mode. Path to the directory with isolines tiles.");
DEFINE_uint64(max_length, 1000, "Custom isolines packing mode. Isolines max length.");
DEFINE_uint64(alt_step_factor, 1, "Custom isolines packing mode. Altitude step factor.");
// Options for custom isolines generating mode.
DEFINE_int32(left, 0, "Custom isolines generating mode. Left longitude of tiles rect [-180, 179].");
DEFINE_int32(right, 0, "Custom isolines generating mode. Right longitude of tiles rect [-179, 180].");
DEFINE_int32(bottom, 0, "Custom isolines generating mode. Bottom latitude of tiles rect [-90, 89].");
DEFINE_int32(top, 0, "Custom isolines generating mode. Top latitude of tiles rect [-89, 90].");
DEFINE_uint64(isolines_step, 10, "Custom isolines generating mode. Isolines step in meters.");
DEFINE_uint64(latlon_step_factor, 2, "Custom isolines generating mode. Lat/lon step factor.");
DEFINE_double(gaussian_st_dev, 2.0, "Custom isolines generating mode. Gaussian filter standard deviation.");
DEFINE_double(gaussian_r_factor, 1.0, "Custom isolines generating mode. Gaussian filter radius factor.");
DEFINE_uint64(median_r, 1, "Custom isolines generating mode. Median filter radius.");
using namespace topography_generator;
MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
{
gflags::SetUsageMessage(
"\n\nThis tool generates isolines and works in the following modes:\n"
"1. Automatic isolines generating mode. Generates a binary file with isolines for each\n"
" country id from the countries_to_generate_path. Gets options for isolines generating\n"
" from the corresponding to the country profile in profiles_path.\n"
" Stores intermediate binary isolines tiles to tiles_isolines_out_dir and result countries\n"
" isolines to countries_isolines_out_dir.\n"
"2. Custom isolines generating mode. Generates binary tile with isolines for each SRTM tile in the\n"
" specified tile rect.\n"
" Mode activates by passing a valid tiles rect.\n"
" An isoline would be generated for each isolines_step difference in height.\n"
" Tiles for lat >= 60 && lat < -60 (converted from ASTER source) can be filtered by\n"
" median and/or gaussian filters.\n"
" Median filter activates by nonzero filter kernel radius median_r.\n"
" Gaussian filter activates by gaussian_st_dev > 0.0 && gaussian_r_factor > 0.0 parameters.\n"
" Contours generating steps through altitudes matrix of SRTM tile can be adjusted by\n"
" latlon_step_factor parameter.\n"
" Isolines simplification activates by nonzero simpl_zoom [1..17]\n"
"\n"
"3. Custom packing isolines from ready tiles into a binary file for specified country id.\n"
" Mode activates by passing a countryId parameter.\n"
" Tool gets isolines from the tiles, covered by the country regions, selects\n"
" altitude levels with alt_step_factor (if a tile stores altitudes for each 10 meters\n"
" and alt_step_factor == 5, the result binary file will store altitudes for each 50 meters).\n"
" Isolines cropped by the country regions and cut by max_length parameter.\n"
" Isolines simplification activates by nonzero simpl_zoom [1..17]\n\n");
gflags::ParseCommandLineFlags(&argc, &argv, true);
bool isCustomGeneratingMode = false;
bool isCustomPackingMode = false;
bool const isAutomaticMode = !FLAGS_profiles_path.empty() ||
!FLAGS_countries_to_generate_path.empty() ||
!FLAGS_tiles_isolines_out_dir.empty() ||
!FLAGS_countries_isolines_out_dir.empty();
if (isAutomaticMode)
{
if (FLAGS_profiles_path.empty() || FLAGS_countries_to_generate_path.empty() ||
FLAGS_tiles_isolines_out_dir.empty() || FLAGS_countries_isolines_out_dir.empty())
{
LOG(LERROR, ("To run automatic isolines generating mode all the params profiles_path, "
"countries_to_generate_path, tiles_isolines_out_dir, countries_isolines_out_dir "
"must be set."));
return EXIT_FAILURE;
}
}
else
{
if (FLAGS_out_dir.empty())
{
LOG(LERROR, ("out_dir must be set."));
return EXIT_FAILURE;
}
auto const validTilesRect = FLAGS_right > FLAGS_left && FLAGS_top > FLAGS_bottom &&
FLAGS_right <= 180 && FLAGS_left >= -180 &&
FLAGS_top <= 90 && FLAGS_bottom >= -90;
isCustomGeneratingMode = validTilesRect;
isCustomPackingMode = !FLAGS_countryId.empty();
if (isCustomGeneratingMode && isCustomPackingMode)
{
LOG(LERROR, ("Both tiles rect and country id are set. Сhoose one operation: "
"generating of the tiles rect or packing tiles for the country"));
return EXIT_FAILURE;
}
if (!isCustomGeneratingMode && !isCustomPackingMode)
{
LOG(LERROR, ("Specify isolines profiles and countries to generate, or a valid tiles rect, "
"or a country id."));
return EXIT_FAILURE;
}
}
if (isCustomPackingMode || isAutomaticMode)
{
if (FLAGS_data_dir.empty())
{
LOG(LERROR, ("data_dir must be set."));
return EXIT_FAILURE;
}
}
if (isCustomGeneratingMode || isAutomaticMode)
{
if (FLAGS_srtm_path.empty())
{
LOG(LERROR, ("srtm_path must be set."));
return EXIT_FAILURE;
}
}
Generator generator(FLAGS_srtm_path, FLAGS_threads, FLAGS_tiles_per_thread, FLAGS_force);
if (isAutomaticMode)
{
generator.InitCountryInfoGetter(FLAGS_data_dir);
generator.InitProfiles(FLAGS_profiles_path, FLAGS_countries_to_generate_path,
FLAGS_tiles_isolines_out_dir, FLAGS_countries_isolines_out_dir);
generator.GenerateIsolinesForCountries();
generator.PackIsolinesForCountries();
return EXIT_SUCCESS;
}
if (isCustomPackingMode)
{
if (FLAGS_isolines_path.empty())
{
LOG(LERROR, ("isolines_path must be set."));
return EXIT_FAILURE;
}
IsolinesPackingParams params;
params.m_outputDir = FLAGS_out_dir;
params.m_simplificationZoom = static_cast<int>(FLAGS_simpl_zoom);
params.m_maxIsolineLength = FLAGS_max_length;
params.m_alitudesStepFactor = FLAGS_alt_step_factor;
params.m_isolinesTilesPath = FLAGS_isolines_path;
generator.InitCountryInfoGetter(FLAGS_data_dir);
generator.PackIsolinesForCountry(FLAGS_countryId, params);
return EXIT_SUCCESS;
}
CHECK(isCustomGeneratingMode, ());
TileIsolinesParams params;
if (FLAGS_median_r > 0)
{
params.m_filters.emplace_back(std::make_unique<MedianFilter<Altitude>>(FLAGS_median_r));
}
if (FLAGS_gaussian_st_dev > 0.0 && FLAGS_gaussian_r_factor > 0)
{
params.m_filters.emplace_back(
std::make_unique<GaussianFilter<Altitude>>(FLAGS_gaussian_st_dev, FLAGS_gaussian_r_factor));
}
params.m_outputDir = FLAGS_out_dir;
params.m_alitudesStep = FLAGS_isolines_step;
params.m_latLonStepFactor = FLAGS_latlon_step_factor;
params.m_simplificationZoom = static_cast<int>(FLAGS_simpl_zoom);
generator.GenerateIsolines(FLAGS_left, FLAGS_bottom, FLAGS_right, FLAGS_top, params);
return EXIT_SUCCESS;
});

View File

@@ -0,0 +1,100 @@
#include "topography_generator/marching_squares/contours_builder.hpp"
#include "geometry/mercator.hpp"
namespace topography_generator
{
ContoursBuilder::ContoursBuilder(size_t levelsCount, std::string const & debugId)
: m_levelsCount(levelsCount)
, m_finalizedContours(levelsCount)
, m_activeContours(levelsCount)
, m_debugId(debugId)
{}
void ContoursBuilder::AddSegment(size_t levelInd, ms::LatLon const & beginPos, ms::LatLon const & endPos)
{
if (beginPos.EqualDxDy(endPos, mercator::kPointEqualityEps))
return;
CHECK_LESS(levelInd, m_levelsCount, (m_debugId));
auto contourItBefore = FindContourWithEndPoint(levelInd, beginPos);
auto contourItAfter = FindContourWithStartPoint(levelInd, endPos);
auto const connectStart = contourItBefore != m_activeContours[levelInd].end();
auto const connectEnd = contourItAfter != m_activeContours[levelInd].end();
if (connectStart && connectEnd && contourItBefore != contourItAfter)
{
std::move(contourItAfter->m_countour.begin(), contourItAfter->m_countour.end(),
std::back_inserter(contourItBefore->m_countour));
contourItBefore->m_active = true;
m_activeContours[levelInd].erase(contourItAfter);
}
else if (connectStart)
{
contourItBefore->m_countour.push_back(endPos);
contourItBefore->m_active = true;
}
else if (connectEnd)
{
contourItAfter->m_countour.push_front(beginPos);
contourItAfter->m_active = true;
}
else
{
m_activeContours[levelInd].emplace_back(ContourRaw({beginPos, endPos}));
}
}
void ContoursBuilder::BeginLine()
{
for (auto & contoursList : m_activeContours)
{
for (auto & activeContour : contoursList)
activeContour.m_active = false;
}
}
void ContoursBuilder::EndLine(bool finalLine)
{
for (size_t levelInd = 0; levelInd < m_levelsCount; ++levelInd)
{
auto & contours = m_activeContours[levelInd];
auto it = contours.begin();
while (it != contours.end())
{
if (!it->m_active || finalLine)
{
m_finalizedContours[levelInd].push_back(std::move(it->m_countour));
it = contours.erase(it);
}
else
{
++it;
}
}
}
}
ContoursBuilder::ActiveContourIter ContoursBuilder::FindContourWithStartPoint(size_t levelInd, ms::LatLon const & pos)
{
auto & contours = m_activeContours[levelInd];
for (auto it = contours.begin(); it != contours.end(); ++it)
{
if (it->m_countour.front().EqualDxDy(pos, mercator::kPointEqualityEps))
return it;
}
return contours.end();
}
ContoursBuilder::ActiveContourIter ContoursBuilder::FindContourWithEndPoint(size_t levelInd, ms::LatLon const & pos)
{
auto & contours = m_activeContours[levelInd];
for (auto it = contours.begin(); it != contours.end(); ++it)
{
if (it->m_countour.back().EqualDxDy(pos, mercator::kPointEqualityEps))
return it;
}
return contours.end();
}
} // namespace topography_generator

View File

@@ -0,0 +1,72 @@
#pragma once
#include "topography_generator/utils/contours.hpp"
#include "geometry/latlon.hpp"
#include "geometry/mercator.hpp"
#include <algorithm>
#include <deque>
#include <list>
#include <vector>
#include <unordered_map>
namespace topography_generator
{
class ContoursBuilder
{
public:
ContoursBuilder(size_t levelsCount, std::string const & debugId);
void AddSegment(size_t levelInd, ms::LatLon const & beginPos, ms::LatLon const & endPos);
void BeginLine();
void EndLine(bool finalLine);
template <typename ValueType>
void GetContours(ValueType minValue, ValueType valueStep,
std::unordered_map<ValueType, std::vector<Contour>> & contours)
{
contours.clear();
for (size_t i = 0; i < m_finalizedContours.size(); ++i)
{
auto const levelValue = minValue + i * valueStep;
auto const & contoursList = m_finalizedContours[i];
for (auto const & contour : contoursList)
{
Contour contourMerc;
contourMerc.reserve(contour.size());
std::transform(contour.begin(), contour.end(), std::back_inserter(contourMerc),
[](ms::LatLon const & pt){ return mercator::FromLatLon(pt); });
contours[levelValue].emplace_back(std::move(contourMerc));
}
}
}
private:
using ContourRaw = std::deque<ms::LatLon>;
using ContoursList = std::list<ContourRaw>;
struct ActiveContour
{
explicit ActiveContour(ContourRaw && isoline)
: m_countour(std::move(isoline))
{}
ContourRaw m_countour;
bool m_active = true;
};
using ActiveContoursList = std::list<ActiveContour>;
using ActiveContourIter = ActiveContoursList::iterator;
ActiveContourIter FindContourWithStartPoint(size_t levelInd, ms::LatLon const & pos);
ActiveContourIter FindContourWithEndPoint(size_t levelInd, ms::LatLon const & pos);
size_t const m_levelsCount;
std::vector<ContoursList> m_finalizedContours;
std::vector<ActiveContoursList> m_activeContours;
std::string m_debugId;
};
} // namespace topography_generator

View File

@@ -0,0 +1,130 @@
#pragma once
#include "topography_generator/marching_squares/contours_builder.hpp"
#include "topography_generator/marching_squares/square.hpp"
#include "topography_generator/utils/contours.hpp"
#include "topography_generator/utils/values_provider.hpp"
#include "base/logging.hpp"
namespace topography_generator
{
template <typename ValueType>
class MarchingSquares
{
public:
MarchingSquares(ms::LatLon const & leftBottom, ms::LatLon const & rightTop,
double step, ValueType valueStep, ValuesProvider<ValueType> & valuesProvider,
std::string const & debugId)
: m_leftBottom(leftBottom)
, m_rightTop(rightTop)
, m_step(step)
, m_valueStep(valueStep)
, m_valuesProvider(valuesProvider)
, m_debugId(debugId)
{
CHECK_GREATER(m_rightTop.m_lon, m_leftBottom.m_lon, ());
CHECK_GREATER(m_rightTop.m_lat, m_leftBottom.m_lat, ());
m_stepsCountLon = static_cast<size_t>((m_rightTop.m_lon - m_leftBottom.m_lon) / step);
m_stepsCountLat = static_cast<size_t>((m_rightTop.m_lat - m_leftBottom.m_lat) / step);
CHECK_GREATER(m_stepsCountLon, 0, ());
CHECK_GREATER(m_stepsCountLat, 0, ());
}
void GenerateContours(Contours<ValueType> & result)
{
std::vector<ValueType> grid((m_stepsCountLat + 1) * (m_stepsCountLon + 1));
ScanValuesInRect(result, grid);
result.m_valueStep = m_valueStep;
auto const levelsCount = static_cast<size_t>(result.m_maxValue - result.m_minValue) / m_valueStep;
if (levelsCount == 0)
{
LOG(LINFO, ("Contours can't be generated: min and max values are equal:", result.m_minValue));
return;
}
ContoursBuilder contoursBuilder(levelsCount, m_debugId);
Square<ValueType> square(result.m_minValue, m_valueStep, m_debugId);
for (size_t i = 0; i < m_stepsCountLat; ++i)
{
contoursBuilder.BeginLine();
for (size_t j = 0; j < m_stepsCountLon; ++j)
{
// This point should be calculated _exact_ the same way as in ScanValuesInRect.
// leftBottom + m_step doesn't work due to different floating results.
square.Init(
m_leftBottom.m_lon + m_step * j, // Left
m_leftBottom.m_lat + m_step * i, // Bottom
m_leftBottom.m_lon + m_step * (j + 1), // Right
m_leftBottom.m_lat + m_step * (i + 1), // Top
grid[Idx(i, j)], // LB
grid[Idx(i, j + 1)], // RB
grid[Idx(i + 1, j)], // LT
grid[Idx(i + 1, j + 1)], // RT
m_valuesProvider.GetInvalidValue());
square.GenerateSegments(contoursBuilder);
}
contoursBuilder.EndLine(i == m_stepsCountLat - 1 /* finalLine */);
}
contoursBuilder.GetContours(result.m_minValue, result.m_valueStep, result.m_contours);
}
private:
size_t Idx(size_t iLat, size_t jLon) const { return iLat * (m_stepsCountLon + 1) + jLon; }
void ScanValuesInRect(Contours<ValueType> & res, std::vector<ValueType> & grid) const
{
res.m_minValue = res.m_maxValue = m_valuesProvider.GetValue(m_leftBottom);
res.m_invalidValuesCount = 0;
for (size_t i = 0; i <= m_stepsCountLat; ++i)
{
for (size_t j = 0; j <= m_stepsCountLon; ++j)
{
ms::LatLon const pos(m_leftBottom.m_lat + m_step * i, m_leftBottom.m_lon + m_step * j);
auto const value = m_valuesProvider.GetValue(pos);
grid[Idx(i, j)] = value;
if (value == m_valuesProvider.GetInvalidValue())
{
++res.m_invalidValuesCount;
continue;
}
if (value < res.m_minValue)
res.m_minValue = value;
if (value > res.m_maxValue)
res.m_maxValue = value;
}
}
if (res.m_invalidValuesCount > 0)
LOG(LWARNING, ("Tile", m_debugId, "contains", res.m_invalidValuesCount, "invalid values."));
Square<ValueType>::ToLevelsRange(m_valueStep, res.m_minValue, res.m_maxValue);
CHECK_GREATER_OR_EQUAL(res.m_maxValue, res.m_minValue, (m_debugId));
}
ms::LatLon const m_leftBottom;
ms::LatLon const m_rightTop;
double const m_step;
ValueType const m_valueStep;
ValuesProvider<ValueType> & m_valuesProvider;
size_t m_stepsCountLon;
size_t m_stepsCountLat;
std::string m_debugId;
};
} // namespace topography_generator

View File

@@ -0,0 +1,233 @@
#pragma once
#include "topography_generator/marching_squares/contours_builder.hpp"
namespace topography_generator
{
template <typename ValueType>
class Square
{
public:
Square(ValueType minValue, ValueType valueStep, std::string const & debugId)
: m_minValue(minValue)
, m_valueStep(valueStep)
, m_debugId(debugId)
{
static_assert(std::is_integral<ValueType>::value && std::is_signed<ValueType>::value);
}
void Init(double left, double bottom, double right, double top,
ValueType lb, ValueType rb, ValueType lt, ValueType rt, ValueType invalid)
{
m_isValid = true;
m_left = left;
m_bottom = bottom;
m_right = right;
m_top = top;
m_valueLB = GetValue(lb, invalid);
m_valueRB = GetValue(rb, invalid);
m_valueLT = GetValue(lt, invalid);
m_valueRT = GetValue(rt, invalid);
}
void GenerateSegments(ContoursBuilder & builder) const
{
if (!m_isValid)
return;
ValueType minVal = std::min(m_valueLB, std::min(m_valueLT, std::min(m_valueRT, m_valueRB)));
ValueType maxVal = std::max(m_valueLB, std::max(m_valueLT, std::max(m_valueRT, m_valueRB)));
ToLevelsRange(m_valueStep, minVal, maxVal);
CHECK_GREATER_OR_EQUAL(minVal, m_minValue, (m_debugId));
for (auto val = minVal; val < maxVal; val += m_valueStep)
AddSegments(val, (val - m_minValue) / m_valueStep, builder);
}
static void ToLevelsRange(ValueType step, ValueType & minVal, ValueType & maxVal)
{
if (minVal > 0)
minVal = step * ((minVal + step - 1) / step);
else
minVal = step * (minVal / step);
if (maxVal > 0)
maxVal = step * ((maxVal + step) / step);
else
maxVal = step * ((maxVal + 1) / step);
}
private:
enum class Rib
{
None,
Left,
Top,
Right,
Bottom,
Unclear,
};
ValueType GetValue(ValueType val, ValueType invalid)
{
// If a contour goes right through the corner of the square false segments can be generated.
// Shift the value slightly from the corner.
if (val == invalid)
{
//LOG(LWARNING, ("Invalid value at the position", pos, m_debugId));
m_isValid = false;
return val;
}
if (abs(val) % m_valueStep == 0)
return val + 1;
return val;
}
void AddSegments(ValueType val, uint16_t ind, ContoursBuilder & builder) const
{
// Segment is a vector directed so that higher values is on the right.
static const std::pair<Rib, Rib> intersectedRibs[] =
{
{Rib::None, Rib::None}, // 0000
{Rib::Left, Rib::Bottom}, // 0001
{Rib::Top, Rib::Left}, // 0010
{Rib::Top, Rib::Bottom}, // 0011
{Rib::Right, Rib::Top}, // 0100
{Rib::Unclear, Rib::Unclear}, // 0101
{Rib::Right, Rib::Left}, // 0110
{Rib::Right, Rib::Bottom}, // 0111
{Rib::Bottom, Rib::Right}, // 1000
{Rib::Left, Rib::Right}, // 1001
{Rib::Unclear, Rib::Unclear}, // 1010
{Rib::Top, Rib::Right}, // 1011
{Rib::Bottom, Rib::Top}, // 1100
{Rib::Left, Rib::Top}, // 1101
{Rib::Bottom, Rib::Left}, // 1110
{Rib::None, Rib::None}, // 1111
};
uint8_t const pattern = (m_valueLB > val ? 1u : 0u) | ((m_valueLT > val ? 1u : 0u) << 1u) |
((m_valueRT > val ? 1u : 0u) << 2u) | ((m_valueRB > val ? 1u : 0u) << 3u);
auto const ribs = intersectedRibs[pattern];
if (ribs.first == Rib::None)
return;
if (ribs.first != Rib::Unclear)
{
builder.AddSegment(ind, InterpolatePoint(ribs.first, val), InterpolatePoint(ribs.second, val));
}
else
{
auto const leftPos = InterpolatePoint(Rib::Left, val);
auto const rightPos = InterpolatePoint(Rib::Right, val);
auto const bottomPos = InterpolatePoint(Rib::Bottom, val);
auto const topPos = InterpolatePoint(Rib::Top, val);
ValueType const avVal = (m_valueLB + m_valueLT + m_valueRT + m_valueRB) / 4;
if (avVal > val)
{
if (m_valueLB > val)
{
builder.AddSegment(ind, leftPos, topPos);
builder.AddSegment(ind, rightPos, bottomPos);
}
else
{
builder.AddSegment(ind, bottomPos, leftPos);
builder.AddSegment(ind, topPos, rightPos);
}
}
else
{
if (m_valueLB > val)
{
builder.AddSegment(ind, leftPos, bottomPos);
builder.AddSegment(ind, rightPos, topPos);
}
else
{
builder.AddSegment(ind, topPos, leftPos);
builder.AddSegment(ind, bottomPos, rightPos);
}
}
}
}
ms::LatLon InterpolatePoint(Square::Rib rib, ValueType val) const
{
double val1;
double val2;
double lat;
double lon;
switch (rib)
{
case Rib::Left:
val1 = static_cast<double>(m_valueLB);
val2 = static_cast<double>(m_valueLT);
lon = m_left;
break;
case Rib::Right:
val1 = static_cast<double>(m_valueRB);
val2 = static_cast<double>(m_valueRT);
lon = m_right;
break;
case Rib::Top:
val1 = static_cast<double>(m_valueLT);
val2 = static_cast<double>(m_valueRT);
lat = m_top;
break;
case Rib::Bottom:
val1 = static_cast<double>(m_valueLB);
val2 = static_cast<double>(m_valueRB);
lat = m_bottom;
break;
default:
UNREACHABLE();
}
CHECK_NOT_EQUAL(val, val2, (m_debugId));
double const coeff = (val1 - val) / (val - val2);
switch (rib)
{
case Rib::Left:
case Rib::Right:
lat = (m_bottom + m_top * coeff) / (1 + coeff);
break;
case Rib::Bottom:
case Rib::Top:
lon = (m_left + m_right * coeff) / (1 + coeff);
break;
default:
UNREACHABLE();
}
return {lat, lon};
}
double m_left;
double m_right;
double m_bottom;
double m_top;
ValueType m_valueLB;
ValueType m_valueLT;
ValueType m_valueRT;
ValueType m_valueRB;
ValueType m_minValue;
ValueType m_valueStep;
std::string m_debugId;
bool m_isValid;
};
} // topography_generator

View File

@@ -0,0 +1,101 @@
#pragma once
#include "topography_generator/filters_impl.hpp"
namespace topography_generator
{
template <typename ValueType>
class FilterInterface
{
public:
virtual ~FilterInterface() = default;
virtual size_t GetKernelRadius() const = 0;
virtual void Process(size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues, ValueType invalidValue) const = 0;
};
template <typename ValueType>
class MedianFilter : public FilterInterface<ValueType>
{
public:
explicit MedianFilter(size_t kernelRadius)
: m_kernelRadius(kernelRadius)
{}
size_t GetKernelRadius() const override { return m_kernelRadius; }
void Process(size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues, ValueType invalidValue) const override
{
ProcessMedian(m_kernelRadius, tileSize, tileOffset, srcValues, dstValues, invalidValue);
}
private:
size_t m_kernelRadius;
};
template <typename ValueType>
class GaussianFilter : public FilterInterface<ValueType>
{
public:
GaussianFilter(double standardDeviation, double radiusFactor)
: m_kernel(CalculateGaussianLinearKernel(standardDeviation, radiusFactor))
{}
size_t GetKernelRadius() const override { return m_kernel.size() / 2; }
void Process(size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues, ValueType invalidValue) const override
{
ProcessWithLinearKernel(m_kernel, tileSize, tileOffset, srcValues, dstValues, invalidValue);
}
private:
std::vector<double> m_kernel;
};
template <typename ValueType>
using FiltersSequence = std::vector<std::unique_ptr<FilterInterface<ValueType>>>;
template <typename ValueType>
std::vector<ValueType> FilterTile(FiltersSequence<ValueType> const & filters,
ms::LatLon const & leftBottom,
size_t stepsInDegree, size_t tileSize,
ValuesProvider<ValueType> & valuesProvider)
{
size_t combinedOffset = 0;
for (auto const & filter : filters)
combinedOffset += filter->GetKernelRadius();
std::vector<ValueType> extTileValues;
GetExtendedTile(leftBottom, stepsInDegree, tileSize, combinedOffset, valuesProvider, extTileValues);
std::vector<ValueType> extTileValues2(extTileValues.size());
size_t const extTileSize = tileSize + 2 * combinedOffset;
CHECK_EQUAL(extTileSize * extTileSize, extTileValues.size(), ());
size_t currentOffset = 0;
for (auto const & filter : filters)
{
currentOffset += filter->GetKernelRadius();
filter->Process(extTileSize, currentOffset, extTileValues, extTileValues2, kInvalidAltitude);
extTileValues.swap(extTileValues2);
}
std::vector<ValueType> result(tileSize * tileSize);
for (size_t i = combinedOffset; i < extTileSize - combinedOffset; ++i)
{
for (size_t j = combinedOffset; j < extTileSize - combinedOffset; ++j)
{
size_t const dstIndex = (i - combinedOffset) * tileSize + j - combinedOffset;
result[dstIndex] = extTileValues[i * extTileSize + j];
}
}
return result;
}
} // namespace topography_generator

View File

@@ -0,0 +1,107 @@
#pragma once
#include "generator/feature_helpers.hpp"
#include "geometry/point2d.hpp"
#include "geometry/region2d.hpp"
#include "indexer/scales.hpp"
#include <vector>
#include <unordered_map>
namespace topography_generator
{
using Contour = std::vector<m2::PointD>;
template <typename ValueType>
struct Contours
{
enum class Version : uint8_t
{
V0 = 0,
Latest = V0
};
std::unordered_map<ValueType, std::vector<Contour>> m_contours;
ValueType m_minValue = 0;
ValueType m_maxValue = 0;
ValueType m_valueStep = 0;
size_t m_invalidValuesCount = 0; // for debug purpose only.
};
template <typename ValueType>
void CropContours(m2::RectD & rect, std::vector<m2::RegionD> & regions, size_t maxLength,
size_t valueStepFactor, Contours<ValueType> & contours)
{
static_assert(std::is_integral<ValueType>::value, "Only integral types are supported.");
contours.m_minValue = std::numeric_limits<ValueType>::max();
contours.m_maxValue = std::numeric_limits<ValueType>::min();
auto it = contours.m_contours.begin();
while (it != contours.m_contours.end())
{
std::vector<Contour> levelCroppedContours;
if (it->first % static_cast<ValueType>(contours.m_valueStep * valueStepFactor) == 0)
{
for (auto const & contour : it->second)
{
Contour cropped;
cropped.reserve(contour.size());
for (auto const & pt : contour)
{
cropped.push_back(pt);
auto const isInside = rect.IsPointInside(pt) && RegionsContain(regions, pt);
if (!isInside || cropped.size() == maxLength)
{
if (cropped.size() > 1)
levelCroppedContours.emplace_back(std::move(cropped));
cropped.clear();
if (isInside)
cropped.push_back(pt);
}
}
if (cropped.size() > 1)
levelCroppedContours.emplace_back(std::move(cropped));
}
}
it->second = std::move(levelCroppedContours);
if (!it->second.empty())
{
contours.m_minValue = std::min(it->first, contours.m_minValue);
contours.m_maxValue = std::max(it->first, contours.m_maxValue);
++it;
}
else
{
it = contours.m_contours.erase(it);
}
}
}
template <typename ValueType>
void SimplifyContours(int simplificationZoom, Contours<ValueType> & contours)
{
for (auto & levelContours : contours.m_contours)
{
for (auto & contour : levelContours.second)
{
std::vector<m2::PointD> contourSimple;
feature::SimplifyPoints(m2::SquaredDistanceFromSegmentToPoint(),
simplificationZoom, contour, contourSimple);
CHECK_GREATER(contourSimple.size(), 1, ());
// Discard closed lines which are degenerate (<=3 points) or too small for the requested zoom level.
/// @todo it doesn't fix all cases as the simplification algo
/// can produce e.g. a 5 points closed but degenerate line ("flat", not a loop anymore).
if (contourSimple.front() == contourSimple.back() &&
!scales::IsGoodOutlineForLevel(simplificationZoom, contourSimple))
contour.clear();
else
contour = std::move(contourSimple);
}
}
}
} // namespace topography_generator

View File

@@ -0,0 +1,141 @@
#pragma once
#include "topography_generator/utils/contours.hpp"
#include "coding/file_writer.hpp"
#include "coding/file_reader.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/internal/file_data.hpp"
namespace topography_generator
{
template <typename ValueType>
class SerializerContours
{
public:
explicit SerializerContours(Contours<ValueType> && contours): m_contours(std::move(contours)) {}
template <typename Sink>
void Serialize(Sink & sink)
{
WriteToSink(sink, base::Underlying(Contours<ValueType>::Version::Latest));
WriteToSink(sink, m_contours.m_minValue);
WriteToSink(sink, m_contours.m_maxValue);
WriteToSink(sink, m_contours.m_valueStep);
WriteToSink(sink, static_cast<uint32_t>(m_contours.m_invalidValuesCount));
WriteToSink(sink, static_cast<uint32_t>(m_contours.m_contours.size()));
for (auto const & levelContours : m_contours.m_contours)
SerializeContours(sink, levelContours.first, levelContours.second);
}
private:
template <typename Sink>
void SerializeContours(Sink & sink, ValueType value,
std::vector<topography_generator::Contour> const & contours)
{
WriteToSink(sink, value);
WriteToSink(sink, static_cast<uint32_t>(contours.size()));
for (auto const & contour : contours)
SerializeContour(sink, contour);
}
template <typename Sink>
void SerializeContour(Sink & sink, topography_generator::Contour const & contour)
{
serial::GeometryCodingParams codingParams(kPointCoordBits, contour[0]);
codingParams.Save(sink);
serial::SaveOuterPath(contour, codingParams, sink);
}
Contours<ValueType> m_contours;
};
template <typename ValueType>
class DeserializerContours
{
public:
template <typename Reader>
void Deserialize(Reader & reader, Contours<ValueType> & contours)
{
NonOwningReaderSource source(reader);
using VersT = typename Contours<ValueType>::Version;
auto const v = static_cast<VersT>(ReadPrimitiveFromSource<std::underlying_type_t<VersT>>(source));
CHECK(v == Contours<ValueType>::Version::Latest, ());
contours.m_minValue = ReadPrimitiveFromSource<ValueType>(source);
contours.m_maxValue = ReadPrimitiveFromSource<ValueType>(source);
contours.m_valueStep = ReadPrimitiveFromSource<ValueType>(source);
contours.m_invalidValuesCount = ReadPrimitiveFromSource<uint32_t>(source);
size_t const levelsCount = ReadPrimitiveFromSource<uint32_t>(source);
for (size_t i = 0; i < levelsCount; ++i)
{
ValueType levelValue;
std::vector<topography_generator::Contour> levelContours;
DeserializeContours(source, levelValue, levelContours);
contours.m_contours[levelValue] = std::move(levelContours);
}
}
private:
void DeserializeContours(NonOwningReaderSource & source, ValueType & value,
std::vector<topography_generator::Contour> & contours)
{
value = ReadPrimitiveFromSource<ValueType>(source);
size_t const contoursCount = ReadPrimitiveFromSource<uint32_t>(source);
contours.resize(contoursCount);
for (auto & contour : contours)
DeserializeContour(source, contour);
}
void DeserializeContour(NonOwningReaderSource & source,
topography_generator::Contour & contour)
{
serial::GeometryCodingParams codingParams;
codingParams.Load(source);
std::vector<m2::PointD> points;
serial::LoadOuterPath(source, codingParams, points);
contour = std::move(points);
}
};
template <typename ValueType>
bool SaveContrours(std::string const & filePath,
Contours<ValueType> && contours)
{
auto const tmpFilePath = filePath + ".tmp";
try
{
FileWriter file(tmpFilePath);
SerializerContours<ValueType> ser(std::move(contours));
ser.Serialize(file);
}
catch (FileWriter::Exception const & ex)
{
LOG(LWARNING, ("File writer exception raised:", ex.what(), ", file", tmpFilePath));
return false;
}
CHECK(base::RenameFileX(tmpFilePath, filePath), (tmpFilePath, filePath));
return true;
}
template <typename ValueType>
bool LoadContours(std::string const & filePath, Contours<ValueType> & contours)
{
try
{
FileReader file(filePath);
DeserializerContours<ValueType> des;
des.Deserialize(file, contours);
}
catch (FileReader::Exception const & ex)
{
LOG(LWARNING, ("File reader exception raised:", ex.what(), ", file", filePath));
return false;
}
return true;
}
} // namespace topography_generator

View File

@@ -0,0 +1,16 @@
#pragma once
#include "geometry/latlon.hpp"
namespace topography_generator
{
template <typename ValueType>
class ValuesProvider
{
public:
virtual ~ValuesProvider() = default;
virtual ValueType GetValue(ms::LatLon const & pos) = 0;
virtual ValueType GetInvalidValue() const = 0;
};
} // namespace topography_generator

View File

@@ -0,0 +1,29 @@
project(track_analyzing)
set(SRC
exceptions.hpp
log_parser.cpp
log_parser.hpp
serialization.hpp
temporary_file.cpp
temporary_file.hpp
track.cpp
track.hpp
track_archive_reader.cpp
track_archive_reader.hpp
track_matcher.cpp
track_matcher.hpp
utils.cpp
utils.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
generator
tracking
platform
)
omim_add_tool_subdirectory(track_analyzer)
omim_add_test_subdirectory(track_analyzing_tests)

View File

@@ -0,0 +1,8 @@
#pragma once
#include "base/exception.hpp"
namespace track_analyzing
{
DECLARE_EXCEPTION(MessageException, RootException);
} // namespace track_analyzing

View File

@@ -0,0 +1,179 @@
#include "track_analyzing/log_parser.hpp"
#include "track_analyzing/exceptions.hpp"
#include "generator/borders.hpp"
#include "platform/platform.hpp"
#include "coding/hex.hpp"
#include "coding/traffic.hpp"
#include "geometry/mercator.hpp"
#include "base/file_name_utils.hpp"
#include "base/timer.hpp"
#include <cstdint>
#include <fstream>
#include <regex>
#include <unordered_set>
using namespace std;
using namespace track_analyzing;
namespace
{
vector<DataPoint> ReadDataPoints(string const & data)
{
string const decoded = FromHex(data);
vector<DataPoint> points;
MemReaderWithExceptions memReader(decoded.data(), decoded.size());
ReaderSource<MemReaderWithExceptions> src(memReader);
try
{
coding::TrafficGPSEncoder::DeserializeDataPoints(coding::TrafficGPSEncoder::kLatestVersion, src,
points);
}
catch (Reader::SizeException const & e)
{
points.clear();
LOG(LERROR, ("DataPoint is corrupted. data:", data));
LOG(LINFO, ("Continue reading..."));
}
return points;
}
class PointToMwmId final
{
public:
PointToMwmId(shared_ptr<m4::Tree<routing::NumMwmId>> mwmTree,
routing::NumMwmIds const & numMwmIds, string const & dataDir)
: m_mwmTree(mwmTree)
{
numMwmIds.ForEachId([&](routing::NumMwmId numMwmId) {
string const & mwmName = numMwmIds.GetFile(numMwmId).GetName();
string const polyFile = base::JoinPath(dataDir, BORDERS_DIR, mwmName + BORDERS_EXTENSION);
borders::LoadBorders(polyFile, m_borders[numMwmId]);
});
}
routing::NumMwmId FindMwmId(m2::PointD const & point, routing::NumMwmId expectedId) const
{
if (expectedId != routing::kFakeNumMwmId && m2::RegionsContain(GetBorders(expectedId), point))
return expectedId;
routing::NumMwmId result = routing::kFakeNumMwmId;
m2::RectD const rect = mercator::RectByCenterXYAndSizeInMeters(point, 1);
m_mwmTree->ForEachInRect(rect, [&](routing::NumMwmId numMwmId) {
if (result == routing::kFakeNumMwmId && m2::RegionsContain(GetBorders(numMwmId), point))
result = numMwmId;
});
return result;
}
private:
vector<m2::RegionD> const & GetBorders(routing::NumMwmId numMwmId) const
{
auto it = m_borders.find(numMwmId);
CHECK(it != m_borders.cend(), ());
return it->second;
}
shared_ptr<m4::Tree<routing::NumMwmId>> m_mwmTree;
unordered_map<routing::NumMwmId, vector<m2::RegionD>> m_borders;
};
} // namespace
namespace track_analyzing
{
LogParser::LogParser(shared_ptr<routing::NumMwmIds> numMwmIds,
unique_ptr<m4::Tree<routing::NumMwmId>> mwmTree, string const & dataDir)
: m_numMwmIds(std::move(numMwmIds)), m_mwmTree(std::move(mwmTree)), m_dataDir(dataDir)
{
CHECK(m_numMwmIds, ());
CHECK(m_mwmTree, ());
}
void LogParser::Parse(string const & logFile, MwmToTracks & mwmToTracks) const
{
UserToTrack userToTrack;
ParseUserTracks(logFile, userToTrack);
SplitIntoMwms(userToTrack, mwmToTracks);
}
void LogParser::ParseUserTracks(string const & logFile, UserToTrack & userToTrack) const
{
base::Timer timer;
std::ifstream stream(logFile);
if (!stream)
MYTHROW(MessageException, ("Can't open file", logFile, "to parse tracks"));
std::regex const base_regex(R"(.*(DataV0|CurrentData)\s+aloha_id\s*:\s*(\S+)\s+.*\|(\w+)\|)");
std::unordered_set<string> usersWithOldVersion;
uint64_t linesCount = 0;
size_t pointsCount = 0;
for (string line; getline(stream, line); ++linesCount)
{
std::smatch base_match;
if (!std::regex_match(line, base_match, base_regex))
continue;
CHECK_EQUAL(base_match.size(), 4, ());
string const version = base_match[1].str();
string const userId = base_match[2].str();
string const data = base_match[3].str();
if (version != "CurrentData")
{
CHECK_EQUAL(version, "DataV0", ());
usersWithOldVersion.insert(userId);
continue;
}
auto const packet = ReadDataPoints(data);
if (!packet.empty())
{
Track & track = userToTrack[userId];
track.insert(track.end(), packet.cbegin(), packet.cend());
}
pointsCount += packet.size();
};
LOG(LINFO, ("Tracks parsing finished, elapsed:", timer.ElapsedSeconds(), "seconds, lines:",
linesCount, ", points", pointsCount));
LOG(LINFO, ("Users with current version:", userToTrack.size(), ", old version:",
usersWithOldVersion.size()));
}
void LogParser::SplitIntoMwms(UserToTrack const & userToTrack, MwmToTracks & mwmToTracks) const
{
base::Timer timer;
PointToMwmId const pointToMwmId(m_mwmTree, *m_numMwmIds, m_dataDir);
for (auto const & kv : userToTrack)
{
string const & user = kv.first;
Track const & track = kv.second;
routing::NumMwmId mwmId = routing::kFakeNumMwmId;
for (DataPoint const & point : track)
{
mwmId = pointToMwmId.FindMwmId(mercator::FromLatLon(point.m_latLon), mwmId);
if (mwmId != routing::kFakeNumMwmId)
mwmToTracks[mwmId][user].push_back(point);
else
LOG(LERROR, ("Can't match mwm region for", point.m_latLon, ", user:", user));
}
}
LOG(LINFO, ("Data was split into", mwmToTracks.size(), "mwms, elapsed:", timer.ElapsedSeconds(),
"seconds"));
}
} // namespace track_analyzing

View File

@@ -0,0 +1,30 @@
#pragma once
#include "track_analyzing/track.hpp"
#include "routing_common/num_mwm_id.hpp"
#include "geometry/tree4d.hpp"
#include <memory>
#include <string>
namespace track_analyzing
{
class LogParser final
{
public:
LogParser(std::shared_ptr<routing::NumMwmIds> numMwmIds,
std::unique_ptr<m4::Tree<routing::NumMwmId>> mwmTree, std::string const & dataDir);
void Parse(std::string const & logFile, MwmToTracks & mwmToTracks) const;
private:
void ParseUserTracks(std::string const & logFile, UserToTrack & userToTrack) const;
void SplitIntoMwms(UserToTrack const & userToTrack, MwmToTracks & mwmToTracks) const;
std::shared_ptr<routing::NumMwmIds> m_numMwmIds;
std::shared_ptr<m4::Tree<routing::NumMwmId>> m_mwmTree;
std::string const m_dataDir;
};
} // namespace track_analyzing

View File

@@ -0,0 +1,176 @@
#pragma once
#include "track_analyzing/track.hpp"
#include "routing/segment.hpp"
#include "routing_common/num_mwm_id.hpp"
#include "platform/country_file.hpp"
#include "coding/read_write_utils.hpp"
#include "coding/reader.hpp"
#include "coding/traffic.hpp"
#include "coding/varint.hpp"
#include "coding/write_to_sink.hpp"
#include "coding/writer.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
#include <cstdint>
#include <memory>
#include <vector>
namespace track_analyzing
{
class MwmToMatchedTracksSerializer final
{
public:
MwmToMatchedTracksSerializer(std::shared_ptr<routing::NumMwmIds> numMwmIds)
: m_numMwmIds(std::move(numMwmIds))
{
}
template <typename Sink>
void Serialize(MwmToMatchedTracks const & mwmToMatchedTracks, Sink & sink)
{
WriteSize(sink, mwmToMatchedTracks.size());
for (auto const & mwmIt : mwmToMatchedTracks)
{
rw::Write(sink, m_numMwmIds->GetFile(mwmIt.first).GetName());
UserToMatchedTracks const & userToMatchedTracks = mwmIt.second;
CHECK(!userToMatchedTracks.empty(), ());
WriteSize(sink, userToMatchedTracks.size());
for (auto const & userIt : userToMatchedTracks)
{
rw::Write(sink, userIt.first);
std::vector<MatchedTrack> const & tracks = userIt.second;
CHECK(!tracks.empty(), ());
WriteSize(sink, tracks.size());
for (MatchedTrack const & track : tracks)
{
CHECK(!track.empty(), ());
WriteSize(sink, track.size());
std::vector<DataPoint> dataPoints;
dataPoints.reserve(track.size());
for (MatchedTrackPoint const & point : track)
{
Serialize(point.GetSegment(), sink);
dataPoints.emplace_back(point.GetDataPoint());
}
std::vector<uint8_t> buffer;
MemWriter<decltype(buffer)> memWriter(buffer);
coding::TrafficGPSEncoder::SerializeDataPoints(coding::TrafficGPSEncoder::kLatestVersion,
memWriter, dataPoints);
WriteSize(sink, buffer.size());
sink.Write(buffer.data(), buffer.size());
}
}
}
}
template <typename Source>
void Deserialize(MwmToMatchedTracks & mwmToMatchedTracks, Source & src)
{
mwmToMatchedTracks.clear();
auto const numMmws = ReadSize(src);
for (size_t iMwm = 0; iMwm < numMmws; ++iMwm)
{
std::string mwmName;
rw::Read(src, mwmName);
auto const mwmId = m_numMwmIds->GetId(platform::CountryFile(mwmName));
UserToMatchedTracks & userToMatchedTracks = mwmToMatchedTracks[mwmId];
auto const numUsers = ReadSize(src);
CHECK_NOT_EQUAL(numUsers, 0, ());
for (size_t iUser = 0; iUser < numUsers; ++iUser)
{
std::string user;
rw::Read(src, user);
std::vector<MatchedTrack> & tracks = userToMatchedTracks[user];
auto const numTracks = ReadSize(src);
CHECK_NOT_EQUAL(numTracks, 0, ());
tracks.resize(numTracks);
for (size_t iTrack = 0; iTrack < numTracks; ++iTrack)
{
auto const numSegments = ReadSize(src);
CHECK_NOT_EQUAL(numSegments, 0, ());
std::vector<routing::Segment> segments;
segments.resize(numSegments);
for (size_t i = 0; i < numSegments; ++i)
Deserialize(mwmId, segments[i], src);
std::vector<uint8_t> buffer;
auto const bufferSize = ReadSize(src);
buffer.resize(bufferSize);
src.Read(buffer.data(), bufferSize);
MemReader memReader(buffer.data(), bufferSize);
ReaderSource<MemReader> memSrc(memReader);
std::vector<DataPoint> dataPoints;
coding::TrafficGPSEncoder::DeserializeDataPoints(
coding::TrafficGPSEncoder::kLatestVersion, memSrc, dataPoints);
CHECK_EQUAL(numSegments, dataPoints.size(), ("mwm:", mwmName, "user:", user));
MatchedTrack & track = tracks[iTrack];
track.reserve(numSegments);
for (size_t i = 0; i < numSegments; ++i)
track.emplace_back(dataPoints[i], segments[i]);
}
}
}
}
private:
static uint8_t constexpr kForward = 0;
static uint8_t constexpr kBackward = 1;
template <typename Sink>
static void WriteSize(Sink & sink, size_t size)
{
WriteVarUint(sink, base::checked_cast<uint64_t>(size));
}
template <typename Source>
static size_t ReadSize(Source & src)
{
return base::checked_cast<size_t>(ReadVarUint<uint64_t>(src));
}
template <typename Sink>
static void Serialize(routing::Segment const & segment, Sink & sink)
{
WriteToSink(sink, segment.GetFeatureId());
WriteToSink(sink, segment.GetSegmentIdx());
auto const direction = segment.IsForward() ? kForward : kBackward;
WriteToSink(sink, direction);
}
template <typename Source>
static void Deserialize(routing::NumMwmId numMwmId, routing::Segment & segment, Source & src)
{
auto const featureId = ReadPrimitiveFromSource<uint32_t>(src);
auto const segmentIdx = ReadPrimitiveFromSource<uint32_t>(src);
auto const direction = ReadPrimitiveFromSource<uint8_t>(src);
segment = {numMwmId, featureId, segmentIdx, direction == kForward};
}
std::shared_ptr<routing::NumMwmIds> m_numMwmIds;
};
} // namespace track_analyzing

View File

@@ -0,0 +1,26 @@
#include "track_analyzing/temporary_file.hpp"
#include "platform/platform.hpp"
#include "coding/file_writer.hpp"
using namespace std;
TemporaryFile::TemporaryFile() : m_filePath(GetPlatform().TmpPathForFile()) {}
TemporaryFile::TemporaryFile(std::string const & namePrefix, std::string const & nameSuffix)
: m_filePath(GetPlatform().TmpPathForFile(namePrefix, nameSuffix))
{
}
TemporaryFile::~TemporaryFile()
{
Platform::RemoveFileIfExists(m_filePath);
}
void TemporaryFile::WriteData(string const & data)
{
FileWriter writer(m_filePath);
writer.Write(data.data(), data.size());
writer.Flush();
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <string>
class TemporaryFile
{
public:
TemporaryFile();
TemporaryFile(std::string const & namePrefix, std::string const & nameSuffix);
TemporaryFile(TemporaryFile const &) = delete;
TemporaryFile & operator=(TemporaryFile const &) = delete;
~TemporaryFile();
std::string const & GetFilePath() const
{
return m_filePath;
}
void WriteData(std::string const & data);
private:
std::string m_filePath;
};

Some files were not shown because too many files have changed in this diff Show More