From 337ecefa455bd58385fa8a1d50a5cac043f490be Mon Sep 17 00:00:00 2001 From: Michael Krasnyk Date: Tue, 20 Dec 2016 11:16:05 +0100 Subject: [PATCH] Added osrm-extract-conditionals tool --- CHANGELOG.md | 2 + CMakeLists.txt | 8 + cmake/FindShapefile.cmake | 21 + include/util/opening_hours.hpp | 20 +- src/tools/extract-conditionals.cpp | 676 +++++++++++++++++++++++++++++ 5 files changed, 719 insertions(+), 8 deletions(-) create mode 100644 cmake/FindShapefile.cmake create mode 100644 src/tools/extract-conditionals.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index bcdd25204..6a3cb9348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ - Datafile versioning is now based on OSRM semver values, rather than source code checksums. Datafiles are compatible between patch levels, but incompatible between minor version or higher bumps. - libOSRM now creates an own watcher thread then used in shared memory mode to listen for data updates + - Tools: + - Added osrm-extract-conditionals tool for checking conditional values in OSM data # 5.5.1 - Changes from 5.5.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 50a2fb763..2c111c481 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -623,6 +623,14 @@ if(BUILD_TOOLS) target_link_libraries(osrm-io-benchmark ${BOOST_BASE_LIBRARIES}) install(TARGETS osrm-io-benchmark DESTINATION bin) + + find_package(Shapefile) # package libshp-dev + if(Shapefile_FOUND) + add_executable(osrm-extract-conditionals src/tools/extract-conditionals.cpp $) + target_include_directories(osrm-extract-conditionals PRIVATE ${LIBSHAPEFILE_INCLUDE_DIR}) + target_link_libraries(osrm-extract-conditionals ${OSMIUM_LIBRARIES} ${BOOST_BASE_LIBRARIES} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${LIBSHAPEFILE_LIBRARY}) + install(TARGETS osrm-extract-conditionals DESTINATION bin) + endif() endif() if (ENABLE_ASSERTIONS) diff --git a/cmake/FindShapefile.cmake b/cmake/FindShapefile.cmake new file mode 100644 index 000000000..5b90ec622 --- /dev/null +++ b/cmake/FindShapefile.cmake @@ -0,0 +1,21 @@ +# - Try to find Shapefile C Library +# http://shapelib.maptools.org/ +# +# Exports: +# Shapefile_FOUND +# LIBSHAPEFILE_INCLUDE_DIR +# LIBSHAPEFILE_LIBRARY +# Hints: +# LIBSHAPEFILE_LIBRARY_DIR + +find_path(LIBSHAPEFILE_INCLUDE_DIR + shapefil.h) + +find_library(LIBSHAPEFILE_LIBRARY + NAMES shp + HINTS "${LIBSHAPEFILE_LIBRARY_DIR}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Shapefile DEFAULT_MSG + LIBSHAPEFILE_LIBRARY LIBSHAPEFILE_INCLUDE_DIR) +mark_as_advanced(LIBSHAPEFILE_INCLUDE_DIR LIBSHAPEFILE_LIBRARY) diff --git a/include/util/opening_hours.hpp b/include/util/opening_hours.hpp index 957a27442..618dc7363 100644 --- a/include/util/opening_hours.hpp +++ b/include/util/opening_hours.hpp @@ -374,15 +374,16 @@ struct opening_hours_grammar : qi::grammar> small_range_selectors(_a))[_val = _a]; wide_range_selectors - = (monthday_selector(_r1) - || year_selector(_r1) - || week_selector(_r1) // TODO week_selector - ) >> -lit(':'); + = (-monthday_selector(_r1) + >> -year_selector(_r1) + >> -week_selector(_r1) // TODO week_selector + ) >> -lit(':') + ; - small_range_selectors = (weekday_selector(_r1) >> (&~lit(',') | eoi)) || time_selector(_r1); + small_range_selectors = -(weekday_selector(_r1) >> (&~lit(',') | eoi)) >> -time_selector(_r1); // Time selector time_selector = (timespan % ',')[ph::bind(&OpeningHours::times, _r1) = _1]; @@ -458,7 +459,7 @@ struct opening_hours_grammar : qi::grammar> wday) | day_offset; date_from - = ((-year[_a = _1] >> (month[_b = _1] || (daynum[_c = _1] >> (&~lit(':') | eoi)))) + = ((-year[_a = _1] >> ((month[_b = _1] >> -daynum[_c = _1]) | daynum[_c = _1])) | variable_date) [_val = ph::construct(_a, _b, _c)] ; @@ -504,7 +505,10 @@ struct opening_hours_grammar : qi::grammar> (&~lit(':') | eoi) + ; weeknum = uint2_p[_pass = bind([](unsigned x) { return 01 <= x && x <= 53; }, _1), _val = _1]; diff --git a/src/tools/extract-conditionals.cpp b/src/tools/extract-conditionals.cpp new file mode 100644 index 000000000..92fde535d --- /dev/null +++ b/src/tools/extract-conditionals.cpp @@ -0,0 +1,676 @@ +#include "util/conditional_restrictions.hpp" +#include "util/exception.hpp" +#include "util/log.hpp" +#include "util/opening_hours.hpp" +#include "util/version.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +//#include + +// Program arguments parsing functions +auto ParseGlobalArguments(int argc, char *argv[]) +{ + namespace po = boost::program_options; + + po::options_description global_options("Global options"); + global_options.add_options()("version,v", "Show version")("help,h", "Show this help message"); + + po::options_description commands("Commands"); + commands.add_options()("command", po::value())( + "subargs", po::value>()); + + po::positional_options_description pos; + pos.add("command", 1).add("subargs", -1); + + po::variables_map vm; + std::vector command_options; + try + { + po::options_description cmdline_options; + cmdline_options.add(global_options).add(commands); + + po::parsed_options parsed = po::command_line_parser(argc, argv) + .options(cmdline_options) + .positional(pos) + .allow_unregistered() + .run(); + + // split parsed options + auto command = std::find_if(parsed.options.begin(), parsed.options.end(), [](auto &o) { + return o.string_key == "command"; + }); + for (auto it = command; it != parsed.options.end(); ++it) + { + std::copy(it->original_tokens.begin(), + it->original_tokens.end(), + back_inserter(command_options)); + } + + parsed.options.erase(command, parsed.options.end()); + po::store(parsed, vm); + } + catch (const po::error &e) + { + throw osrm::util::exception(e.what()); + } + + if (vm.count("version")) + { + std::cout << OSRM_VERSION << std::endl; + std::exit(EXIT_SUCCESS); + } + + if (vm.count("help") || command_options.empty()) + { + const auto *executable = argv[0]; + std::cout << boost::filesystem::path(executable).filename().string() + << " [] []\n\n" + "Supported commands are:\n\n" + " cond-dump Save conditional restrictions in CSV format\n" + " cond-check Check conditional restrictions and save in CSV format\n" + << "\n" + << global_options; + std::exit(EXIT_SUCCESS); + } + + return command_options; +} + +void ParseRestrictionsDumpArguments(const char *executable, + const std::vector &arguments, + std::string &osm_filename, + std::string &csv_filename) +{ + namespace po = boost::program_options; + + po::options_description options("cond-dump"); + options.add_options()("help,h", "Show this help message")( + "input,i", + po::value(&osm_filename), + "OSM input file in .osm, .osm.bz2 or .osm.pbf format")( + "output,o", + po::value(&csv_filename), + "Output conditional restrictions file in CSV format"); + + po::positional_options_description pos; + pos.add("input", 1).add("output", 1); + + po::variables_map vm; + po::store(po::command_line_parser(arguments).options(options).positional(pos).run(), vm); + + if (vm.count("help")) + { + std::cout << boost::filesystem::path(executable).filename().string() + << " cond-dump []\n\n" + << options; + std::exit(EXIT_SUCCESS); + } + po::notify(vm); +} + +void ParseRestrictionsCheckArguments(const char *executable, + const std::vector &arguments, + std::string &input_filename, + std::string &output_filename, + std::string &tz_filename, + std::time_t &utc_time, + std::int64_t &restriction_value) +{ + namespace po = boost::program_options; + + po::options_description options("cond-dump"); + options.add_options()("help,h", "Show this help message")( + "input,i", + po::value(&input_filename), + "Input conditional restrictions file in CSV format")( + "output,o", + po::value(&output_filename), + "Output conditional restrictions file in CSV format")( + "tz-shapes,s", + po::value(&tz_filename)->default_value(tz_filename), + "Timezones shape file without extension")( + "utc-time,t", + po::value(&utc_time)->default_value(utc_time), + "UTC time-stamp [default=current UTC time]")( + "value,v", + po::value(&restriction_value)->default_value(restriction_value), + "Restriction value for active time conditional restrictions"); + + po::positional_options_description pos; + pos.add("input", 1).add("output", 1); + + po::variables_map vm; + po::store(po::command_line_parser(arguments).options(options).positional(pos).run(), vm); + + if (vm.count("help")) + { + std::cout << boost::filesystem::path(executable).filename().string() + << " cond-check []\n\n" + << options; + std::exit(EXIT_SUCCESS); + } + 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; +}; + +BOOST_FUSION_ADAPT_ADT(LocatedConditionalRestriction, + (obj.restriction.from, obj.restriction.from = val) // + (obj.restriction.via, obj.restriction.via = val) // + (obj.restriction.to, obj.restriction.to = val) // + (obj.restriction.tag, obj.restriction.tag = val) // + (obj.restriction.value, obj.restriction.value = val) // + (obj.restriction.condition, obj.restriction.condition = val) // + (obj.location.lon(), obj.location.set_lon(val)) // + (obj.location.lat(), obj.location.set_lat(val))) + +// The first pass relations handler that collects conditional restrictions +class ConditionalRestrictionsCollector : public osmium::handler::Handler +{ + public: + ConditionalRestrictionsCollector(std::vector &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::max(); + osmium::tags::Filter tag_filter; + std::vector &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 &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(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({nodes.front().ref(), way.id(), nodes[1].ref()}); + + if (related_vias.find(nodes.back().ref()) != related_vias.end()) + via_adjacency.push_back({nodes.back().ref(), way.id(), nodes[nodes.size() - 2].ref()}); + } + + template void process_restrictions(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(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; + using adjacency_type = + std::tuple; + + const std::vector &restrictions; + std::unordered_set related_vias; + std::vector via_adjacency; + index_type location_storage; +}; + +int RestrictionsDumpCommand(const char *executable, const std::vector &arguments) +{ + std::string osm_filename, csv_filename; + ParseRestrictionsDumpArguments(executable, arguments, osm_filename, csv_filename); + + // Read OSM input file + const osmium::io::File input_file(osm_filename); + + // Read relations + std::vector conditional_restrictions_prior; + ConditionalRestrictionsCollector restrictions_collector(conditional_restrictions_prior); + osmium::io::Reader reader1( + input_file, osmium::io::read_meta::no, osmium::osm_entity_bits::relation); + osmium::apply(reader1, restrictions_collector); + reader1.close(); + + // Handle nodes and ways in relations + ConditionalRestrictionsHandler restrictions_handler(conditional_restrictions_prior); + osmium::io::Reader reader2(input_file, + osmium::io::read_meta::no, + osmium::osm_entity_bits::node | osmium::osm_entity_bits::way); + osmium::apply(reader2, restrictions_handler); + reader2.close(); + + // Prepare output stream + std::streambuf *buf = std::cout.rdbuf(); + std::ofstream of; + if (!csv_filename.empty()) + { + of.open(csv_filename, std::ios::binary); + buf = of.rdbuf(); + } + std::ostream stream(buf); + + // Process collected restrictions and print CSV output + restrictions_handler.process_restrictions([&stream](const osmium::Location &location, + osmium::object_id_type from, + osmium::object_id_type via, + osmium::object_id_type to, + const std::string &tag, + const std::string &value, + const std::string &condition) { + stream << from << "," << via << "," << to << "," << tag << "," << value << ",\"" + << condition << "\"" + << "," << std::setprecision(6) << location.lon() << "," << std::setprecision(6) + << location.lat() << "\n"; + }); + + 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>; +using polygon_t = boost::geometry::model::polygon; +using box_t = boost::geometry::model::box; +using rtree_t = + boost::geometry::index::rtree, boost::geometry::index::rstar<8>>; +using local_time_t = std::pair; + +// 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 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 polygons; + std::vector 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(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(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 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 &arguments) +{ + std::string input_filename, output_filename; + std::string tz_filename = "tz_world"; + std::time_t utc_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::int64_t restriction_value = 32000; + ParseRestrictionsCheckArguments(executable, + arguments, + input_filename, + output_filename, + tz_filename, + utc_time, + restriction_value); + + // Prepare input stream + std::streambuf *input_buffer = std::cin.rdbuf(); + std::ifstream input_file; + if (!input_filename.empty()) + { + input_file.open(input_filename, std::ios::binary); + input_buffer = input_file.rdbuf(); + } + std::istream input_stream(input_buffer); + input_stream.unsetf(std::ios::skipws); + + boost::spirit::istream_iterator sfirst(input_stream); + boost::spirit::istream_iterator slast; + + boost::spirit::line_pos_iterator first(sfirst); + boost::spirit::line_pos_iterator last(slast); + + // Parse CSV file + namespace qi = boost::spirit::qi; + + std::vector conditional_restrictions; + qi::rule csv_line = + qi::ulong_long >> ',' >> qi::ulong_long >> ',' >> qi::ulong_long >> ',' >> + qi::as_string[+(~qi::lit(','))] >> ',' >> qi::as_string[+(~qi::lit(','))] >> ',' >> '"' >> + qi::as_string[qi::no_skip[*(~qi::lit('"'))]] >> '"' >> ',' >> qi::double_ >> ',' >> + qi::double_; + const auto ok = qi::phrase_parse(first, last, *(csv_line), qi::space, conditional_restrictions); + + if (!ok || first != last) + { + osrm::util::Log(logERROR) << input_filename << ":" << first.position() << ": parsing error"; + return EXIT_FAILURE; + } + + // Load R-tree with local times + auto get_local_time = LoadLocalTimesRTree(tz_filename, utc_time); + + // Prepare output stream + std::streambuf *output_buffer = std::cout.rdbuf(); + std::ofstream output_file; + if (!output_filename.empty()) + { + output_file.open(output_filename, std::ios::binary); + output_buffer = output_file.rdbuf(); + } + std::ostream output_stream(output_buffer); + + // For each conditional restriction if condition is active than print a line + for (auto &value : conditional_restrictions) + { + const auto &location = value.location; + const auto &restriction = value.restriction; + + // Get local time of the restriction + const auto &local_time = get_local_time(point_t{location.lon(), location.lat()}); + + // TODO: check restriction type [:][:] + // http://wiki.openstreetmap.org/wiki/Conditional_restrictions#Tagging + + // TODO: parsing will fail for combined conditions, e.g. Sa-Su AND weight>7 + // http://wiki.openstreetmap.org/wiki/Conditional_restrictions#Combined_conditions:_AND + + const auto &opening_hours = osrm::util::ParseOpeningHours(restriction.condition); + + if (opening_hours.empty()) + { + osrm::util::Log(logWARNING) + << "Condition parsing failed for \"" << restriction.condition << "\" at the turn " + << restriction.from << " -> " << restriction.via << " -> " << restriction.to; + continue; + } + + if (osrm::util::CheckOpeningHours(opening_hours, local_time)) + { + output_stream << restriction.from << "," << restriction.via << "," << restriction.to + << "," << restriction_value << "\n"; + } + } + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) try +{ + osrm::util::LogPolicy::GetInstance().Unmute(); + + // Parse program argument + auto arguments = ParseGlobalArguments(argc, argv); + BOOST_ASSERT(!arguments.empty()); + + std::unordered_map &)> + commands = {{"cond-dump", &RestrictionsDumpCommand}, + {"cond-check", &RestrictionsCheckCommand}}; + + auto command = commands.find(arguments.front()); + if (command == commands.end()) + { + std::cerr << "Unknown command: " << arguments.front() << "\n\n" + << "Available commands:\n"; + for (auto &command : commands) + std::cerr << " " << command.first << "\n"; + return EXIT_FAILURE; + } + + arguments.erase(arguments.begin()); + return command->second(argv[0], arguments); +} +catch (const std::exception &e) +{ + osrm::util::Log(logERROR) << e.what(); + return EXIT_FAILURE; +}