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 &current_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