osrm-backend/include/util/guidance/toolkit.hpp
Moritz Kobitzsch 5e167b8745 Turn Angles in OSRM were computed using a lookahead of 10 meters.
This PR adds more advanced coordinate extraction, analysing the road
to detect offsets due to OSM way modelling.

In addition it improves the handling of bearings. Right now OSM reports
bearings simply based on the very first coordinate along a way.
With this PR, we store the bearings for a turn correctly, making the
bearings for turns correct.
2016-10-20 10:47:29 +02:00

272 lines
11 KiB
C++

#ifndef OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_
#define OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_
/* A set of tools required for guidance in both pre and post-processing */
#include "extractor/guidance/turn_instruction.hpp"
#include "engine/guidance/route_step.hpp"
#include "engine/phantom_node.hpp"
#include "util/attributes.hpp"
#include "util/guidance/bearing_class.hpp"
#include "util/guidance/entry_class.hpp"
#include "util/simple_logger.hpp"
#include <algorithm>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
namespace osrm
{
namespace util
{
namespace guidance
{
inline double angularDeviation(const double angle, const double from)
{
const double deviation = std::abs(angle - from);
return std::min(360 - deviation, deviation);
}
inline bool hasRampType(const extractor::guidance::TurnInstruction instruction)
{
return instruction.type == extractor::guidance::TurnType::OffRamp ||
instruction.type == extractor::guidance::TurnType::OnRamp;
}
inline extractor::guidance::DirectionModifier::Enum getTurnDirection(const double angle)
{
// An angle of zero is a u-turn
// 180 goes perfectly straight
// 0-180 are right turns
// 180-360 are left turns
if (angle > 0 && angle < 60)
return extractor::guidance::DirectionModifier::SharpRight;
if (angle >= 60 && angle < 140)
return extractor::guidance::DirectionModifier::Right;
if (angle >= 140 && angle < 170)
return extractor::guidance::DirectionModifier::SlightRight;
if (angle >= 165 && angle <= 195)
return extractor::guidance::DirectionModifier::Straight;
if (angle > 190 && angle <= 220)
return extractor::guidance::DirectionModifier::SlightLeft;
if (angle > 220 && angle <= 300)
return extractor::guidance::DirectionModifier::Left;
if (angle > 300 && angle < 360)
return extractor::guidance::DirectionModifier::SharpLeft;
return extractor::guidance::DirectionModifier::UTurn;
}
// swaps left <-> right modifier types
OSRM_ATTR_WARN_UNUSED
inline extractor::guidance::DirectionModifier::Enum
mirrorDirectionModifier(const extractor::guidance::DirectionModifier::Enum modifier)
{
const constexpr extractor::guidance::DirectionModifier::Enum results[] = {
extractor::guidance::DirectionModifier::UTurn,
extractor::guidance::DirectionModifier::SharpLeft,
extractor::guidance::DirectionModifier::Left,
extractor::guidance::DirectionModifier::SlightLeft,
extractor::guidance::DirectionModifier::Straight,
extractor::guidance::DirectionModifier::SlightRight,
extractor::guidance::DirectionModifier::Right,
extractor::guidance::DirectionModifier::SharpRight};
return results[modifier];
}
inline bool hasLeftModifier(const extractor::guidance::TurnInstruction instruction)
{
return instruction.direction_modifier == extractor::guidance::DirectionModifier::SharpLeft ||
instruction.direction_modifier == extractor::guidance::DirectionModifier::Left ||
instruction.direction_modifier == extractor::guidance::DirectionModifier::SlightLeft;
}
inline bool hasRightModifier(const extractor::guidance::TurnInstruction instruction)
{
return instruction.direction_modifier == extractor::guidance::DirectionModifier::SharpRight ||
instruction.direction_modifier == extractor::guidance::DirectionModifier::Right ||
instruction.direction_modifier == extractor::guidance::DirectionModifier::SlightRight;
}
inline bool isLeftTurn(const extractor::guidance::TurnInstruction instruction)
{
switch (instruction.type)
{
case extractor::guidance::TurnType::Merge:
return hasRightModifier(instruction);
default:
return hasLeftModifier(instruction);
}
}
inline bool isRightTurn(const extractor::guidance::TurnInstruction instruction)
{
switch (instruction.type)
{
case extractor::guidance::TurnType::Merge:
return hasLeftModifier(instruction);
default:
return hasRightModifier(instruction);
}
}
inline bool entersRoundabout(const extractor::guidance::TurnInstruction instruction)
{
return (instruction.type == extractor::guidance::TurnType::EnterRoundabout ||
instruction.type == extractor::guidance::TurnType::EnterRotary ||
instruction.type == extractor::guidance::TurnType::EnterRoundaboutIntersection ||
instruction.type == extractor::guidance::TurnType::EnterRoundaboutAtExit ||
instruction.type == extractor::guidance::TurnType::EnterRotaryAtExit ||
instruction.type == extractor::guidance::TurnType::EnterRoundaboutIntersectionAtExit ||
instruction.type == extractor::guidance::TurnType::EnterAndExitRoundabout ||
instruction.type == extractor::guidance::TurnType::EnterAndExitRotary ||
instruction.type == extractor::guidance::TurnType::EnterAndExitRoundaboutIntersection);
}
inline bool leavesRoundabout(const extractor::guidance::TurnInstruction instruction)
{
return (instruction.type == extractor::guidance::TurnType::ExitRoundabout ||
instruction.type == extractor::guidance::TurnType::ExitRotary ||
instruction.type == extractor::guidance::TurnType::ExitRoundaboutIntersection ||
instruction.type == extractor::guidance::TurnType::EnterAndExitRoundabout ||
instruction.type == extractor::guidance::TurnType::EnterAndExitRotary ||
instruction.type == extractor::guidance::TurnType::EnterAndExitRoundaboutIntersection);
}
inline bool staysOnRoundabout(const extractor::guidance::TurnInstruction instruction)
{
return instruction.type == extractor::guidance::TurnType::StayOnRoundabout;
}
// Name Change Logic
// Used both during Extraction as well as during Post-Processing
inline std::pair<std::string, std::string> getPrefixAndSuffix(const std::string &data)
{
const auto suffix_pos = data.find_last_of(' ');
if (suffix_pos == std::string::npos)
return {};
const auto prefix_pos = data.find_first_of(' ');
auto result = std::make_pair(data.substr(0, prefix_pos), data.substr(suffix_pos + 1));
boost::to_lower(result.first);
boost::to_lower(result.second);
return result;
}
// Note: there is an overload without suffix checking below.
// (that's the reason we template the suffix table here)
template <typename SuffixTable>
inline bool requiresNameAnnounced(const std::string &from_name,
const std::string &from_ref,
const std::string &to_name,
const std::string &to_ref,
const SuffixTable &suffix_table)
{
// first is empty and the second is not
if ((from_name.empty() && from_ref.empty()) && !(to_name.empty() && to_ref.empty()))
return true;
// FIXME, handle in profile to begin with?
// Input for this function should be a struct separating streetname, suffix (e.g. road,
// boulevard, North, West ...), and a list of references
// check similarity of names
const auto names_are_empty = from_name.empty() && to_name.empty();
const auto name_is_contained =
boost::starts_with(from_name, to_name) || boost::starts_with(to_name, from_name);
const auto checkForPrefixOrSuffixChange = [](
const std::string &first, const std::string &second, const SuffixTable &suffix_table) {
const auto first_prefix_and_suffixes = getPrefixAndSuffix(first);
const auto second_prefix_and_suffixes = getPrefixAndSuffix(second);
// reverse strings, get suffices and reverse them to get prefixes
const auto checkTable = [&](const std::string &str) {
return str.empty() || suffix_table.isSuffix(str);
};
const auto getOffset = [](const std::string &str) -> std::size_t {
if (str.empty())
return 0;
else
return str.length() + 1;
};
const bool is_prefix_change = [&]() -> bool {
if (!checkTable(first_prefix_and_suffixes.first))
return false;
if (!checkTable(second_prefix_and_suffixes.first))
return false;
return !first.compare(getOffset(first_prefix_and_suffixes.first),
std::string::npos,
second,
getOffset(second_prefix_and_suffixes.first),
std::string::npos);
}();
const bool is_suffix_change = [&]() -> bool {
if (!checkTable(first_prefix_and_suffixes.second))
return false;
if (!checkTable(second_prefix_and_suffixes.second))
return false;
return !first.compare(0,
first.length() - getOffset(first_prefix_and_suffixes.second),
second,
0,
second.length() - getOffset(second_prefix_and_suffixes.second));
}();
return is_prefix_change || is_suffix_change;
};
const auto is_suffix_change = checkForPrefixOrSuffixChange(from_name, to_name, suffix_table);
const auto names_are_equal = from_name == to_name || name_is_contained || is_suffix_change;
const auto name_is_removed = !from_name.empty() && to_name.empty();
// references are contained in one another
const auto refs_are_empty = from_ref.empty() && to_ref.empty();
const auto ref_is_contained =
from_ref.empty() || to_ref.empty() ||
(from_ref.find(to_ref) != std::string::npos || to_ref.find(from_ref) != std::string::npos);
const auto ref_is_removed = !from_ref.empty() && to_ref.empty();
const auto obvious_change =
(names_are_empty && refs_are_empty) || (names_are_equal && ref_is_contained) ||
(names_are_equal && refs_are_empty) || (ref_is_contained && name_is_removed) ||
(names_are_equal && ref_is_removed) || is_suffix_change;
const auto needs_announce =
// " (Ref)" -> "Name "
(from_name.empty() && !from_ref.empty() && !to_name.empty() && to_ref.empty());
return !obvious_change || needs_announce;
}
// Overload without suffix checking
inline bool requiresNameAnnounced(const std::string &from_name,
const std::string &from_ref,
const std::string &to_name,
const std::string &to_ref)
{
// Dummy since we need to provide a SuffixTable but do not have the data for it.
// (Guidance Post-Processing does not keep the suffix table around at the moment)
struct NopSuffixTable final
{
NopSuffixTable() {}
bool isSuffix(const std::string &) const { return false; }
} static const table;
return requiresNameAnnounced(from_name, from_ref, to_name, to_ref, table);
}
} // namespace guidance
} // namespace util
} // namespace osrm
#endif /* OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_ */