Conditional turn restriction support (#3841)

* optionally include condition and via node coords in InputRestrictionContainer

* only write conditionals to disk, custom serialization for restrictions

* conditional turn lookup, reuse timezone validation from
extract-conditionals

* adapt updater to use coordinates/osm ids, remove internal to external map

* add utc time now parameter to contraction

* only compile timezone code where libshp is found, adapt test running

* slight refactor, more tests

* catch invalid via nodes in restriction parsing, set default cucumber
origin to guinée

* add another run to test mld routed paths

* cosmetic review changes

* Simplify Timezoner for windows build

* Split declaration and parsing parts for opening hours

* adjust conditional tests to run without shapefiles

* always include parse conditionals option

* Adjust travis timeout

* Added dummy TZ shapefile with test timezone polygons

* [skip ci] update changelog
This commit is contained in:
Karen Shea
2017-05-11 12:13:52 +02:00
committed by GitHub
parent 12f47708cd
commit 799a677e7a
42 changed files with 2116 additions and 1310 deletions
+4 -485
View File
@@ -1,35 +1,13 @@
#include "util/conditional_restrictions.hpp"
#include "tools/extract-conditionals.hpp"
#include "util/exception.hpp"
#include "util/for_each_pair.hpp"
#include "util/log.hpp"
#include "util/opening_hours.hpp"
#include "util/timezones.hpp"
#include "util/version.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/fusion/include/adapt_adt.hpp>
#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/program_options.hpp>
#include <boost/scope_exit.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include <osmium/handler.hpp>
#include <osmium/index/map/sparse_mem_array.hpp>
#include <osmium/io/any_input.hpp>
#include <osmium/tags/regex_filter.hpp>
#include <osmium/visitor.hpp>
#include <shapefil.h>
#include <fstream>
#include <cstdlib>
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <chrono>
#include <cstdlib>
//#include <ctime>
// Program arguments parsing functions
@@ -181,23 +159,6 @@ void ParseCheckCommandArguments(const char *executable,
po::notify(vm);
}
// Data types and functions for conditional restriction
struct ConditionalRestriction
{
osmium::object_id_type from;
osmium::object_id_type via;
osmium::object_id_type to;
std::string tag;
std::string value;
std::string condition;
};
struct LocatedConditionalRestriction
{
osmium::Location location;
ConditionalRestriction restriction;
};
// clang-format off
BOOST_FUSION_ADAPT_ADT(LocatedConditionalRestriction,
(osmium::object_id_type, osmium::object_id_type, obj.restriction.from, obj.restriction.from = val)
@@ -210,189 +171,6 @@ BOOST_FUSION_ADAPT_ADT(LocatedConditionalRestriction,
(std::int32_t, std::int32_t, obj.location.lat(), obj.location.set_lat(val)))
// clang-format on
// The first pass relations handler that collects conditional restrictions
class ConditionalRestrictionsCollector : public osmium::handler::Handler
{
public:
ConditionalRestrictionsCollector(std::vector<ConditionalRestriction> &restrictions)
: restrictions(restrictions)
{
tag_filter.add(true, std::regex("^restriction.*:conditional$"));
}
void relation(const osmium::Relation &relation) const
{
// Check if relation contains any ":conditional" tag
const osmium::TagList &tags = relation.tags();
typename decltype(tag_filter)::iterator first(tag_filter, tags.begin(), tags.end());
typename decltype(tag_filter)::iterator last(tag_filter, tags.end(), tags.end());
if (first == last)
return;
// Get member references of from(way) -> via(node) -> to(way)
auto from = invalid_id, via = invalid_id, to = invalid_id;
for (const auto &member : relation.members())
{
if (member.ref() == 0)
continue;
if (member.type() == osmium::item_type::node && strcmp(member.role(), "via") == 0)
{
via = member.ref();
}
else if (member.type() == osmium::item_type::way && strcmp(member.role(), "from") == 0)
{
from = member.ref();
}
else if (member.type() == osmium::item_type::way && strcmp(member.role(), "to") == 0)
{
to = member.ref();
}
}
if (from == invalid_id || via == invalid_id || to == invalid_id)
return;
for (; first != last; ++first)
{
// Parse condition and add independent value/condition pairs
const auto &parsed = osrm::util::ParseConditionalRestrictions(first->value());
if (parsed.empty())
{
osrm::util::Log(logWARNING) << "Conditional restriction parsing failed for \""
<< first->value() << "\" at the turn " << from << " -> "
<< via << " -> " << to;
continue;
}
for (auto &restriction : parsed)
{
restrictions.push_back(
{from, via, to, first->key(), restriction.value, restriction.condition});
}
}
}
private:
const osmium::object_id_type invalid_id = std::numeric_limits<osmium::object_id_type>::max();
osmium::tags::Filter<std::regex> tag_filter;
std::vector<ConditionalRestriction> &restrictions;
};
// The second pass handler that collects related nodes and ways.
// process_restrictions method calls for every collected conditional restriction
// a callback function with the prototype:
// (location, from node, via node, to node, tag, value, condition)
// If the restriction value starts with "only_" the callback function will be called
// for every edge adjacent to `via` node but not containing `to` node.
class ConditionalRestrictionsHandler : public osmium::handler::Handler
{
public:
ConditionalRestrictionsHandler(const std::vector<ConditionalRestriction> &restrictions)
: restrictions(restrictions)
{
for (auto &restriction : restrictions)
related_vias.insert(restriction.via);
via_adjacency.reserve(restrictions.size() * 2);
}
void node(const osmium::Node &node)
{
const osmium::object_id_type id = node.id();
if (related_vias.find(id) != related_vias.end())
{
location_storage.set(static_cast<osmium::unsigned_object_id_type>(id), node.location());
}
}
void way(const osmium::Way &way)
{
const auto &nodes = way.nodes();
if (related_vias.find(nodes.front().ref()) != related_vias.end())
via_adjacency.push_back(std::make_tuple(nodes.front().ref(), way.id(), nodes[1].ref()));
if (related_vias.find(nodes.back().ref()) != related_vias.end())
via_adjacency.push_back(
std::make_tuple(nodes.back().ref(), way.id(), nodes[nodes.size() - 2].ref()));
}
template <typename Callback> void process(Callback callback)
{
location_storage.sort();
std::sort(via_adjacency.begin(), via_adjacency.end());
auto adjacent_nodes = [this](auto node) {
auto first = std::lower_bound(
via_adjacency.begin(), via_adjacency.end(), adjacency_type{node, 0, 0});
auto last =
std::upper_bound(first, via_adjacency.end(), adjacency_type{node + 1, 0, 0});
return std::make_pair(first, last);
};
auto find_node = [](auto nodes, auto way) {
return std::find_if(
nodes.first, nodes.second, [way](const auto &n) { return std::get<1>(n) == way; });
};
for (auto &restriction : restrictions)
{
auto nodes = adjacent_nodes(restriction.via);
auto from = find_node(nodes, restriction.from);
if (from == nodes.second)
continue;
const auto &location =
location_storage.get(static_cast<osmium::unsigned_object_id_type>(restriction.via));
if (boost::algorithm::starts_with(restriction.value, "only_"))
{
for (auto it = nodes.first; it != nodes.second; ++it)
{
if (std::get<1>(*it) == restriction.to)
continue;
callback(location,
std::get<2>(*from),
restriction.via,
std::get<2>(*it),
restriction.tag,
restriction.value,
restriction.condition);
}
}
else
{
auto to = find_node(nodes, restriction.to);
if (to != nodes.second)
{
callback(location,
std::get<2>(*from),
restriction.via,
std::get<2>(*to),
restriction.tag,
restriction.value,
restriction.condition);
}
}
}
}
private:
using index_type =
osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
using adjacency_type =
std::tuple<osmium::object_id_type, osmium::object_id_type, osmium::object_id_type>;
const std::vector<ConditionalRestriction> &restrictions;
std::unordered_set<osmium::object_id_type> related_vias;
std::vector<adjacency_type> via_adjacency;
index_type location_storage;
};
int RestrictionsDumpCommand(const char *executable, const std::vector<std::string> &arguments)
{
std::string osm_filename, csv_filename;
@@ -444,22 +222,6 @@ int RestrictionsDumpCommand(const char *executable, const std::vector<std::strin
return EXIT_SUCCESS;
}
// Data types and functions for conditional speed limits
struct ConditionalSpeedLimit
{
osmium::object_id_type from;
osmium::object_id_type to;
std::string tag;
int value;
std::string condition;
};
struct LocatedConditionalSpeedLimit
{
osmium::Location location;
ConditionalSpeedLimit speed_limit;
};
// clang-format off
BOOST_FUSION_ADAPT_ADT(LocatedConditionalSpeedLimit,
(osmium::object_id_type, osmium::object_id_type, obj.speed_limit.from, obj.speed_limit.from = val)
@@ -471,136 +233,6 @@ BOOST_FUSION_ADAPT_ADT(LocatedConditionalSpeedLimit,
(std::int32_t, std::int32_t, obj.location.lat(), obj.location.set_lat(val)))
// clang-format on
class ConditionalSpeedLimitsCollector : public osmium::handler::Handler
{
public:
ConditionalSpeedLimitsCollector()
{
tag_filter.add(true, std::regex("^maxspeed.*:conditional$"));
}
void node(const osmium::Node &node)
{
const osmium::object_id_type id = node.id();
if (related_nodes.find(id) != related_nodes.end())
{
location_storage.set(static_cast<osmium::unsigned_object_id_type>(id), node.location());
}
}
void way(const osmium::Way &way)
{
const osmium::TagList &tags = way.tags();
typename decltype(tag_filter)::iterator first(tag_filter, tags.begin(), tags.end());
typename decltype(tag_filter)::iterator last(tag_filter, tags.end(), tags.end());
if (first == last)
return;
for (; first != last; ++first)
{
// Parse condition and add independent value/condition pairs
const auto &parsed = osrm::util::ParseConditionalRestrictions(first->value());
if (parsed.empty())
{
osrm::util::Log(logWARNING) << "Conditional speed limit parsing failed for \""
<< first->value() << "\" on the way " << way.id();
continue;
}
// Collect node IDs for the second pass over nodes
std::transform(way.nodes().begin(),
way.nodes().end(),
std::inserter(related_nodes, related_nodes.end()),
[](const auto &node_ref) { return node_ref.ref(); });
// Collect speed limits
for (auto &speed_limit : parsed)
{
// Convert value to an integer
int speed_limit_value;
{
namespace qi = boost::spirit::qi;
std::string::const_iterator first(speed_limit.value.begin()),
last(speed_limit.value.end());
if (!qi::parse(first, last, qi::int_, speed_limit_value) || first != last)
{
osrm::util::Log(logWARNING)
<< "Conditional speed limit has non-integer value \""
<< speed_limit.value << "\" on the way " << way.id();
continue;
}
}
std::string key = first->key();
const bool is_forward = key.find(":forward:") != std::string::npos;
const bool is_backward = key.find(":backward:") != std::string::npos;
const bool is_direction_defined = is_forward || is_backward;
if (is_forward || !is_direction_defined)
{
osrm::util::for_each_pair(way.nodes().cbegin(),
way.nodes().cend(),
[&](const auto &from, const auto &to) {
speed_limits.push_back(
ConditionalSpeedLimit{from.ref(),
to.ref(),
key,
speed_limit_value,
speed_limit.condition});
});
}
if (is_backward || !is_direction_defined)
{
osrm::util::for_each_pair(way.nodes().cbegin(),
way.nodes().cend(),
[&](const auto &to, const auto &from) {
speed_limits.push_back(
ConditionalSpeedLimit{from.ref(),
to.ref(),
key,
speed_limit_value,
speed_limit.condition});
});
}
}
}
}
template <typename Callback> void process(Callback callback)
{
location_storage.sort();
for (auto &speed_limit : speed_limits)
{
const auto &location_from = location_storage.get(
static_cast<osmium::unsigned_object_id_type>(speed_limit.from));
const auto &location_to =
location_storage.get(static_cast<osmium::unsigned_object_id_type>(speed_limit.to));
osmium::Location location{(location_from.lon() + location_to.lon()) / 2,
(location_from.lat() + location_to.lat()) / 2};
callback(location,
speed_limit.from,
speed_limit.to,
speed_limit.tag,
speed_limit.value,
speed_limit.condition);
}
}
private:
using index_type =
osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
osmium::tags::Filter<std::regex> tag_filter;
std::unordered_set<osmium::object_id_type> related_nodes;
std::vector<ConditionalSpeedLimit> speed_limits;
index_type location_storage;
};
int SpeedLimitsDumpCommand(const char *executable, const std::vector<std::string> &arguments)
{
std::string osm_filename, csv_filename;
@@ -646,119 +278,6 @@ int SpeedLimitsDumpCommand(const char *executable, const std::vector<std::string
return EXIT_SUCCESS;
}
// Time zone shape polygons loaded in R-tree
// local_time_t is a pair of a time zone shape polygon and the corresponding local time
// rtree_t is a lookup R-tree that maps a geographic point to an index in a local_time_t vector
using point_t = boost::geometry::model::
point<double, 2, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>>;
using polygon_t = boost::geometry::model::polygon<point_t>;
using box_t = boost::geometry::model::box<point_t>;
using rtree_t =
boost::geometry::index::rtree<std::pair<box_t, size_t>, boost::geometry::index::rstar<8>>;
using local_time_t = std::pair<polygon_t, struct tm>;
// Function loads time zone shape polygons, computes a zone local time for utc_time,
// creates a lookup R-tree and returns a lambda function that maps a point
// to the corresponding local time
auto LoadLocalTimesRTree(const std::string &tz_shapes_filename, std::time_t utc_time)
{
// Load time zones shapes and collect local times of utc_time
auto shphandle = SHPOpen(tz_shapes_filename.c_str(), "rb");
auto dbfhandle = DBFOpen(tz_shapes_filename.c_str(), "rb");
BOOST_SCOPE_EXIT(&shphandle, &dbfhandle)
{
DBFClose(dbfhandle);
SHPClose(shphandle);
}
BOOST_SCOPE_EXIT_END
if (!shphandle || !dbfhandle)
{
throw osrm::util::exception("failed to open " + tz_shapes_filename + ".shp or " +
tz_shapes_filename + ".dbf file");
}
int num_entities, shape_type;
SHPGetInfo(shphandle, &num_entities, &shape_type, NULL, NULL);
if (num_entities != DBFGetRecordCount(dbfhandle))
{
throw osrm::util::exception("inconsistent " + tz_shapes_filename + ".shp and " +
tz_shapes_filename + ".dbf files");
}
const auto tzid = DBFGetFieldIndex(dbfhandle, "TZID");
if (tzid == -1)
{
throw osrm::util::exception("did not find field called 'TZID' in the " +
tz_shapes_filename + ".dbf file");
}
// Lambda function that returns local time in the tzname time zone
// Thread safety: MT-Unsafe const:env
std::unordered_map<std::string, struct tm> local_time_memo;
auto get_local_time_in_tz = [utc_time, &local_time_memo](const char *tzname) {
auto it = local_time_memo.find(tzname);
if (it == local_time_memo.end())
{
struct tm timeinfo;
setenv("TZ", tzname, 1);
tzset();
localtime_r(&utc_time, &timeinfo);
it = local_time_memo.insert({tzname, timeinfo}).first;
}
return it->second;
};
// Get all time zone shapes and save local times in a vector
std::vector<rtree_t::value_type> polygons;
std::vector<local_time_t> local_times;
for (int shape = 0; shape < num_entities; ++shape)
{
auto object = SHPReadObject(shphandle, shape);
BOOST_SCOPE_EXIT(&object) { SHPDestroyObject(object); }
BOOST_SCOPE_EXIT_END
if (object && object->nSHPType == SHPT_POLYGON)
{
// Find time zone polygon and place its bbox in into R-Tree
polygon_t polygon;
for (int vertex = 0; vertex < object->nVertices; ++vertex)
{
polygon.outer().emplace_back(object->padfX[vertex], object->padfY[vertex]);
}
polygons.emplace_back(boost::geometry::return_envelope<box_t>(polygon),
local_times.size());
// Get time zone name and emplace polygon and local time for the UTC input
const auto tzname = DBFReadStringAttribute(dbfhandle, shape, tzid);
local_times.emplace_back(local_time_t{polygon, get_local_time_in_tz(tzname)});
// std::cout << boost::geometry::dsv(boost::geometry::return_envelope<box_t>(polygon))
// << " " << tzname << " " << asctime(&local_times.back().second);
}
}
// Create R-tree for collected shape polygons
rtree_t rtree(polygons);
// Return a lambda function that maps the input point and UTC time to the local time
// binds rtree and local_times
return [rtree, local_times](const point_t &point) {
std::vector<rtree_t::value_type> result;
rtree.query(boost::geometry::index::intersects(point), std::back_inserter(result));
for (const auto v : result)
{
const auto index = v.second;
if (boost::geometry::within(point, local_times[index].first))
return local_times[index].second;
}
return tm{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
};
}
int RestrictionsCheckCommand(const char *executable, const std::vector<std::string> &arguments)
{
std::string input_filename, output_filename;