api_version = 4 | |
Set = require('lib/set') | |
Sequence = require('lib/sequence') | |
Handlers = require("lib/way_handlers") | |
Relations = require("lib/relations") | |
find_access_tag = require("lib/access").find_access_tag | |
limit = require("lib/maxspeed").limit | |
Utils = require("lib/utils") | |
Measure = require("lib/measure") | |
function setup() | |
return { | |
properties = { | |
max_speed_for_map_matching = 180/3.6, -- 180kmph -> m/s | |
-- For routing based on duration, but weighted for preferring certain roads | |
weight_name = 'routability', | |
-- For shortest duration without penalties for accessibility | |
-- weight_name = 'duration', | |
-- For shortest distance without penalties for accessibility | |
-- weight_name = 'distance', | |
process_call_tagless_node = false, | |
u_turn_penalty = 20, | |
continue_straight_at_waypoint = true, | |
use_turn_restrictions = true, | |
left_hand_driving = false, | |
traffic_light_penalty = 2, | |
}, | |
default_mode = mode.driving, | |
default_speed = 10, | |
oneway_handling = true, | |
side_road_multiplier = 0.8, | |
turn_penalty = 7.5, | |
speed_reduction = 0.8, | |
turn_bias = 1.075, | |
cardinal_directions = false, | |
-- Size of the vehicle, to be limited by physical restriction of the way | |
vehicle_height = 2.0, -- in meters, 2.0m is the height slightly above biggest SUVs | |
vehicle_width = 1.9, -- in meters, ways with narrow tag are considered narrower than 2.2m | |
-- Size of the vehicle, to be limited mostly by legal restriction of the way | |
vehicle_length = 4.8, -- in meters, 4.8m is the length of large or family car | |
vehicle_weight = 2000, -- in kilograms | |
-- a list of suffixes to suppress in name change instructions. The suffixes also include common substrings of each other | |
suffix_list = { | |
'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East', 'Nor', 'Sou', 'We', 'Ea' | |
}, | |
barrier_whitelist = Set { | |
'cattle_grid', | |
'border_control', | |
'toll_booth', | |
'sally_port', | |
'gate', | |
'lift_gate', | |
'no', | |
'entrance', | |
'height_restrictor', | |
'arch' | |
}, | |
access_tag_whitelist = Set { | |
'yes', | |
'motorcar', | |
'motor_vehicle', | |
'vehicle', | |
'permissive', | |
'designated', | |
'hov' | |
}, | |
access_tag_blacklist = Set { | |
'no', | |
'agricultural', | |
'forestry', | |
'emergency', | |
'psv', | |
'customers', | |
'private', | |
'delivery', | |
'destination' | |
}, | |
-- tags disallow access to in combination with highway=service | |
service_access_tag_blacklist = Set { | |
'private' | |
}, | |
restricted_access_tag_list = Set { | |
'private', | |
'delivery', | |
'destination', | |
'customers', | |
}, | |
access_tags_hierarchy = Sequence { | |
'motorcar', | |
'motor_vehicle', | |
'vehicle', | |
'access' | |
}, | |
service_tag_forbidden = Set { | |
'emergency_access' | |
}, | |
restrictions = Sequence { | |
'motorcar', | |
'motor_vehicle', | |
'vehicle' | |
}, | |
classes = Sequence { | |
'toll', 'motorway', 'ferry', 'restricted', 'tunnel' | |
}, | |
-- classes to support for exclude flags | |
excludable = Sequence { | |
Set {'toll'}, | |
Set {'motorway'}, | |
Set {'ferry'} | |
}, | |
avoid = Set { | |
'area', | |
-- 'toll', -- uncomment this to avoid tolls | |
'reversible', | |
'impassable', | |
'hov_lanes', | |
'steps', | |
'construction', | |
'proposed' | |
}, | |
speeds = Sequence { | |
highway = { | |
motorway = 90, | |
motorway_link = 45, | |
trunk = 85, | |
trunk_link = 40, | |
primary = 65, | |
primary_link = 30, | |
secondary = 55, | |
secondary_link = 25, | |
tertiary = 40, | |
tertiary_link = 20, | |
unclassified = 25, | |
residential = 25, | |
living_street = 10, | |
service = 15 | |
} | |
}, | |
service_penalties = { | |
alley = 0.5, | |
parking = 0.5, | |
parking_aisle = 0.5, | |
driveway = 0.5, | |
["drive-through"] = 0.5, | |
["drive-thru"] = 0.5 | |
}, | |
restricted_highway_whitelist = Set { | |
'motorway', | |
'motorway_link', | |
'trunk', | |
'trunk_link', | |
'primary', | |
'primary_link', | |
'secondary', | |
'secondary_link', | |
'tertiary', | |
'tertiary_link', | |
'residential', | |
'living_street', | |
'unclassified', | |
'service' | |
}, | |
construction_whitelist = Set { | |
'no', | |
'widening', | |
'minor', | |
}, | |
route_speeds = { | |
ferry = 5, | |
shuttle_train = 10 | |
}, | |
bridge_speeds = { | |
movable = 5 | |
}, | |
-- surface/trackype/smoothness | |
-- values were estimated from looking at the photos at the relevant wiki pages | |
-- max speed for surfaces | |
surface_speeds = { | |
asphalt = nil, -- nil mean no limit. removing the line has the same effect | |
concrete = nil, | |
["concrete:plates"] = nil, | |
["concrete:lanes"] = nil, | |
paved = nil, | |
cement = 80, | |
compacted = 80, | |
fine_gravel = 80, | |
paving_stones = 60, | |
metal = 60, | |
bricks = 60, | |
grass = 40, | |
wood = 40, | |
sett = 40, | |
grass_paver = 40, | |
gravel = 40, | |
unpaved = 40, | |
ground = 40, | |
dirt = 40, | |
pebblestone = 40, | |
tartan = 40, | |
cobblestone = 30, | |
clay = 30, | |
earth = 20, | |
stone = 20, | |
rocky = 20, | |
sand = 20, | |
mud = 10 | |
}, | |
-- max speed for tracktypes | |
tracktype_speeds = { | |
grade1 = 60, | |
grade2 = 40, | |
grade3 = 30, | |
grade4 = 25, | |
grade5 = 20 | |
}, | |
-- max speed for smoothnesses | |
smoothness_speeds = { | |
intermediate = 80, | |
bad = 40, | |
very_bad = 20, | |
horrible = 10, | |
very_horrible = 5, | |
impassable = 0 | |
}, | |
-- http://wiki.openstreetmap.org/wiki/Speed_limits | |
maxspeed_table_default = { | |
urban = 50, | |
rural = 90, | |
trunk = 110, | |
motorway = 130 | |
}, | |
-- List only exceptions | |
maxspeed_table = { | |
["at:rural"] = 100, | |
["at:trunk"] = 100, | |
["be:motorway"] = 120, | |
["be-vlg:rural"] = 70, | |
["by:urban"] = 60, | |
["by:motorway"] = 110, | |
["ch:rural"] = 80, | |
["ch:trunk"] = 100, | |
["ch:motorway"] = 120, | |
["cz:trunk"] = 0, | |
["cz:motorway"] = 0, | |
["de:living_street"] = 7, | |
["de:rural"] = 100, | |
["de:motorway"] = 0, | |
["dk:rural"] = 80, | |
["fr:rural"] = 80, | |
["gb:nsl_single"] = (60*1609)/1000, | |
["gb:nsl_dual"] = (70*1609)/1000, | |
["gb:motorway"] = (70*1609)/1000, | |
["nl:rural"] = 80, | |
["nl:trunk"] = 100, | |
['no:rural'] = 80, | |
['no:motorway'] = 110, | |
['pl:rural'] = 100, | |
['pl:trunk'] = 120, | |
['pl:motorway'] = 140, | |
["ro:trunk"] = 100, | |
["ru:living_street"] = 20, | |
["ru:urban"] = 60, | |
["ru:motorway"] = 110, | |
["uk:nsl_single"] = (60*1609)/1000, | |
["uk:nsl_dual"] = (70*1609)/1000, | |
["uk:motorway"] = (70*1609)/1000, | |
['za:urban'] = 60, | |
['za:rural'] = 100, | |
["none"] = 140 | |
}, | |
relation_types = Sequence { | |
"route" | |
}, | |
-- classify highway tags when necessary for turn weights | |
highway_turn_classification = { | |
}, | |
-- classify access tags when necessary for turn weights | |
access_turn_classification = { | |
} | |
} | |
end | |
function process_node(profile, node, result, relations) | |
-- parse access and barrier tags | |
local access = find_access_tag(node, profile.access_tags_hierarchy) | |
if access then | |
if profile.access_tag_blacklist[access] and not profile.restricted_access_tag_list[access] then | |
result.barrier = true | |
end | |
else | |
local barrier = node:get_value_by_key("barrier") | |
if barrier then | |
-- check height restriction barriers | |
local restricted_by_height = false | |
if barrier == 'height_restrictor' then | |
local maxheight = Measure.get_max_height(node:get_value_by_key("maxheight"), node) | |
restricted_by_height = maxheight and maxheight < profile.vehicle_height | |
end | |
-- make an exception for rising bollard barriers | |
local bollard = node:get_value_by_key("bollard") | |
local rising_bollard = bollard and "rising" == bollard | |
if not profile.barrier_whitelist[barrier] and not rising_bollard or restricted_by_height 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 | |
result.traffic_lights = true | |
end | |
end | |
function process_way(profile, way, result, relations) | |
-- 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') | |
} | |
-- perform an quick initial check and abort if the way is | |
-- obviously not routable. | |
-- highway or route tags must be in data table, bridge is optional | |
if (not data.highway or data.highway == '') and | |
(not data.route or data.route == '') | |
then | |
return | |
end | |
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, | |
WayHandlers.avoid_ways, | |
WayHandlers.handle_height, | |
WayHandlers.handle_width, | |
WayHandlers.handle_length, | |
WayHandlers.handle_weight, | |
-- determine access status by checking our hierarchy of | |
-- access tags, e.g: motorcar, motor_vehicle, vehicle | |
WayHandlers.access, | |
-- check whether forward/backward directions are routable | |
WayHandlers.oneway, | |
-- check a road's destination | |
WayHandlers.destinations, | |
-- check whether we're using a special transport mode | |
WayHandlers.ferries, | |
WayHandlers.movables, | |
-- handle service road restrictions | |
WayHandlers.service, | |
-- handle hov | |
WayHandlers.hov, | |
-- compute speed taking into account way type, maxspeed tags, etc. | |
WayHandlers.speed, | |
WayHandlers.surface, | |
WayHandlers.maxspeed, | |
WayHandlers.penalties, | |
-- compute class labels | |
WayHandlers.classes, | |
-- handle turn lanes and road classification, used for guidance | |
WayHandlers.turn_lanes, | |
WayHandlers.classification, | |
-- handle various other flags | |
WayHandlers.roundabouts, | |
WayHandlers.startpoint, | |
WayHandlers.driving_side, | |
-- set name, ref and pronunciation | |
WayHandlers.names, | |
-- set weight properties of the way | |
WayHandlers.weights, | |
-- set classification of ways relevant for turns | |
WayHandlers.way_classification_for_turn | |
} | |
WayHandlers.run(profile, way, result, data, handlers, relations) | |
-- customized speed manipulation | |
forward_sp = result.forward_speed | |
backward_sp = result.backward_speed | |
factor = 1.0 | |
if way:get_location_tag('speed_factor') then | |
factor = way:get_location_tag('speed_factor') | |
end | |
if forward_sp and forward_sp > 0 then | |
result.forward_speed = forward_sp * factor | |
end | |
if backward_sp and backward_sp > 0 then | |
result.backward_speed = backward_sp * factor | |
end | |
-- end customized speed manipulation | |
if profile.cardinal_directions then | |
Relations.process_way_refs(way, relations, result) | |
end | |
end | |
function process_turn(profile, turn) | |
-- Use a sigmoid function to return a penalty that maxes out at turn_penalty | |
-- over the space of 0-180 degrees. Values here were chosen by fitting | |
-- the function to some turn penalty samples from real driving. | |
local turn_penalty = profile.turn_penalty | |
local turn_bias = turn.is_left_hand_driving and 1. / profile.turn_bias or profile.turn_bias | |
if turn.has_traffic_light then | |
turn.duration = profile.properties.traffic_light_penalty | |
end | |
if turn.number_of_roads > 2 or turn.source_mode ~= turn.target_mode or turn.is_u_turn then | |
if turn.angle >= 0 then | |
turn.duration = turn.duration + turn_penalty / (1 + math.exp( -((13 / turn_bias) * turn.angle/180 - 6.5*turn_bias))) | |
else | |
turn.duration = turn.duration + turn_penalty / (1 + math.exp( -((13 * turn_bias) * -turn.angle/180 - 6.5/turn_bias))) | |
end | |
if turn.is_u_turn then | |
turn.duration = turn.duration + profile.properties.u_turn_penalty | |
end | |
end | |
-- for distance based routing we don't want to have penalties based on turn angle | |
if profile.properties.weight_name == 'distance' then | |
turn.weight = 0 | |
else | |
turn.weight = turn.duration | |
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 = constants.max_turn_weight | |
end | |
end | |
end | |
return { | |
setup = setup, | |
process_way = process_way, | |
process_node = process_node, | |
process_turn = process_turn | |
} |
Hi Tim!
OSRM / Docker newby here.
I am trying to develop my own version of a car.lua in which I use the polygons representing flooded streets with two different depths, to alter the travel speed / close the street. I have been recommended to check this code, which looks super promising (context here). My goal is to create my own OSRM server with updated speed settings to then carry out some routing analyses in R using the OSRM package.
Before I dive into it, I have some questions that I would like to run through you. If this is not the right channel to discuss it, let me know and I will be happy to use a different one:
-
Can the location dependent polygon be composed of several geometries? My starting point on the polygons side is a shapefile with several "puddles" that I would then export as a geojson.
-
If the polygon covers a road segment only partially, does the new factor apply only to the portion of the road that overlaps with the polygon? or is it the entire road segment?
-
Would it be possible to define a new speed for the flooded roads, rather than a factor? for example, if I have 2 flood depth categories, I would like category 1 to set a maxspeed of 20 mph; for category 2 the road would be closed (maxspeed = 0). Looking into the code above it seems like it would be possible to replace the lines in which the factor is collected and re-defined by redefining the speed, but I am not familair with .lua at all, so I am not sure how to rewrite it. Without really knowing what I am doing, here is my stab at it (rewritten lines 448 - 464):
First, the geojson would have a field called "new_speed" indicating the new speed for any overlapping road, this field is then identified in the customized speed manipulation.
-- customized speed manipulation
forward_sp = result.forward_speed
backward_sp = result.backward_speed
if way:get_location_tag('new_speed') then
NEW_SPEED = way:get_location_tag('new_speed')
end
if forward_sp and forward_sp > 0 then
result.forward_speed = NEW_SPEED
end
if backward_sp and backward_sp > 0 then
result.backward_speed = NEW_SPEED
end
-- end customized speed manipulation
If the code above is right, there would be another check to add - in the cases in which the current speed is lower than 20 mph (e.g. local streets are 15), then the speed should NOT be rewritten.
Greetings,
let me try and explain.
- If you have multiple polygon geometries, I think the best is to chain them as arguments, i.e.
docker run -it -v $(pwd)/data:/data osrm/osrm-backend:v5.23.0 osrm-extract -p /data/osrm/uk/your_lua.lua --location-dependent-data=/data/osrm/uk/london.geojson --location-dependent-data=/data/osrm/uk/edinburgh.geojson --location-dependent-data=/data/osrm/uk/dublin.geojson --location-dependent-data=/data/osrm/uk/aberdeen.geojson /data/osrm/uk/britain-and-ireland-latest.osm.pbf
-
As soon as it intersects it will be considered in the logic, it won't split that road or anything afaik. You could check this easily though by feeding a small artificial OSM dataset to OSRM with very long ways and on polygon intersecting them.
-
Your logic looks sound, yes. Again, I would test with a small artificial dataset. You can print the results in the lua to command line, one helpful issue on this could be this Project-OSRM/osrm-backend#3718
Good luck! :)
Thank you for your response Tim!
regarding question 1 - just to clarify - could london.geojson hold more than one single polygon? e.g. n areas that flood and overlap with street segments within london.
For example, a .json file like the following below, with 2 geometries [areas flooded], would work? (also I foresee that I will have many geometries, in the 100s or 1000s)
{"type":"FeatureCollection","crs":{"type":"name","properties":{"name":"EPSG:26918"}},"features":[{"type":"Feature","id":0,"geometry":{"type":"Polygon","coordinates":[[[599687.0990039676,4516071.7771109855],[599686.89171178814,4516072.4379095687],[599687.42897854745,4516072.6064011483],[599683.72309983696,4516084.4230810879],[599672.78987609863,4516080.9943190096],[599676.47075100779,4516069.2573376466],[599676.82331754465,4516069.3678350858],[599677.05551337055,4516068.6273371056],[599687.0990039676,4516071.7771109855]]]},"properties":{"FID":0,"new_speed":20}},{"type":"Feature","id":1,"geometry":{"type":"Polygon","coordinates":[[[599772.63732254691,4516095.9699044283],[599773.51327440294,4516096.2475355314],[599768.83976576838,4516110.9957737578],[599761.59993338655,4516108.7015918251],[599760.43618100707,4516108.3327977397],[599765.08936211385,4516093.6487146541],[599766.15767968807,4516093.9872267181],[599766.61037690716,4516092.5585861932],[599768.45592401538,4516093.1434100932],[599768.79713207553,4516092.0666250121],[599773.24732469139,4516093.4768790873],[599773.41089934821,4516093.528733613],[599772.63732254691,4516095.9699044283]]]},"properties":{"FID":1,"new_speed":0}}]}
I don't think so, possibly a MultiPolygon will work but that then would use the same settings for all polygons within. Just give it a try, you will find eventually out :-)
Thanks! In that case it wouldnt be that bad. I could just have two sequential multipolygon jsons, one with each new speeds according to the two different depths im considering.
I cant wait to dive in! Thank you for your help. I am 0 familiar with lua so if there is an editor or viewer I should seek, any link to a set up tutorial / osm lua files handling tutorial would be amazing. Will report back if I have results.
Reg. the lua files, there are many flying around as gists and there is quite some information in the repo itself, you will have to browse around a bit. Printing within the lua file is usually my best friend. If Multipolygon does not work, you would also "script together" the CLI command if there are hundreds of location data arguments.
A location dependent polygon around London could look like:
And making use of everything together:
docker run -t -v "${PWD}:/data" osrm/osrm-backend:v5.23.0 osrm-extract -p /data/car-location-dependent-speeds.lua --location-dependent-data=/data/london.geojson /data/britain-and-ireland-latest.osm.pbf
... before contracting and starting the OSRM engine.