1 #include "extractor/restriction_parser.hpp"
2 #include "extractor/profile_properties.hpp"
3
4 #include "util/conditional_restrictions.hpp"
5 #include "util/log.hpp"
6
7 #include <boost/algorithm/string/predicate.hpp>
8 #include <boost/optional/optional.hpp>
9 #include <boost/ref.hpp>
10
11 #include <osmium/osm.hpp>
12 #include <osmium/tags/regex_filter.hpp>
13
14 #include <algorithm>
15
16 namespace osrm
17 {
18 namespace extractor
19 {
20
RestrictionParser(bool use_turn_restrictions_,bool parse_conditionals_,std::vector<std::string> & restrictions_)21 RestrictionParser::RestrictionParser(bool use_turn_restrictions_,
22 bool parse_conditionals_,
23 std::vector<std::string> &restrictions_)
24 : use_turn_restrictions(use_turn_restrictions_), parse_conditionals(parse_conditionals_),
25 restrictions(restrictions_)
26 {
27 if (use_turn_restrictions)
28 {
29 const unsigned count = restrictions.size();
30 if (count > 0)
31 {
32 util::Log() << "Found " << count << " turn restriction tags:";
33 for (const std::string &str : restrictions)
34 {
35 util::Log() << " " << str;
36 }
37 }
38 else
39 {
40 util::Log() << "Found no turn restriction tags";
41 }
42 }
43 }
44
45 /**
46 * Tries to parse a relation as a turn restriction. This can fail for a number of
47 * reasons. The return type is a boost::optional<T>.
48 *
49 * Some restrictions can also be ignored: See the ```get_restrictions``` function
50 * in the corresponding profile. We use it for both namespacing restrictions, as in
51 * restriction:motorcar as well as whitelisting if its in except:motorcar.
52 */
53 boost::optional<InputTurnRestriction>
TryParse(const osmium::Relation & relation) const54 RestrictionParser::TryParse(const osmium::Relation &relation) const
55 {
56 // return if turn restrictions should be ignored
57 if (!use_turn_restrictions)
58 {
59 return boost::none;
60 }
61
62 osmium::tags::KeyFilter filter(false);
63 filter.add(true, "restriction");
64 if (parse_conditionals)
65 {
66 filter.add(true, "restriction:conditional");
67 for (const auto &namespaced : restrictions)
68 {
69 filter.add(true, "restriction:" + namespaced + ":conditional");
70 }
71 }
72
73 // Not only use restriction= but also e.g. restriction:motorcar=
74 // Include restriction:{mode}:conditional if flagged
75 for (const auto &namespaced : restrictions)
76 {
77 filter.add(true, "restriction:" + namespaced);
78 }
79
80 const osmium::TagList &tag_list = relation.tags();
81
82 osmium::tags::KeyFilter::iterator fi_begin(filter, tag_list.begin(), tag_list.end());
83 osmium::tags::KeyFilter::iterator fi_end(filter, tag_list.end(), tag_list.end());
84
85 // if it's not a restriction, continue;
86 if (std::distance(fi_begin, fi_end) == 0)
87 {
88 return boost::none;
89 }
90
91 // check if the restriction should be ignored
92 const char *except = relation.get_value_by_key("except");
93 if (except != nullptr && ShouldIgnoreRestriction(except))
94 {
95 return boost::none;
96 }
97
98 bool is_only_restriction = false;
99
100 for (; fi_begin != fi_end; ++fi_begin)
101 {
102 const std::string key(fi_begin->key());
103 const std::string value(fi_begin->value());
104
105 // documented OSM restriction tags start either with only_* or no_*;
106 // check and return on these values, and ignore no_*_on_red or unrecognized values
107 if (value.find("only_") == 0)
108 {
109 is_only_restriction = true;
110 }
111 else if (value.find("no_") == 0 && !boost::algorithm::ends_with(value, "_on_red"))
112 {
113 is_only_restriction = false;
114 }
115 else // unrecognized value type
116 {
117 return boost::none;
118 }
119 }
120
121 InputTurnRestriction restriction_container;
122 restriction_container.is_only = is_only_restriction;
123
124 constexpr auto INVALID_OSM_ID = std::numeric_limits<std::uint64_t>::max();
125 auto from = INVALID_OSM_ID;
126 auto via_node = INVALID_OSM_ID;
127 std::vector<OSMWayID> via_ways;
128 auto to = INVALID_OSM_ID;
129 bool is_node_restriction = true;
130
131 for (const auto &member : relation.members())
132 {
133 const char *role = member.role();
134 if (strcmp("from", role) != 0 && strcmp("to", role) != 0 && strcmp("via", role) != 0)
135 {
136 continue;
137 }
138
139 switch (member.type())
140 {
141 case osmium::item_type::node:
142 {
143
144 // Make sure nodes appear only in the role if a via node
145 if (0 == strcmp("from", role) || 0 == strcmp("to", role))
146 {
147 continue;
148 }
149 BOOST_ASSERT(0 == strcmp("via", role));
150 via_node = static_cast<std::uint64_t>(member.ref());
151 is_node_restriction = true;
152 // set via node id
153 break;
154 }
155 case osmium::item_type::way:
156 BOOST_ASSERT(0 == strcmp("from", role) || 0 == strcmp("to", role) ||
157 0 == strcmp("via", role));
158 if (0 == strcmp("from", role))
159 {
160 from = static_cast<std::uint64_t>(member.ref());
161 }
162 else if (0 == strcmp("to", role))
163 {
164 to = static_cast<std::uint64_t>(member.ref());
165 }
166 else if (0 == strcmp("via", role))
167 {
168 via_ways.push_back({static_cast<std::uint64_t>(member.ref())});
169 is_node_restriction = false;
170 }
171 break;
172 case osmium::item_type::relation:
173 // not yet supported, but who knows what the future holds...
174 break;
175 default:
176 // shouldn't ever happen
177 break;
178 }
179 }
180
181 // parse conditional tags
182 if (parse_conditionals)
183 {
184 osmium::tags::KeyFilter::iterator fi_begin(filter, tag_list.begin(), tag_list.end());
185 osmium::tags::KeyFilter::iterator fi_end(filter, tag_list.end(), tag_list.end());
186 for (; fi_begin != fi_end; ++fi_begin)
187 {
188 const std::string key(fi_begin->key());
189 const std::string value(fi_begin->value());
190
191 // Parse condition and add independent value/condition pairs
192 const auto &parsed = osrm::util::ParseConditionalRestrictions(value);
193
194 if (parsed.empty())
195 continue;
196
197 for (const auto &p : parsed)
198 {
199 std::vector<util::OpeningHours> hours = util::ParseOpeningHours(p.condition);
200 // found unrecognized condition, continue
201 if (hours.empty())
202 return boost::none;
203
204 restriction_container.condition = std::move(hours);
205 }
206 }
207 }
208
209 if (from != INVALID_OSM_ID && (via_node != INVALID_OSM_ID || !via_ways.empty()) &&
210 to != INVALID_OSM_ID)
211 {
212 if (is_node_restriction)
213 {
214 // template struct requires bracket for ID initialisation :(
215 restriction_container.node_or_way = InputNodeRestriction{{from}, {via_node}, {to}};
216 }
217 else
218 {
219 // template struct requires bracket for ID initialisation :(
220 restriction_container.node_or_way = InputWayRestriction{{from}, via_ways, {to}};
221 }
222 return restriction_container;
223 }
224 else
225 {
226 return boost::none;
227 }
228 }
229
ShouldIgnoreRestriction(const std::string & except_tag_string) const230 bool RestrictionParser::ShouldIgnoreRestriction(const std::string &except_tag_string) const
231 {
232 // should this restriction be ignored? yes if there's an overlap between:
233 // a) the list of modes in the except tag of the restriction
234 // (except_tag_string), eg: except=bus;bicycle
235 // b) the lua profile defines a hierarchy of modes,
236 // eg: [access, vehicle, bicycle]
237
238 if (except_tag_string.empty())
239 {
240 return false;
241 }
242
243 // Be warned, this is quadratic work here, but we assume that
244 // only a few exceptions are actually defined.
245 const std::regex delimiter_re("[;][ ]*");
246 std::sregex_token_iterator except_tags_begin(
247 except_tag_string.begin(), except_tag_string.end(), delimiter_re, -1);
248 std::sregex_token_iterator except_tags_end;
249
250 return std::any_of(except_tags_begin, except_tags_end, [&](const std::string ¤t_string) {
251 return std::end(restrictions) !=
252 std::find(std::begin(restrictions), std::end(restrictions), current_string);
253 });
254 }
255 } // namespace extractor
256 } // namespace osrm
257