Added location dependent data

This commit is contained in:
Michael Krasnyk 2017-08-15 13:57:44 +02:00
parent 9a482ff828
commit c9673741de
12 changed files with 285 additions and 15 deletions

View File

@ -51,6 +51,10 @@
- Fix a pre-processing bug where incorrect directions could be issued when two turns would have similar instructions and we tried to give them distinct values (https://github.com/Project-OSRM/osrm-backend/pull/4375) - Fix a pre-processing bug where incorrect directions could be issued when two turns would have similar instructions and we tried to give them distinct values (https://github.com/Project-OSRM/osrm-backend/pull/4375)
- The entry bearing for correct the cardinality of a direction value (https://github.com/Project-OSRM/osrm-backend/pull/4353 - The entry bearing for correct the cardinality of a direction value (https://github.com/Project-OSRM/osrm-backend/pull/4353
- Change timezones in West Africa to the WAT zone so they're recognized on the Windows platform - Change timezones in West Africa to the WAT zone so they're recognized on the Windows platform
- Profiles
- Added an optional argument `location_data` to `process_way`function that is a table containing OSM tags in a GeoJSON file specified by `--location-dependent-data` command line argument of `osrm-extract` (the option requires `osmium add-locations-to-ways` preparation step)
- `left_hand_driving` flag is no more global but a local Boolean flag `is_left_hand_driving` in `ExtractionWay` and `ExtractionTurn`
# 5.10.0 # 5.10.0
- Changes from 5.9: - Changes from 5.9:

View File

@ -61,8 +61,13 @@ class DB {
}); });
w.nodes.forEach((k) => { w.nodes.forEach((k) => {
way.ele('nd')
let nd = way.ele('nd')
.att('ref', k.id); .att('ref', k.id);
if (w.add_locations) {
nd.att('lon', k.lon);
nd.att('lat', k.lat);
}
}); });
for (var k in w.tags) { for (var k in w.tags) {
@ -120,13 +125,14 @@ class Node {
} }
class Way { class Way {
constructor (id, OSM_USER, OSM_TIMESTAMP, OSM_UID) { constructor (id, OSM_USER, OSM_TIMESTAMP, OSM_UID, add_locations) {
this.id = id; this.id = id;
this.OSM_USER = OSM_USER; this.OSM_USER = OSM_USER;
this.OSM_TIMESTAMP = OSM_TIMESTAMP; this.OSM_TIMESTAMP = OSM_TIMESTAMP;
this.OSM_UID = OSM_UID; this.OSM_UID = OSM_UID;
this.tags = {}; this.tags = {};
this.nodes = []; this.nodes = [];
this.add_locations = add_locations;
} }
addNode (node) { addNode (node) {

View File

@ -6,10 +6,6 @@ Feature: osrm-extract lua ways:get_nodes()
""" """
a b a b
""" """
And the ways
| nodes |
| ab |
And the data has been saved to disk
Scenario: osrm-extract - Passing base file Scenario: osrm-extract - Passing base file
Given the profile file Given the profile file
@ -27,7 +23,50 @@ Feature: osrm-extract lua ways:get_nodes()
functions.process_way = way_function functions.process_way = way_function
return functions return functions
""" """
And the ways
| nodes |
| ab |
And the data has been saved to disk
When I run "osrm-extract --profile {profile_file} {osm_file}" When I run "osrm-extract --profile {profile_file} {osm_file}"
Then it should exit successfully Then it should exit successfully
And stdout should contain "node id 1" And stdout should contain "node id 1"
And stdout should contain "node id 2" And stdout should contain "node id 2"
Scenario: osrm-extract location-dependent data without add-locations-to-ways preprocessing
Given the profile "testbot"
And the ways
| nodes |
| ab |
And the data has been saved to disk
When I try to run "osrm-extract --profile {profile_file} {osm_file} --location-dependent-data test/data/regions/null-island.geojson"
Then it should exit with an error
And stderr should contain "invalid location"
Scenario: osrm-extract location-dependent data
Given the profile file
"""
functions = require('testbot')
function way_function(profile, way, result, location_data)
assert(location_data)
for k, v in pairs(location_data) do print (k .. ' ' .. tostring(v)) end
result.forward_mode = mode.driving
result.forward_speed = 1
end
functions.process_way = way_function
return functions
"""
And the ways with locations
| nodes |
| ab |
And the data has been saved to disk
When I run "osrm-extract --profile {profile_file} {osm_file} --location-dependent-data test/data/regions/null-island.geojson"
Then it should exit successfully
And stdout should contain "answer 42"
And stdout should not contain "array"

View File

@ -129,13 +129,13 @@ module.exports = function () {
q.awaitAll(callback); q.awaitAll(callback);
}); });
this.Given(/^the ways$/, (table, callback) => { this.Given(/^the ways( with locations)?$/, (add_locations, table, callback) => {
if (this.osm_str) throw new Error('*** Map data already defined - did you pass an input file in this scenario?'); if (this.osm_str) throw new Error('*** Map data already defined - did you pass an input file in this scenario?');
let q = d3.queue(); let q = d3.queue();
let addWay = (row, cb) => { let addWay = (row, cb) => {
let way = new OSM.Way(this.makeOSMId(), this.OSM_USER, this.OSM_TIMESTAMP, this.OSM_UID); let way = new OSM.Way(this.makeOSMId(), this.OSM_USER, this.OSM_TIMESTAMP, this.OSM_UID, !!add_locations);
let nodes = row.nodes; let nodes = row.nodes;
if (this.nameWayHash.nodes) throw new Error(util.format('*** duplicate way %s', nodes)); if (this.nameWayHash.nodes) throw new Error(util.format('*** duplicate way %s', nodes));

View File

@ -79,6 +79,7 @@ struct ExtractorConfig final : storage::IOConfig
boost::filesystem::path input_path; boost::filesystem::path input_path;
boost::filesystem::path profile_path; boost::filesystem::path profile_path;
boost::filesystem::path location_dependent_data_path;
unsigned requested_num_threads; unsigned requested_num_threads;
unsigned small_component_size; unsigned small_component_size;

View File

@ -0,0 +1,44 @@
#ifndef OSRM_LOCATION_DEPENDENT_DATA_HPP
#define OSRM_LOCATION_DEPENDENT_DATA_HPP
#include <boost/filesystem/path.hpp>
#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <osmium/osm/way.hpp>
#include <sol2/sol.hpp>
#include <string>
#include <unordered_map>
namespace osrm
{
namespace extractor
{
struct LocationDependentData
{
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 polygon_position_t = std::size_t;
using rtree_t = boost::geometry::index::rtree<std::pair<box_t, polygon_position_t>,
boost::geometry::index::rstar<8>>;
using property_t = boost::variant<boost::blank, double, std::string, bool>;
using properties_t = std::unordered_map<std::string, property_t>;
LocationDependentData(const boost::filesystem::path &file_path);
sol::table operator()(sol::state &state, const osmium::Way &way) const;
private:
rtree_t rtree;
std::vector<std::pair<polygon_t, properties_t>> polygons;
};
}
}
#endif

View File

@ -2,6 +2,7 @@
#define SCRIPTING_ENVIRONMENT_LUA_HPP #define SCRIPTING_ENVIRONMENT_LUA_HPP
#include "extractor/extraction_relation.hpp" #include "extractor/extraction_relation.hpp"
#include "extractor/location_dependent_data.hpp"
#include "extractor/raster_source.hpp" #include "extractor/raster_source.hpp"
#include "extractor/scripting_environment.hpp" #include "extractor/scripting_environment.hpp"
@ -20,6 +21,11 @@ namespace extractor
struct LuaScriptingContext final struct LuaScriptingContext final
{ {
LuaScriptingContext(const LocationDependentData &location_dependent_data)
: location_dependent_data(location_dependent_data)
{
}
void ProcessNode(const osmium::Node &, void ProcessNode(const osmium::Node &,
ExtractionNode &result, ExtractionNode &result,
const ExtractionRelationContainer::RelationList &relations); const ExtractionRelationContainer::RelationList &relations);
@ -46,6 +52,7 @@ struct LuaScriptingContext final
int api_version; int api_version;
sol::table profile_table; sol::table profile_table;
const LocationDependentData &location_dependent_data;
}; };
/** /**
@ -61,7 +68,8 @@ class Sol2ScriptingEnvironment final : public ScriptingEnvironment
static const constexpr int SUPPORTED_MIN_API_VERSION = 0; static const constexpr int SUPPORTED_MIN_API_VERSION = 0;
static const constexpr int SUPPORTED_MAX_API_VERSION = 3; static const constexpr int SUPPORTED_MAX_API_VERSION = 3;
explicit Sol2ScriptingEnvironment(const std::string &file_name); explicit Sol2ScriptingEnvironment(const std::string &file_name,
const boost::filesystem::path &location_dependent_data_path);
~Sol2ScriptingEnvironment() override = default; ~Sol2ScriptingEnvironment() override = default;
const ProfileProperties &GetProfileProperties() override; const ProfileProperties &GetProfileProperties() override;
@ -93,6 +101,7 @@ class Sol2ScriptingEnvironment final : public ScriptingEnvironment
std::mutex init_mutex; std::mutex init_mutex;
std::string file_name; std::string file_name;
tbb::enumerable_thread_specific<std::unique_ptr<LuaScriptingContext>> script_contexts; tbb::enumerable_thread_specific<std::unique_ptr<LuaScriptingContext>> script_contexts;
const LocationDependentData location_dependent_data;
}; };
} }
} }

View File

@ -0,0 +1,147 @@
#include "extractor/location_dependent_data.hpp"
#include "util/exception.hpp"
#include "util/geojson_validation.hpp"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/istreamwrapper.h>
#include <boost/filesystem.hpp>
#include <string>
namespace osrm
{
namespace extractor
{
LocationDependentData::LocationDependentData(const boost::filesystem::path &file_path)
{
if (file_path.empty())
return;
if (!boost::filesystem::exists(file_path) || !boost::filesystem::is_regular_file(file_path))
{
throw osrm::util::exception(std::string("File with location-dependent data ") +
file_path.string() + " does not exists");
}
std::ifstream file(file_path.string());
if (!file.is_open())
throw osrm::util::exception("failed to open " + file_path.string());
rapidjson::IStreamWrapper isw(file);
rapidjson::Document geojson;
geojson.ParseStream(isw);
if (geojson.HasParseError())
{
throw osrm::util::exception(std::string("Failed to parse ") + file_path.string() + ":" +
std::to_string(geojson.GetErrorOffset()) + " error: " +
rapidjson::GetParseError_En(geojson.GetParseError()));
}
BOOST_ASSERT(geojson.HasMember("type"));
BOOST_ASSERT(geojson["type"].IsString());
BOOST_ASSERT(std::strcmp(geojson["type"].GetString(), "FeatureCollection") == 0);
BOOST_ASSERT(geojson.HasMember("features"));
BOOST_ASSERT(geojson["features"].IsArray());
const auto &features_array = geojson["features"].GetArray();
std::vector<rtree_t::value_type> bounding_boxes;
for (rapidjson::SizeType i = 0; i < features_array.Size(); i++)
{
util::validateFeature(features_array[i]);
const auto &feature = features_array[i].GetObject();
const auto &geometry = feature["geometry"].GetObject();
BOOST_ASSERT(geometry.HasMember("type"));
// Case-sensitive check of type https://tools.ietf.org/html/rfc7946#section-1.4
if (std::strcmp(geometry["type"].GetString(), "Polygon"))
{
util::Log(logDEBUG) << "Skipping non-polygon shape in geojson file";
continue;
}
// The first array of polygon coords is the exterior ring
// https://tools.ietf.org/html/rfc7946#section-3.1.6
polygon_t polygon;
const auto &coords_outer_array = geometry["coordinates"].GetArray()[0].GetArray();
for (rapidjson::SizeType i = 0; i < coords_outer_array.Size(); ++i)
{
util::validateCoordinate(coords_outer_array[i]);
const auto &coords = coords_outer_array[i].GetArray();
polygon.outer().emplace_back(coords[0].GetDouble(), coords[1].GetDouble());
}
bounding_boxes.emplace_back(boost::geometry::return_envelope<box_t>(polygon),
polygons.size());
// Collect feature properties and store in polygons vector
auto convert_value = [](const auto &property) -> property_t {
if (property.IsString())
return std::string(property.GetString());
if (property.IsNumber())
return property.GetDouble();
if (property.IsBool())
return property.GetBool();
return {};
};
properties_t properties;
for (const auto &property : feature["properties"].GetObject())
{
properties.insert({property.name.GetString(), convert_value(property.value)});
}
polygons.push_back(std::make_pair(polygon, properties));
}
// Create R-tree for bounding boxes of collected polygons
rtree = rtree_t(bounding_boxes);
util::Log() << "Parsed " << polygons.size() << " geojson polygons with location-dependent data";
}
namespace
{
struct table_setter : public boost::static_visitor<>
{
table_setter(sol::table &table, const std::string &key) : table(table), key(key) {}
template <typename T> void operator()(const T &value) const { table.set(key, value); }
void operator()(const boost::blank &) const { /* ignore */}
sol::table &table;
const std::string &key;
};
}
sol::table LocationDependentData::operator()(sol::state &state, const osmium::Way &way) const
{
if (rtree.empty())
return sol::make_object(state, sol::nil);
// HEURISTIC: use a single node (last) of the way to localize the way
// For more complicated scenarios a proper merging of multiple tags
// at one or many locations must be provided
const auto &nodes = way.nodes();
const auto &location = nodes.back().location();
const point_t point(location.lon(), location.lat());
auto table = sol::table(state, sol::create);
// Search the R-tree and collect a Lua table of tags that correspond to the location
for (auto it = rtree.qbegin(
boost::geometry::index::intersects(point) &&
boost::geometry::index::satisfies([this, &point](const rtree_t::value_type &v) {
return boost::geometry::within(point, polygons[v.second].first);
}));
it != rtree.qend();
++it)
{
for (const auto &pair : polygons[it->second].second)
{
boost::apply_visitor(table_setter(table, pair.first), pair.second);
}
}
return table;
}
}
}

View File

@ -80,8 +80,9 @@ template <class T> double lonToDouble(T const &object)
return static_cast<double>(util::toFloating(object.lon)); return static_cast<double>(util::toFloating(object.lon));
} }
Sol2ScriptingEnvironment::Sol2ScriptingEnvironment(const std::string &file_name) Sol2ScriptingEnvironment::Sol2ScriptingEnvironment(
: file_name(file_name) const std::string &file_name, const boost::filesystem::path &location_dependent_data_path)
: file_name(file_name), location_dependent_data(location_dependent_data_path)
{ {
util::Log() << "Using script " << file_name; util::Log() << "Using script " << file_name;
} }
@ -666,7 +667,7 @@ LuaScriptingContext &Sol2ScriptingEnvironment::GetSol2Context()
auto &ref = script_contexts.local(initialized); auto &ref = script_contexts.local(initialized);
if (!initialized) if (!initialized)
{ {
ref = std::make_unique<LuaScriptingContext>(); ref = std::make_unique<LuaScriptingContext>(location_dependent_data);
InitContext(*ref); InitContext(*ref);
} }
@ -983,7 +984,7 @@ void LuaScriptingContext::ProcessWay(const osmium::Way &way,
way_function(profile_table, way, result, relations); way_function(profile_table, way, result, relations);
break; break;
case 2: case 2:
way_function(profile_table, way, result); way_function(profile_table, way, result, location_dependent_data(state, way));
break; break;
case 1: case 1:
case 0: case 0:

View File

@ -10,7 +10,8 @@ namespace osrm
void extract(const extractor::ExtractorConfig &config) void extract(const extractor::ExtractorConfig &config)
{ {
extractor::Sol2ScriptingEnvironment scripting_environment(config.profile_path.string().c_str()); extractor::Sol2ScriptingEnvironment scripting_environment(config.profile_path.string(),
config.location_dependent_data_path);
extractor::Extractor(config).run(scripting_environment); extractor::Extractor(config).run(scripting_environment);
} }

View File

@ -62,7 +62,11 @@ return_code parseArguments(int argc,
->implicit_value(true) ->implicit_value(true)
->default_value(false), ->default_value(false),
"Save conditional restrictions found during extraction to disk for use " "Save conditional restrictions found during extraction to disk for use "
"during contraction"); "during contraction")("location-dependent-data",
boost::program_options::value<boost::filesystem::path>(
&extractor_config.location_dependent_data_path)
->composing(),
"GeoJSON files with location-dependent data");
bool dummy; bool dummy;
// hidden options, will be allowed on command line, but will not be // hidden options, will be allowed on command line, but will not be

View File

@ -0,0 +1,14 @@
{
"type": "FeatureCollection",
"features": [ {
"type": "Feature",
"properties": {
"driving_side": "left",
"answer": 42,
"boolean": true,
"object": { "hello": "world" },
"array": [4, 8, 15, 16, 23, 42]
},
"geometry": { "type": "Polygon", "coordinates": [ [ [0, 0], [0, 2], [2, 2], [2, 0], [0, 0] ] ] }
} ]
}