1 #ifndef OSRM_UTIL_GUIDANCE_NAME_ANNOUNCEMENT_HPP_
2 #define OSRM_UTIL_GUIDANCE_NAME_ANNOUNCEMENT_HPP_
3 
4 /* A set of tools required for guidance in both pre and post-processing */
5 
6 #include "extractor/name_table.hpp"
7 #include "extractor/suffix_table.hpp"
8 
9 #include "util/attributes.hpp"
10 #include "util/typedefs.hpp"
11 
12 #include <algorithm>
13 #include <string>
14 #include <tuple>
15 #include <utility>
16 #include <vector>
17 
18 #include <boost/algorithm/string.hpp>
19 #include <boost/algorithm/string/predicate.hpp>
20 
21 namespace osrm
22 {
23 namespace util
24 {
25 namespace guidance
26 {
27 // Name Change Logic
28 // Used both during Extraction as well as during Post-Processing
29 
longest_common_substring(const util::StringView & lhs,const util::StringView & rhs)30 inline util::StringView longest_common_substring(const util::StringView &lhs,
31                                                  const util::StringView &rhs)
32 {
33     if (lhs.empty() || rhs.empty())
34         return "";
35 
36     // array for dynamic programming
37     std::vector<std::uint32_t> dp_previous(rhs.size(), 0), dp_current(rhs.size(), 0);
38 
39     // to remember the best location
40     std::uint32_t best = 0;
41     std::uint32_t best_pos = 0;
42     using std::swap;
43     for (std::uint32_t i = 0; i < lhs.size(); ++i)
44     {
45         for (std::uint32_t j = 0; j < rhs.size(); ++j)
46         {
47             if (lhs[i] == rhs[j])
48             {
49                 dp_current[j] = (j == 0) ? 1 : (dp_previous[j - 1] + 1);
50                 if (dp_current[j] > best)
51                 {
52                     best = dp_current[j];
53                     best_pos = i + 1;
54                 }
55             }
56         }
57         swap(dp_previous, dp_current);
58     }
59     // the best position marks the end of the string
60     return lhs.substr(best_pos - best, best);
61 }
62 
63 // TODO US-ASCII support only, no UTF-8 support
64 // While UTF-8 might work in some cases, we do not guarantee full functionality
decompose(const StringView & lhs,const StringView & rhs)65 template <typename StringView> inline auto decompose(const StringView &lhs, const StringView &rhs)
66 {
67     auto const lcs = longest_common_substring(lhs, rhs);
68 
69     // trim spaces, transform to lower
70     const auto trim = [](StringView view) {
71         // we compare suffixes based on this value, it might break UTF chars, but as long as we are
72         // consistent in handling, we do not create bad results
73         std::string str = boost::to_lower_copy(view.to_string());
74         auto front = str.find_first_not_of(" ");
75 
76         if (front == std::string::npos)
77             return str;
78 
79         auto back = str.find_last_not_of(" ");
80         return str.substr(front, back - front + 1);
81     };
82 
83     if (lcs.empty())
84     {
85         return std::make_tuple(trim(lhs), trim(rhs), std::string(), std::string());
86     }
87 
88     // find the common substring in both
89     auto lhs_pos = lhs.find(lcs);
90     auto rhs_pos = rhs.find(lcs);
91 
92     BOOST_ASSERT(lhs_pos + lcs.size() <= lhs.size());
93     BOOST_ASSERT(rhs_pos + lcs.size() <= rhs.size());
94 
95     // prefixes
96     auto lhs_prefix = (lhs_pos > 0) ? lhs.substr(0, lhs_pos) : StringView();
97     auto rhs_prefix = (rhs_pos > 0) ? rhs.substr(0, rhs_pos) : StringView();
98 
99     // suffices
100     auto lhs_suffix = lhs.substr(lhs_pos + lcs.size());
101     auto rhs_suffix = rhs.substr(rhs_pos + lcs.size());
102 
103     return std::make_tuple(trim(lhs_prefix), trim(lhs_suffix), trim(rhs_prefix), trim(rhs_suffix));
104 }
105 
106 // Note: there is an overload without suffix checking below.
107 // (that's the reason we template the suffix table here)
108 template <typename StringView, typename SuffixTable>
requiresNameAnnounced(const StringView & from_name,const StringView & from_ref,const StringView & from_pronunciation,const StringView & from_exits,const StringView & to_name,const StringView & to_ref,const StringView & to_pronunciation,const StringView & to_exits,const SuffixTable & suffix_table)109 inline bool requiresNameAnnounced(const StringView &from_name,
110                                   const StringView &from_ref,
111                                   const StringView &from_pronunciation,
112                                   const StringView &from_exits,
113                                   const StringView &to_name,
114                                   const StringView &to_ref,
115                                   const StringView &to_pronunciation,
116                                   const StringView &to_exits,
117                                   const SuffixTable &suffix_table)
118 {
119     // first is empty and the second is not
120     if ((from_name.empty() && from_ref.empty()) && !(to_name.empty() && to_ref.empty()))
121         return true;
122 
123     // FIXME, handle in profile to begin with?
124     // Input for this function should be a struct separating streetname, suffix (e.g. road,
125     // boulevard, North, West ...), and a list of references
126 
127     // check similarity of names
128     const auto names_are_empty = from_name.empty() && to_name.empty();
129     const auto name_is_contained =
130         boost::starts_with(from_name, to_name) || boost::starts_with(to_name, from_name);
131 
132     const auto checkForPrefixOrSuffixChange =
133         [](const StringView &first, const StringView &second, const SuffixTable &suffix_table) {
134             std::string first_prefix, first_suffix, second_prefix, second_suffix;
135             std::tie(first_prefix, first_suffix, second_prefix, second_suffix) =
136                 decompose(first, second);
137 
138             const auto checkTable = [&](const std::string &str) {
139                 return str.empty() || suffix_table.isSuffix(str);
140             };
141 
142             return checkTable(first_prefix) && checkTable(first_suffix) &&
143                    checkTable(second_prefix) && checkTable(second_suffix);
144         };
145 
146     const auto is_suffix_change = checkForPrefixOrSuffixChange(from_name, to_name, suffix_table);
147     const auto names_are_equal = from_name == to_name || name_is_contained || is_suffix_change;
148     const auto name_is_removed = !from_name.empty() && to_name.empty();
149     // references are contained in one another
150     const auto refs_are_empty = from_ref.empty() && to_ref.empty();
151     const auto ref_is_contained =
152         from_ref.empty() || to_ref.empty() ||
153         (from_ref.find(to_ref) != std::string::npos || to_ref.find(from_ref) != std::string::npos);
154     const auto ref_is_removed = !from_ref.empty() && to_ref.empty();
155 
156     const auto obvious_change =
157         (names_are_empty && refs_are_empty) || (names_are_equal && ref_is_contained) ||
158         (names_are_equal && refs_are_empty) || (ref_is_contained && name_is_removed) ||
159         (names_are_equal && ref_is_removed) || is_suffix_change;
160 
161     const auto needs_announce =
162         // " (Ref)" -> "Name " and reverse
163         (from_name.empty() && !from_ref.empty() && !to_name.empty() && to_ref.empty()) ||
164         (!from_name.empty() && from_ref.empty() && to_name.empty() && !to_ref.empty()) ||
165         // ... or names are empty but reference changed
166         (names_are_empty && !ref_is_contained);
167 
168     const auto pronunciation_changes = from_pronunciation != to_pronunciation;
169 
170     // when exiting onto ramps, we need to be careful about exit numbers. These can often be only
171     // assigned to the first part of the ramp
172     //
173     //  a . . b . c . . d
174     //         ` e . . f
175     //
176     // could assign the exit number to `be` when exiting `abcd` instead of the full ramp.
177     //
178     // Issuing a new-name instruction here would result in the turn assuming the short segment to be
179     // irrelevant and remove the exit number in a collapse scenario. We don't want to issue any
180     // instruction from be-ef, since we only loose the exit number. So we want to make sure that we
181     // don't just loose an exit number, when exits change
182     const auto exits_change = from_exits != to_exits;
183     const auto looses_exit = (names_are_equal && !from_exits.empty() && to_exits.empty());
184 
185     return !obvious_change || needs_announce || pronunciation_changes ||
186            (exits_change && !looses_exit);
187 }
188 
189 // Overload without suffix checking
requiresNameAnnounced(const std::string & from_name,const std::string & from_ref,const std::string & from_pronunciation,const std::string & from_exits,const std::string & to_name,const std::string & to_ref,const std::string & to_pronunciation,const std::string & to_exits)190 inline bool requiresNameAnnounced(const std::string &from_name,
191                                   const std::string &from_ref,
192                                   const std::string &from_pronunciation,
193                                   const std::string &from_exits,
194                                   const std::string &to_name,
195                                   const std::string &to_ref,
196                                   const std::string &to_pronunciation,
197                                   const std::string &to_exits)
198 {
199     // Dummy since we need to provide a SuffixTable but do not have the data for it.
200     // (Guidance Post-Processing does not keep the suffix table around at the moment)
201     struct NopSuffixTable final
202     {
203         NopSuffixTable() {}
204         bool isSuffix(const StringView &) const { return false; }
205     } static const table;
206 
207     return requiresNameAnnounced(util::StringView(from_name),
208                                  util::StringView(from_ref),
209                                  util::StringView(from_pronunciation),
210                                  util::StringView(from_exits),
211                                  util::StringView(to_name),
212                                  util::StringView(to_ref),
213                                  util::StringView(to_pronunciation),
214                                  util::StringView(to_exits),
215                                  table);
216 }
217 
requiresNameAnnounced(const NameID from_name_id,const NameID to_name_id,const extractor::NameTable & name_table,const extractor::SuffixTable & suffix_table)218 inline bool requiresNameAnnounced(const NameID from_name_id,
219                                   const NameID to_name_id,
220                                   const extractor::NameTable &name_table,
221                                   const extractor::SuffixTable &suffix_table)
222 {
223     if (from_name_id == to_name_id)
224         return false;
225     else
226         return requiresNameAnnounced(name_table.GetNameForID(from_name_id),
227                                      name_table.GetRefForID(from_name_id),
228                                      name_table.GetPronunciationForID(from_name_id),
229                                      name_table.GetExitsForID(from_name_id),
230                                      //
231                                      name_table.GetNameForID(to_name_id),
232                                      name_table.GetRefForID(to_name_id),
233                                      name_table.GetPronunciationForID(to_name_id),
234                                      name_table.GetExitsForID(to_name_id),
235                                      //
236                                      suffix_table);
237 }
238 
239 } // namespace guidance
240 } // namespace util
241 } // namespace osrm
242 
243 #endif /* OSRM_UTIL_GUIDANCE_NAME_ANNOUNCEMENT_HPP_ */
244