[routing] Relaxed roundabout skip turns check (driveway, parking_aisle).

Signed-off-by: Viktor Govako <viktor.govako@gmail.com>
This commit is contained in:
Viktor Govako
2025-06-29 18:31:43 -03:00
committed by Konstantin Pastbin
parent 014b54b1f7
commit effc8ba5d1
8 changed files with 129 additions and 69 deletions

View File

@@ -352,18 +352,19 @@ void RuleDrawer::ProcessLineStyle(FeatureType & f, Stylist const & s, TInsertSha
if (m_context->IsTrafficEnabled() && m_zoomLevel >= kRoadClass0ZoomLevel) if (m_context->IsTrafficEnabled() && m_zoomLevel >= kRoadClass0ZoomLevel)
{ {
using ftypes::HighwayClass;
struct Checker struct Checker
{ {
std::vector<ftypes::HighwayClass> m_highwayClasses; std::vector<HighwayClass> m_highwayClasses;
int m_zoomLevel; int m_zoomLevel;
df::RoadClass m_roadClass; df::RoadClass m_roadClass;
}; };
static Checker const checkers[] = { static Checker const checkers[] = {
{{ftypes::HighwayClass::Trunk, ftypes::HighwayClass::Primary}, {{HighwayClass::Trunk, HighwayClass::Primary},
kRoadClass0ZoomLevel, df::RoadClass::Class0}, kRoadClass0ZoomLevel, df::RoadClass::Class0},
{{ftypes::HighwayClass::Secondary, ftypes::HighwayClass::Tertiary}, {{HighwayClass::Secondary, HighwayClass::Tertiary},
kRoadClass1ZoomLevel, df::RoadClass::Class1}, kRoadClass1ZoomLevel, df::RoadClass::Class1},
{{ftypes::HighwayClass::LivingStreet, ftypes::HighwayClass::Service}, {{HighwayClass::LivingStreet, HighwayClass::Service, HighwayClass::ServiceMinor},
kRoadClass2ZoomLevel, df::RoadClass::Class2} kRoadClass2ZoomLevel, df::RoadClass::Class2}
}; };

View File

@@ -22,71 +22,76 @@ namespace
{ {
class HighwayClasses class HighwayClasses
{ {
map<uint32_t, ftypes::HighwayClass> m_map; map<uint32_t, HighwayClass> m_map;
public: public:
HighwayClasses() HighwayClasses()
{ {
auto const & c = classif(); auto const & c = classif();
m_map[c.GetTypeByPath({"route", "ferry"})] = ftypes::HighwayClass::Transported; m_map[c.GetTypeByPath({"route", "ferry"})] = HighwayClass::Transported;
m_map[c.GetTypeByPath({"route", "shuttle_train"})] = ftypes::HighwayClass::Transported; m_map[c.GetTypeByPath({"route", "shuttle_train"})] = HighwayClass::Transported;
m_map[c.GetTypeByPath({"highway", "motorway"})] = ftypes::HighwayClass::Trunk; m_map[c.GetTypeByPath({"highway", "motorway"})] = HighwayClass::Trunk;
m_map[c.GetTypeByPath({"highway", "motorway_link"})] = ftypes::HighwayClass::Trunk; m_map[c.GetTypeByPath({"highway", "motorway_link"})] = HighwayClass::Trunk;
m_map[c.GetTypeByPath({"highway", "trunk"})] = ftypes::HighwayClass::Trunk; m_map[c.GetTypeByPath({"highway", "trunk"})] = HighwayClass::Trunk;
m_map[c.GetTypeByPath({"highway", "trunk_link"})] = ftypes::HighwayClass::Trunk; m_map[c.GetTypeByPath({"highway", "trunk_link"})] = HighwayClass::Trunk;
m_map[c.GetTypeByPath({"highway", "primary"})] = ftypes::HighwayClass::Primary; m_map[c.GetTypeByPath({"highway", "primary"})] = HighwayClass::Primary;
m_map[c.GetTypeByPath({"highway", "primary_link"})] = ftypes::HighwayClass::Primary; m_map[c.GetTypeByPath({"highway", "primary_link"})] = HighwayClass::Primary;
m_map[c.GetTypeByPath({"highway", "secondary"})] = ftypes::HighwayClass::Secondary; m_map[c.GetTypeByPath({"highway", "secondary"})] = HighwayClass::Secondary;
m_map[c.GetTypeByPath({"highway", "secondary_link"})] = ftypes::HighwayClass::Secondary; m_map[c.GetTypeByPath({"highway", "secondary_link"})] = HighwayClass::Secondary;
m_map[c.GetTypeByPath({"highway", "tertiary"})] = ftypes::HighwayClass::Tertiary; m_map[c.GetTypeByPath({"highway", "tertiary"})] = HighwayClass::Tertiary;
m_map[c.GetTypeByPath({"highway", "tertiary_link"})] = ftypes::HighwayClass::Tertiary; m_map[c.GetTypeByPath({"highway", "tertiary_link"})] = HighwayClass::Tertiary;
m_map[c.GetTypeByPath({"highway", "unclassified"})] = ftypes::HighwayClass::LivingStreet; m_map[c.GetTypeByPath({"highway", "unclassified"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "residential"})] = ftypes::HighwayClass::LivingStreet; m_map[c.GetTypeByPath({"highway", "residential"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "living_street"})] = ftypes::HighwayClass::LivingStreet; m_map[c.GetTypeByPath({"highway", "living_street"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "road"})] = ftypes::HighwayClass::LivingStreet; m_map[c.GetTypeByPath({"highway", "road"})] = HighwayClass::LivingStreet;
m_map[c.GetTypeByPath({"highway", "service"})] = ftypes::HighwayClass::Service; m_map[c.GetTypeByPath({"highway", "service"})] = HighwayClass::Service;
m_map[c.GetTypeByPath({"highway", "track"})] = ftypes::HighwayClass::Service; m_map[c.GetTypeByPath({"highway", "track"})] = HighwayClass::Service;
m_map[c.GetTypeByPath({"highway", "busway"})] = ftypes::HighwayClass::Service; m_map[c.GetTypeByPath({"highway", "busway"})] = HighwayClass::Service;
m_map[c.GetTypeByPath({"man_made", "pier"})] = ftypes::HighwayClass::Service; m_map[c.GetTypeByPath({"man_made", "pier"})] = HighwayClass::Service;
m_map[c.GetTypeByPath({"highway", "pedestrian"})] = ftypes::HighwayClass::Pedestrian; // 3-level types.
m_map[c.GetTypeByPath({"highway", "footway"})] = ftypes::HighwayClass::Pedestrian; m_map[c.GetTypeByPath({"highway", "service", "driveway"})] = HighwayClass::ServiceMinor;
m_map[c.GetTypeByPath({"highway", "bridleway"})] = ftypes::HighwayClass::Pedestrian; m_map[c.GetTypeByPath({"highway", "service", "parking_aisle"})] = HighwayClass::ServiceMinor;
m_map[c.GetTypeByPath({"highway", "steps"})] = ftypes::HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "cycleway"})] = ftypes::HighwayClass::Pedestrian; m_map[c.GetTypeByPath({"highway", "pedestrian"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "path"})] = ftypes::HighwayClass::Pedestrian; m_map[c.GetTypeByPath({"highway", "footway"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "construction"})] = ftypes::HighwayClass::Pedestrian; m_map[c.GetTypeByPath({"highway", "bridleway"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "steps"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "cycleway"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "path"})] = HighwayClass::Pedestrian;
m_map[c.GetTypeByPath({"highway", "construction"})] = HighwayClass::Pedestrian;
} }
ftypes::HighwayClass Get(uint32_t t) const HighwayClass Get(uint32_t t) const
{ {
auto const it = m_map.find(t); auto const it = m_map.find(t);
if (it == m_map.cend()) if (it == m_map.cend())
return ftypes::HighwayClass::Undefined; return HighwayClass::Undefined;
return it->second; return it->second;
} }
}; };
char const * HighwayClassToString(ftypes::HighwayClass const cls) char const * HighwayClassToString(HighwayClass const cls)
{ {
switch (cls) switch (cls)
{ {
case ftypes::HighwayClass::Undefined: return "Undefined"; case HighwayClass::Undefined: return "Undefined";
case ftypes::HighwayClass::Transported: return "Transported"; case HighwayClass::Transported: return "Transported";
case ftypes::HighwayClass::Trunk: return "Trunk"; case HighwayClass::Trunk: return "Trunk";
case ftypes::HighwayClass::Primary: return "Primary"; case HighwayClass::Primary: return "Primary";
case ftypes::HighwayClass::Secondary: return "Secondary"; case HighwayClass::Secondary: return "Secondary";
case ftypes::HighwayClass::Tertiary: return "Tertiary"; case HighwayClass::Tertiary: return "Tertiary";
case ftypes::HighwayClass::LivingStreet: return "LivingStreet"; case HighwayClass::LivingStreet: return "LivingStreet";
case ftypes::HighwayClass::Service: return "Service"; case HighwayClass::Service: return "Service";
case ftypes::HighwayClass::Pedestrian: return "Pedestrian"; case HighwayClass::ServiceMinor: return "ServiceMinor";
case ftypes::HighwayClass::Count: return "Count"; case HighwayClass::Pedestrian: return "Pedestrian";
case HighwayClass::Count: return "Count";
} }
ASSERT(false, ()); ASSERT(false, ());
return ""; return "";
@@ -121,10 +126,13 @@ HighwayClass GetHighwayClass(feature::TypesHolder const & types)
for (auto t : types) for (auto t : types)
{ {
ftype::TruncValue(t, 2); for (uint8_t level : {3, 2})
HighwayClass const hc = highwayClasses.Get(t); {
if (hc != HighwayClass::Undefined) ftype::TruncValue(t, level);
return hc; HighwayClass const hc = highwayClasses.Get(t);
if (hc != HighwayClass::Undefined)
return hc;
}
} }
return HighwayClass::Undefined; return HighwayClass::Undefined;

View File

@@ -635,6 +635,9 @@ enum class HighwayClass
Tertiary, Tertiary,
LivingStreet, LivingStreet,
Service, Service,
// OSM highway=service type is widely used even for _significant_ roads.
// Adding a new type to distinguish mapped driveway or parking_aisle.
ServiceMinor,
Pedestrian, Pedestrian,
Transported, // Vehicles are transported by train or ferry. Transported, // Vehicles are transported by train or ferry.
Count // This value is used for internals only. Count // This value is used for internals only.

View File

@@ -11,8 +11,7 @@
#include <algorithm> #include <algorithm>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <string>
#include <type_traits>
namespace namespace
{ {
@@ -29,6 +28,7 @@ openlr::FunctionalRoadClass HighwayClassToFunctionalRoadClass(ftypes::HighwayCla
case ftypes::HighwayClass::Tertiary: return openlr::FunctionalRoadClass::FRC3; case ftypes::HighwayClass::Tertiary: return openlr::FunctionalRoadClass::FRC3;
case ftypes::HighwayClass::LivingStreet: return openlr::FunctionalRoadClass::FRC4; case ftypes::HighwayClass::LivingStreet: return openlr::FunctionalRoadClass::FRC4;
case ftypes::HighwayClass::Service: return openlr::FunctionalRoadClass::FRC5; case ftypes::HighwayClass::Service: return openlr::FunctionalRoadClass::FRC5;
case ftypes::HighwayClass::ServiceMinor: return openlr::FunctionalRoadClass::FRC6;
default: return openlr::FunctionalRoadClass::FRC7; default: return openlr::FunctionalRoadClass::FRC7;
} }
} }
@@ -45,38 +45,42 @@ optional<Score> GetFrcScore(Graph::Edge const & e, FunctionalRoadClass functiona
auto const hwClass = infoGetter.Get(e.GetFeatureId()).m_hwClass; auto const hwClass = infoGetter.Get(e.GetFeatureId()).m_hwClass;
using ftypes::HighwayClass;
switch (functionalRoadClass) switch (functionalRoadClass)
{ {
case FunctionalRoadClass::FRC0: case FunctionalRoadClass::FRC0:
// Note. HighwayClass::Trunk means motorway, motorway_link, trunk or trunk_link. // Note. HighwayClass::Trunk means motorway, motorway_link, trunk or trunk_link.
return hwClass == ftypes::HighwayClass::Trunk ? optional<Score>(kMaxScoreForFrc) : nullopt; return hwClass == HighwayClass::Trunk ? optional<Score>(kMaxScoreForFrc) : nullopt;
case FunctionalRoadClass::FRC1: case FunctionalRoadClass::FRC1:
return (hwClass == ftypes::HighwayClass::Trunk || hwClass == ftypes::HighwayClass::Primary) return (hwClass == HighwayClass::Trunk || hwClass == HighwayClass::Primary)
? optional<Score>(kMaxScoreForFrc) ? optional<Score>(kMaxScoreForFrc)
: nullopt; : nullopt;
case FunctionalRoadClass::FRC2: case FunctionalRoadClass::FRC2:
case FunctionalRoadClass::FRC3: case FunctionalRoadClass::FRC3:
if (hwClass == ftypes::HighwayClass::Secondary || hwClass == ftypes::HighwayClass::Tertiary) if (hwClass == HighwayClass::Secondary || hwClass == HighwayClass::Tertiary)
return optional<Score>(kMaxScoreForFrc); return optional<Score>(kMaxScoreForFrc);
return hwClass == ftypes::HighwayClass::Primary || hwClass == ftypes::HighwayClass::LivingStreet return hwClass == HighwayClass::Primary || hwClass == HighwayClass::LivingStreet
? optional<Score>(0) ? optional<Score>(0)
: nullopt; : nullopt;
case FunctionalRoadClass::FRC4: case FunctionalRoadClass::FRC4:
if (hwClass == ftypes::HighwayClass::LivingStreet || hwClass == ftypes::HighwayClass::Service) if (hwClass == HighwayClass::LivingStreet || hwClass == HighwayClass::Service)
return optional<Score>(kMaxScoreForFrc); return optional<Score>(kMaxScoreForFrc);
return (hwClass == ftypes::HighwayClass::Tertiary || hwClass == ftypes::HighwayClass::Secondary) return (hwClass == HighwayClass::Tertiary || hwClass == HighwayClass::Secondary)
? optional<Score>(0) ? optional<Score>(0)
: nullopt; : nullopt;
case FunctionalRoadClass::FRC5: case FunctionalRoadClass::FRC5:
case FunctionalRoadClass::FRC6: case FunctionalRoadClass::FRC6:
case FunctionalRoadClass::FRC7: case FunctionalRoadClass::FRC7:
return hwClass == ftypes::HighwayClass::LivingStreet || hwClass == ftypes::HighwayClass::Service return (hwClass == HighwayClass::LivingStreet ||
hwClass == HighwayClass::Service ||
hwClass == HighwayClass::ServiceMinor)
? optional<Score>(kMaxScoreForFrc) ? optional<Score>(kMaxScoreForFrc)
: nullopt; : nullopt;

View File

@@ -6,7 +6,6 @@
#include "geometry/angles.hpp" #include "geometry/angles.hpp"
#include <limits.h>
namespace routing namespace routing
{ {
@@ -175,8 +174,8 @@ bool KeepRoundaboutTurnByHighwayClass(TurnCandidates const & possibleTurns,
if (t.m_segment == firstOutgoingSegment) if (t.m_segment == firstOutgoingSegment)
continue; continue;
// For roundabouts of Tertiary and less significant class count every road. // For roundabouts of Tertiary and less significant class count every road.
// For more significant roundabouts - ignore service roads. // For more significant roundabouts - ignore service roads (driveway, parking_aisle).
if (turnInfo.m_outgoing->m_highwayClass >= HighwayClass::Tertiary || t.m_highwayClass != HighwayClass::Service) if (turnInfo.m_outgoing->m_highwayClass >= HighwayClass::Tertiary || t.m_highwayClass != HighwayClass::ServiceMinor)
return true; return true;
} }
return false; return false;
@@ -303,7 +302,8 @@ bool CanDiscardTurnByHighwayClassOrAngles(CarDirection const routeDirection,
continue; continue;
// If outgoing route's road is significantly larger than candidate's service road, the candidate can be ignored. // If outgoing route's road is significantly larger than candidate's service road, the candidate can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiffForService && candidateRoadClass == HighwayClass::Service) if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiffForService &&
(candidateRoadClass == HighwayClass::Service || candidateRoadClass == HighwayClass::ServiceMinor))
continue; continue;
// If igngoing and outgoing edges are not links // If igngoing and outgoing edges are not links
@@ -445,7 +445,8 @@ void GetTurnDirectionBasic(IRoutingResult const & result, size_t const outgoingS
return; return;
// No turns are needed on transported road. // No turns are needed on transported road.
if (turnInfo.m_ingoing->m_highwayClass == HighwayClass::Transported && turnInfo.m_outgoing->m_highwayClass == HighwayClass::Transported) if (turnInfo.m_ingoing->m_highwayClass == HighwayClass::Transported &&
turnInfo.m_outgoing->m_highwayClass == HighwayClass::Transported)
return; return;
Segment firstOutgoingSeg; Segment firstOutgoingSeg;

View File

@@ -93,6 +93,8 @@ UNIT_TEST(Russia_Moscow_TrikotagniAndPohodniRoundabout_TurnTest)
Route const & route = *routeResult.first; Route const & route = *routeResult.first;
RouterResultCode const result = routeResult.second; RouterResultCode const result = routeResult.second;
/// @todo Simple highway=service is now included in turns count.
/// Fix OSM on driveway/parking_aisle ?
TEST_EQUAL(result, RouterResultCode::NoError, ()); TEST_EQUAL(result, RouterResultCode::NoError, ());
integration::TestTurnCount(route, 2 /* expectedTurnCount */); integration::TestTurnCount(route, 2 /* expectedTurnCount */);
@@ -1389,4 +1391,39 @@ UNIT_TEST(UK_Junction_Circular)
GetNthTurn(route, 1).TestValid().TestDirection(CarDirection::LeaveRoundAbout); GetNthTurn(route, 1).TestValid().TestDirection(CarDirection::LeaveRoundAbout);
} }
UNIT_TEST(Integrated_TurnTest_IncludeServiceRoads)
{
struct Sample
{
ms::LatLon start, finish;
int expectedTurns;
};
Sample arr[] = {
// https://github.com/organicmaps/organicmaps/issues/8892
{{50.128011, 14.7100098}, {50.1283017, 14.7119639}, 3},
{{50.1283462, 14.7122953}, {50.1280032, 14.7099638}, 3},
// https://github.com/organicmaps/organicmaps/issues/5888
{{58.8428062, 5.71619759}, {58.8422583, 5.71672851}, 3},
// https://github.com/organicmaps/organicmaps/issues/3596
{{38.7114203, 0.0365096768}, {38.7103102, 0.0349380496}, 2},
};
for (auto const & s : arr)
{
using namespace integration;
TRouteResult const routeResult = CalculateRoute(GetVehicleComponents(VehicleType::Car),
mercator::FromLatLon(s.start), {0., 0.},
mercator::FromLatLon(s.finish));
Route const & route = *routeResult.first;
RouterResultCode const result = routeResult.second;
TEST_EQUAL(result, RouterResultCode::NoError, ());
GetNthTurn(route, 0)
.TestValid()
.TestDirection(CarDirection::EnterRoundAbout)
.TestRoundAboutExitNum(s.expectedTurns);
}
}
} // namespace turn_test } // namespace turn_test

View File

@@ -4,7 +4,6 @@
#include "indexer/ftypes_matcher.hpp" #include "indexer/ftypes_matcher.hpp"
#include "base/stl_helpers.hpp"
namespace routing namespace routing
{ {
@@ -47,7 +46,8 @@ bool CanDiscardTurnByHighwayClass(std::vector<TurnCandidate> const & turnCandida
continue; continue;
// If route's road is significantly larger than candidate's service road, the candidate can be ignored. // If route's road is significantly larger than candidate's service road, the candidate can be ignored.
if (CalcDiffRoadClasses(maxRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiffForService && candidateRoadClass == HighwayClass::Service) if (CalcDiffRoadClasses(maxRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiffForService &&
(candidateRoadClass == HighwayClass::Service || candidateRoadClass == HighwayClass::ServiceMinor))
continue; continue;
return false; return false;

View File

@@ -1,8 +1,11 @@
#include "routing/turns_generator_utils.hpp" #include "routing/turns_generator_utils.hpp"
#include "platform/measurement_utils.hpp"
#include "geometry/mercator.hpp"
#include "routing/turns.hpp" #include "routing/turns.hpp"
#include "platform/measurement_utils.hpp"
#include "geometry/mercator.hpp"
namespace routing namespace routing
{ {
namespace turns namespace turns
@@ -19,7 +22,9 @@ bool IsHighway(HighwayClass hwClass, bool isLink)
bool IsSmallRoad(HighwayClass hwClass) bool IsSmallRoad(HighwayClass hwClass)
{ {
return hwClass == HighwayClass::LivingStreet || return hwClass == HighwayClass::LivingStreet ||
hwClass == HighwayClass::Service || hwClass == HighwayClass::Pedestrian; hwClass == HighwayClass::Service ||
hwClass == HighwayClass::ServiceMinor ||
hwClass == HighwayClass::Pedestrian;
} }
int CalcDiffRoadClasses(HighwayClass const left, HighwayClass const right) int CalcDiffRoadClasses(HighwayClass const left, HighwayClass const right)
@@ -120,7 +125,8 @@ double CalcEstimatedTimeToPass(double const distanceMeters, HighwayClass const h
case HighwayClass::Secondary: speedKmph = 70.0; break; case HighwayClass::Secondary: speedKmph = 70.0; break;
case HighwayClass::Tertiary: speedKmph = 50.0; break; case HighwayClass::Tertiary: speedKmph = 50.0; break;
case HighwayClass::LivingStreet: speedKmph = 20.0; break; case HighwayClass::LivingStreet: speedKmph = 20.0; break;
case HighwayClass::Service: speedKmph = 10.0; break; case HighwayClass::Service:
case HighwayClass::ServiceMinor: speedKmph = 10.0; break;
case HighwayClass::Pedestrian: speedKmph = 5.0; break; case HighwayClass::Pedestrian: speedKmph = 5.0; break;
default: speedKmph = 50.0; break; default: speedKmph = 50.0; break;
} }