Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ continue_straight_at_waypoint | Boolean | Must the route continue straig
max_speed_for_map_matching | Float | Maximum vehicle speed to be assumed in matching (in m/s)
max_turn_weight | Float | Maximum turn penalty weight
force_split_edges | Boolean | True value forces a split of forward and backward edges of extracted ways and guarantees that `process_segment` will be called for all segments (default `false`)
max_collapse_distance | Float | Maximum distance in meters for collapsing turn instructions in guidance (default `30.0`)


The following additional global properties can be set in the hash you return in the `setup` function:
Expand Down
2 changes: 1 addition & 1 deletion include/engine/api/route_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ class RouteAPI : public BaseAPI

guidance::trimShortSegments(steps, leg_geometry);
leg.steps = guidance::handleRoundabouts(std::move(steps));
leg.steps = guidance::collapseTurnInstructions(std::move(leg.steps));
leg.steps = guidance::collapseTurnInstructions(BaseAPI::facade, std::move(leg.steps));
leg.steps = guidance::anticipateLaneChange(std::move(leg.steps));
leg.steps = guidance::buildIntersections(std::move(leg.steps));
leg.steps = guidance::suppressShortNameSegments(std::move(leg.steps));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,11 @@ class ContiguousInternalMemoryDataFacadeBase : public BaseDataFacade
return m_profile_properties->max_speed_for_map_matching;
}

double GetMaxCollapseDistance() const override final
{
return m_profile_properties->GetMaxCollapseDistance();
}

const char *GetWeightName() const override final { return m_profile_properties->weight_name; }

unsigned GetWeightPrecision() const override final
Expand Down
2 changes: 2 additions & 0 deletions include/engine/datafacade/datafacade_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ class BaseDataFacade

virtual double GetMapMatchingMaxSpeed() const = 0;

virtual double GetMaxCollapseDistance() const = 0;

virtual const char *GetWeightName() const = 0;

virtual unsigned GetWeightPrecision() const = 0;
Expand Down
7 changes: 6 additions & 1 deletion include/engine/guidance/collapse_turns.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
#include <type_traits>
#include <vector>

namespace osrm::engine::datafacade
{
class BaseDataFacade;
}

namespace osrm::engine::guidance
{

// Multiple possible reasons can result in unnecessary/confusing instructions
// Collapsing such turns into a single turn instruction, we give a clearer
// set of instructions that is not cluttered by unnecessary turns/name changes.
[[nodiscard]] std::vector<RouteStep> collapseTurnInstructions(std::vector<RouteStep> steps);
[[nodiscard]] std::vector<RouteStep> collapseTurnInstructions(const datafacade::BaseDataFacade &facade, std::vector<RouteStep> steps);

// Multiple possible reasons can result in unnecessary/confusing instructions
// A prime example would be a segregated intersection. Turning around at this
Expand Down
6 changes: 5 additions & 1 deletion include/engine/guidance/collapsing_utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ namespace osrm::engine::guidance
using RouteSteps = std::vector<RouteStep>;
using RouteStepIterator = typename RouteSteps::iterator;
const constexpr std::size_t MIN_END_OF_ROAD_INTERSECTIONS = std::size_t{2};
const constexpr double MAX_COLLAPSE_DISTANCE = 30.0;
// Default value for max collapse distance
const constexpr double DEFAULT_MAX_COLLAPSE_DISTANCE = 30.0;

// Thread-local storage for configurable max collapse distance
extern thread_local double current_max_collapse_distance;
// a bit larger than 100 to avoid oscillation in tests
const constexpr double NAME_SEGMENT_CUTOFF_LENGTH = 105.0;
const double constexpr STRAIGHT_ANGLE = 180.;
Expand Down
11 changes: 10 additions & 1 deletion include/extractor/profile_properties.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct ProfileProperties
max_speed_for_map_matching(DEFAULT_MAX_SPEED), continue_straight_at_waypoint(true),
use_turn_restrictions(false), left_hand_driving(false), fallback_to_duration(true),
weight_name{"duration"}, class_names{{}}, excludable_classes{{}},
call_tagless_node_function(true)
call_tagless_node_function(true), max_collapse_distance(30.0)
{
std::fill(excludable_classes.begin(), excludable_classes.end(), INAVLID_CLASS_DATA);
BOOST_ASSERT(weight_name[MAX_WEIGHT_NAME_LENGTH] == '\0');
Expand All @@ -55,6 +55,13 @@ struct ProfileProperties
max_speed_for_map_matching = max_speed_for_map_matching_;
}

double GetMaxCollapseDistance() const { return max_collapse_distance; }

void SetMaxCollapseDistance(const double max_collapse_distance_)
{
max_collapse_distance = max_collapse_distance_;
}

void SetWeightName(const std::string &name)
{
auto count = std::min<std::size_t>(name.length(), MAX_WEIGHT_NAME_LENGTH) + 1;
Expand Down Expand Up @@ -135,6 +142,8 @@ struct ProfileProperties
unsigned weight_precision = 1;
bool force_split_edges = false;
bool call_tagless_node_function = true;
//! maximum distance for collapsing turns in guidance (in meters)
double max_collapse_distance;
};
} // namespace osrm::extractor

