-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize RestrictionParser performance #6344
Changes from all commits
c14461c
01e2a2d
794ae59
cf223ef
1d86602
ecbee99
c8f28fb
ff704db
70a507f
47f7ad7
94495b5
69a3843
7dfe466
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,14 +2,14 @@ | |
#include "extractor/profile_properties.hpp" | ||
|
||
#include "util/conditional_restrictions.hpp" | ||
#include "util/integer_range.hpp" | ||
#include "util/log.hpp" | ||
|
||
#include <boost/algorithm/string/predicate.hpp> | ||
#include <boost/optional/optional.hpp> | ||
#include <boost/ref.hpp> | ||
|
||
#include <osmium/osm.hpp> | ||
#include <osmium/tags/regex_filter.hpp> | ||
|
||
#include <algorithm> | ||
|
||
|
@@ -20,9 +20,9 @@ namespace extractor | |
|
||
RestrictionParser::RestrictionParser(bool use_turn_restrictions_, | ||
bool parse_conditionals_, | ||
std::vector<std::string> &restrictions_) | ||
const std::vector<std::string> &restrictions_) | ||
: use_turn_restrictions(use_turn_restrictions_), parse_conditionals(parse_conditionals_), | ||
restrictions(restrictions_) | ||
restrictions(restrictions_.begin(), restrictions_.end()), filter(false) | ||
{ | ||
if (use_turn_restrictions) | ||
{ | ||
|
@@ -40,11 +40,28 @@ RestrictionParser::RestrictionParser(bool use_turn_restrictions_, | |
util::Log() << "Found no turn restriction tags"; | ||
} | ||
} | ||
|
||
filter.add(true, "restriction"); | ||
if (parse_conditionals) | ||
{ | ||
filter.add(true, "restriction:conditional"); | ||
for (const auto &namespaced : restrictions_) | ||
{ | ||
filter.add(true, "restriction:" + namespaced + ":conditional"); | ||
} | ||
} | ||
|
||
// Not only use restriction= but also e.g. restriction:motorcar= | ||
// Include restriction:{mode}:conditional if flagged | ||
for (const auto &namespaced : restrictions_) | ||
{ | ||
filter.add(true, "restriction:" + namespaced); | ||
} | ||
} | ||
|
||
/** | ||
* Tries to parse a relation as a turn restriction. This can fail for a number of | ||
* reasons. The return type is a boost::optional<T>. | ||
* reasons. The return type is a std::vector<T>. | ||
* | ||
* Some restrictions can also be ignored: See the ```get_restrictions``` function | ||
* in the corresponding profile. We use it for both namespacing restrictions, as in | ||
|
@@ -59,31 +76,13 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const | |
return {}; | ||
} | ||
|
||
osmium::tags::KeyFilter filter(false); | ||
filter.add(true, "restriction"); | ||
if (parse_conditionals) | ||
{ | ||
filter.add(true, "restriction:conditional"); | ||
for (const auto &namespaced : restrictions) | ||
{ | ||
filter.add(true, "restriction:" + namespaced + ":conditional"); | ||
} | ||
} | ||
|
||
// Not only use restriction= but also e.g. restriction:motorcar= | ||
// Include restriction:{mode}:conditional if flagged | ||
for (const auto &namespaced : restrictions) | ||
{ | ||
filter.add(true, "restriction:" + namespaced); | ||
} | ||
|
||
const osmium::TagList &tag_list = relation.tags(); | ||
|
||
osmium::tags::KeyFilter::iterator fi_begin(filter, tag_list.begin(), tag_list.end()); | ||
osmium::tags::KeyFilter::iterator fi_end(filter, tag_list.end(), tag_list.end()); | ||
|
||
// if it's not a restriction, continue; | ||
if (std::distance(fi_begin, fi_end) == 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is O(N) for this iterator if I understand it correctly. |
||
if (fi_begin == fi_end) | ||
{ | ||
return {}; | ||
} | ||
|
@@ -99,18 +98,20 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const | |
bool is_multi_from = false; | ||
bool is_multi_to = false; | ||
|
||
std::vector<util::OpeningHours> condition; | ||
|
||
for (; fi_begin != fi_end; ++fi_begin) | ||
{ | ||
const std::string key(fi_begin->key()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why didn’t we have warning about unused |
||
const std::string value(fi_begin->value()); | ||
auto value = fi_begin->value(); | ||
|
||
// documented OSM restriction tags start either with only_* or no_*; | ||
// check and return on these values, and ignore no_*_on_red or unrecognized values | ||
if (value.find("only_") == 0) | ||
if (boost::algorithm::starts_with(value, "only_")) | ||
{ | ||
is_only_restriction = true; | ||
} | ||
else if (value.find("no_") == 0 && !boost::algorithm::ends_with(value, "_on_red")) | ||
else if (boost::algorithm::starts_with(value, "no_") && | ||
!boost::algorithm::ends_with(value, "_on_red")) | ||
{ | ||
is_only_restriction = false; | ||
if (boost::algorithm::starts_with(value, "no_exit")) | ||
|
@@ -126,6 +127,25 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const | |
{ | ||
return {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noticed that here we do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In most cases you'll get at most one restriction returned per relation, it's only no entry/exits that could have multiple. I think the way to view it is, if the relation is invalid, how correct will the remaining parsed restrictions be? It's difficult to know, so it's cleaner to not to try and deal with them. We could add a log, but I would only make it a debug level. There could be a lot of invalid OSM relations. |
||
} | ||
|
||
if (parse_conditionals) | ||
{ | ||
// Parse condition and add independent value/condition pairs | ||
const auto &parsed = osrm::util::ParseConditionalRestrictions(value); | ||
|
||
if (parsed.empty()) | ||
continue; | ||
|
||
for (const auto &p : parsed) | ||
{ | ||
std::vector<util::OpeningHours> hours = util::ParseOpeningHours(p.condition); | ||
// found unrecognized condition, continue | ||
if (hours.empty()) | ||
return {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same, if we found something unexpected we just return, should it be just ignoring instead? |
||
|
||
condition = std::move(hours); | ||
} | ||
} | ||
} | ||
|
||
constexpr auto INVALID_OSM_ID = std::numeric_limits<std::uint64_t>::max(); | ||
|
@@ -138,7 +158,11 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const | |
for (const auto &member : relation.members()) | ||
{ | ||
const char *role = member.role(); | ||
if (strcmp("from", role) != 0 && strcmp("to", role) != 0 && strcmp("via", role) != 0) | ||
const bool is_from_role = strcmp("from", role) == 0; | ||
const bool is_to_role = strcmp("to", role) == 0; | ||
const bool is_via_role = strcmp("via", role) == 0; | ||
|
||
if (!is_from_role && !is_to_role && !is_via_role) | ||
{ | ||
continue; | ||
} | ||
|
@@ -149,28 +173,27 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const | |
{ | ||
|
||
// Make sure nodes appear only in the role if a via node | ||
if (0 == strcmp("from", role) || 0 == strcmp("to", role)) | ||
if (is_from_role || is_to_role) | ||
{ | ||
continue; | ||
} | ||
BOOST_ASSERT(0 == strcmp("via", role)); | ||
BOOST_ASSERT(is_via_role); | ||
via_node = static_cast<std::uint64_t>(member.ref()); | ||
is_node_restriction = true; | ||
// set via node id | ||
break; | ||
} | ||
case osmium::item_type::way: | ||
BOOST_ASSERT(0 == strcmp("from", role) || 0 == strcmp("to", role) || | ||
0 == strcmp("via", role)); | ||
if (0 == strcmp("from", role)) | ||
BOOST_ASSERT(is_from_role || is_to_role || is_via_role); | ||
if (is_from_role) | ||
{ | ||
from_ways.push_back({static_cast<std::uint64_t>(member.ref())}); | ||
} | ||
else if (0 == strcmp("to", role)) | ||
else if (is_to_role) | ||
{ | ||
to_ways.push_back({static_cast<std::uint64_t>(member.ref())}); | ||
} | ||
else if (0 == strcmp("via", role)) | ||
else if (is_via_role) | ||
{ | ||
via_ways.push_back({static_cast<std::uint64_t>(member.ref())}); | ||
is_node_restriction = false; | ||
|
@@ -185,35 +208,6 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const | |
} | ||
} | ||
|
||
std::vector<util::OpeningHours> condition; | ||
// parse conditional tags | ||
if (parse_conditionals) | ||
{ | ||
osmium::tags::KeyFilter::iterator fi_begin(filter, tag_list.begin(), tag_list.end()); | ||
osmium::tags::KeyFilter::iterator fi_end(filter, tag_list.end(), tag_list.end()); | ||
for (; fi_begin != fi_end; ++fi_begin) | ||
{ | ||
const std::string key(fi_begin->key()); | ||
const std::string value(fi_begin->value()); | ||
|
||
// Parse condition and add independent value/condition pairs | ||
const auto &parsed = osrm::util::ParseConditionalRestrictions(value); | ||
|
||
if (parsed.empty()) | ||
continue; | ||
|
||
for (const auto &p : parsed) | ||
{ | ||
std::vector<util::OpeningHours> hours = util::ParseOpeningHours(p.condition); | ||
// found unrecognized condition, continue | ||
if (hours.empty()) | ||
return {}; | ||
|
||
condition = std::move(hours); | ||
} | ||
} | ||
} | ||
|
||
std::vector<InputTurnRestriction> restriction_containers; | ||
if (!from_ways.empty() && (via_node != INVALID_OSM_ID || !via_ways.empty()) && !to_ways.empty()) | ||
{ | ||
|
@@ -270,17 +264,25 @@ bool RestrictionParser::ShouldIgnoreRestriction(const std::string &except_tag_st | |
return false; | ||
} | ||
|
||
// Be warned, this is quadratic work here, but we assume that | ||
// only a few exceptions are actually defined. | ||
const std::regex delimiter_re("[;][ ]*"); | ||
std::sregex_token_iterator except_tags_begin( | ||
except_tag_string.begin(), except_tag_string.end(), delimiter_re, -1); | ||
std::sregex_token_iterator except_tags_end; | ||
|
||
return std::any_of(except_tags_begin, except_tags_end, [&](const std::string ¤t_string) { | ||
return std::end(restrictions) != | ||
std::find(std::begin(restrictions), std::end(restrictions), current_string); | ||
}); | ||
// split `except_tag_string` by semicolon and check if any of items is in `restrictions` | ||
std::string current_string; | ||
for (auto index : util::irange<size_t>(0, except_tag_string.size())) | ||
{ | ||
const auto ch = except_tag_string[index]; | ||
if (ch != ';') | ||
{ | ||
current_string += ch; | ||
} | ||
else | ||
{ | ||
if (restrictions.find(current_string) != restrictions.end()) | ||
{ | ||
return true; | ||
} | ||
current_string.clear(); | ||
} | ||
} | ||
return restrictions.find(current_string) != restrictions.end(); | ||
} | ||
} // namespace extractor | ||
} // namespace osrm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Osmium docs say this is deprecated, and should be replaced with
osmium::TagsFilter
.