Expand Down
272 changes: 272 additions & 0 deletions profiles/foot_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
-- Test Foot profile with configurable max_collapse_distance

api_version = 2

Set = require('lib/set')
Sequence = require('lib/sequence')
Handlers = require("lib/way_handlers")
find_access_tag = require("lib/access").find_access_tag

function setup()
local walking_speed = 5
return {
properties = {
weight_name = 'duration',
max_speed_for_map_matching = 40/3.6, -- kmph -> m/s
call_tagless_node_function = false,
traffic_signal_penalty = 2,
u_turn_penalty = 2,
continue_straight_at_waypoint = false,
use_turn_restrictions = false,
-- Test with a smaller max_collapse_distance for precise pedestrian routing
max_collapse_distance = 10.0, -- 10 meters instead of default 30
},

default_mode = mode.walking,
default_speed = walking_speed,
oneway_handling = 'specific', -- respect 'oneway:foot' but not 'oneway'

barrier_blacklist = Set {
'yes',
'wall',
'fence'
},

access_tag_whitelist = Set {
'yes',
'foot',
'permissive',
'designated'
},

access_tag_blacklist = Set {
'no',
'agricultural',
'forestry',
'private',
'delivery',
},

restricted_access_tag_list = Set { },

restricted_highway_whitelist = Set { },

construction_whitelist = Set {},

access_tags_hierarchy = Sequence {
'foot',
'access'
},

-- tags disallow access to in combination with highway=service
service_access_tag_blacklist = Set { },

restrictions = Sequence {
'foot'
},

-- list of suffixes to suppress in name change instructions
suffix_list = Set {
'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East'
},

avoid = Set {
'impassable',
'proposed'
},

speeds = Sequence {
highway = {
primary = walking_speed,
primary_link = walking_speed,
secondary = walking_speed,
secondary_link = walking_speed,
tertiary = walking_speed,
tertiary_link = walking_speed,
unclassified = walking_speed,
residential = walking_speed,
road = walking_speed,
living_street = walking_speed,
service = walking_speed,
track = walking_speed,
path = walking_speed,
steps = walking_speed,
pedestrian = walking_speed,
platform = walking_speed,
footway = walking_speed,
pier = walking_speed,
},

railway = {
platform = walking_speed
},

amenity = {
parking = walking_speed,
parking_entrance= walking_speed
},

man_made = {
pier = walking_speed
},

leisure = {
track = walking_speed
}
},

route_speeds = {
ferry = 5
},

bridge_speeds = {
},

surface_speeds = {
fine_gravel = walking_speed*0.75,
gravel = walking_speed*0.75,
pebblestone = walking_speed*0.75,
mud = walking_speed*0.5,
sand = walking_speed*0.5
},

tracktype_speeds = {
},

smoothness_speeds = {
}
}
end

function process_node(profile, node, result)
-- parse access and barrier tags
local access = find_access_tag(node, profile.access_tags_hierarchy)
if access then
if profile.access_tag_blacklist[access] then
result.barrier = true
end
else
local barrier = node:get_value_by_key("barrier")
if barrier then
-- make an exception for rising bollard barriers
local bollard = node:get_value_by_key("bollard")
local rising_bollard = bollard and "rising" == bollard

if profile.barrier_blacklist[barrier] and not rising_bollard then
result.barrier = true
end
end
end

-- check if node is a traffic light
local tag = node:get_value_by_key("highway")
if "traffic_signals" == tag then
-- Direction should only apply to vehicles
result.traffic_lights = true
end
end

-- main entry point for processsing a way
function process_way(profile, way, result)
-- the intial filtering of ways based on presence of tags
-- affects processing times significantly, because all ways
-- have to be checked.
-- to increase performance, prefetching and intial tag check
-- is done in directly instead of via a handler.

-- in general we should try to abort as soon as
-- possible if the way is not routable, to avoid doing
-- unnecessary work. this implies we should check things that
-- commonly forbids access early, and handle edge cases later.

-- data table for storing intermediate values during processing
local data = {
-- prefetch tags
highway = way:get_value_by_key('highway'),
bridge = way:get_value_by_key('bridge'),
route = way:get_value_by_key('route'),
leisure = way:get_value_by_key('leisure'),
man_made = way:get_value_by_key('man_made'),
railway = way:get_value_by_key('railway'),
platform = way:get_value_by_key('platform'),
amenity = way:get_value_by_key('amenity'),
public_transport = way:get_value_by_key('public_transport')
}

-- perform an quick initial check and abort if the way is
-- obviously not routable. here we require at least one
-- of the prefetched tags to be present, ie. the data table
-- cannot be empty
if next(data) == nil then -- is the data table empty?
return
end

local handlers = Sequence {
-- set the default mode for this profile. if can be changed later
-- in case it turns we're e.g. on a ferry
WayHandlers.default_mode,

-- check various tags that could indicate that the way is not
-- routable. this includes things like status=impassable,
-- toll=yes and oneway=reversible
WayHandlers.blocked_ways,

-- determine access status by checking our hierarchy of
-- access tags, e.g: motorcar, motor_vehicle, vehicle
WayHandlers.access,

-- check whether forward/backward directons are routable
WayHandlers.oneway,

-- check whether forward/backward directons are routable
WayHandlers.destinations,

-- check whether we're using a special transport mode
WayHandlers.ferries,
WayHandlers.movables,

-- compute speed taking into account way type, maxspeed tags, etc.
WayHandlers.speed,
WayHandlers.surface,

-- handle turn lanes and road classification, used for guidance
WayHandlers.classification,

-- handle various other flags
WayHandlers.roundabouts,
WayHandlers.startpoint,

-- set name, ref and pronunciation
WayHandlers.names,

-- set weight properties of the way
WayHandlers.weights
}

WayHandlers.run(profile, way, result, data, handlers)
end

function process_turn (profile, turn)
turn.duration = 0.

if turn.direction_modifier == direction_modifier.u_turn then
turn.duration = turn.duration + profile.properties.u_turn_penalty
end

if turn.has_traffic_light then
turn.duration = profile.properties.traffic_signal_penalty
end
if profile.properties.weight_name == 'routability' then
-- penalize turns from non-local access only segments onto local access only tags
if not turn.source_restricted and turn.target_restricted then
turn.weight = turn.weight + 3000
end
end
end

return {
setup = setup,
process_way = process_way,
process_node = process_node,
process_turn = process_turn
}
Loading