diff --git a/.gitignore b/.gitignore index 683c1eb..1ff48ae 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,8 @@ yarn-error.log .awcache +/osrm/pbf +/osrm/pbf/* + # Bundle *.js.map diff --git a/osrm/lib/access.lua b/osrm/lib/access.lua new file mode 100644 index 0000000..678bd5c --- /dev/null +++ b/osrm/lib/access.lua @@ -0,0 +1,15 @@ +local ipairs = ipairs + +local Access = {} + +function Access.find_access_tag(source,access_tags_hierarchy) + for i,v in ipairs(access_tags_hierarchy) do + local tag = source:get_value_by_key(v) + if tag then + return tag + end + end + return nil +end + +return Access diff --git a/osrm/lib/destination.lua b/osrm/lib/destination.lua new file mode 100644 index 0000000..a09330a --- /dev/null +++ b/osrm/lib/destination.lua @@ -0,0 +1,29 @@ +local Destination = {} + +function Destination.get_directional_tag(way, is_forward, tag) + local v + if is_forward then + v = way:get_value_by_key(tag .. ':forward') or way:get_value_by_key(tag) + else + v = way:get_value_by_key(tag .. ':backward') or way:get_value_by_key(tag) + end + if v then + return v.gsub(v, ';', ', ') + end +end + +-- Assemble destination as: "A59: Düsseldorf, Köln" +-- destination:ref ^ ^ destination + +function Destination.get_destination(way, is_forward) + ref = Destination.get_directional_tag(way, is_forward, 'destination:ref') + dest = Destination.get_directional_tag(way, is_forward, 'destination') + street = Destination.get_directional_tag(way, is_forward, 'destination:street') + if ref and dest then + return ref .. ': ' .. dest + else + return ref or dest or street or '' + end +end + +return Destination diff --git a/osrm/lib/guidance.lua b/osrm/lib/guidance.lua new file mode 100644 index 0000000..6c25ed6 --- /dev/null +++ b/osrm/lib/guidance.lua @@ -0,0 +1,146 @@ +local Tags = require('lib/tags') +local Set = require('lib/set') + +local Guidance = {} + +-- Guidance: Default Mapping from roads to types/priorities +highway_classes = { + motorway = road_priority_class.motorway, + motorway_link = road_priority_class.link_road, + trunk = road_priority_class.trunk, + trunk_link = road_priority_class.link_road, + primary = road_priority_class.primary, + primary_link = road_priority_class.link_road, + secondary = road_priority_class.secondary, + secondary_link = road_priority_class.link_road, + tertiary = road_priority_class.tertiary, + tertiary_link = road_priority_class.link_road, + unclassified = road_priority_class.side_residential, + residential = road_priority_class.side_residential, + service = road_priority_class.connectivity, + living_street = road_priority_class.main_residential, + track = road_priority_class.bike_path, + path = road_priority_class.bike_path, + footway = road_priority_class.foot_path, + pedestrian = road_priority_class.foot_path, + steps = road_priority_class.foot_path +} + +default_highway_class = road_priority_class.connectivity; + +motorway_types = Set { + 'motorway', + 'motorway_link', + 'trunk', + 'trunk_link' +} + +-- these road types are set with a car in mind. For bicycle/walk we probably need different ones +road_types = Set { + 'motorway', + 'motorway_link', + 'trunk', + 'trunk_link', + 'primary', + 'primary_link', + 'secondary', + 'secondary_link', + 'tertiary', + 'tertiary_link', + 'unclassified', + 'residential', + 'living_street' +} + +link_types = Set { + 'motorway_link', + 'trunk_link', + 'primary_link', + 'secondary_link', + 'tertiary_link' +} + +function Guidance.set_classification (highway, result, input_way) + if motorway_types[highway] then + result.road_classification.motorway_class = true; + end + if link_types[highway] then + result.road_classification.link_class = true; + end + if highway_classes[highway] ~= nil then + result.road_classification.road_priority_class = highway_classes[highway] + else + result.road_classification.road_priority_class = default_highway_class + end + if road_types[highway] then + result.road_classification.may_be_ignored = false; + else + result.road_classification.may_be_ignored = true; + end + + local lane_count = input_way:get_value_by_key("lanes") + if lane_count then + local lc = tonumber(lane_count) + if lc ~= nil then + result.road_classification.num_lanes = lc + end + else + local total_count = 0 + local forward_count = input_way:get_value_by_key("lanes:forward") + if forward_count then + local fc = tonumber(forward_count) + if fc ~= nil then + total_count = fc + end + end + local backward_count = input_way:get_value_by_key("lanes:backward") + if backward_count then + local bc = tonumber(backward_count) + if bc ~= nil then + total_count = total_count + bc + end + end + if total_count ~= 0 then + result.road_classification.num_lanes = total_count + end + end +end + +-- returns forward,backward psv lane count +local function get_psv_counts(way,data) + local psv_forward, psv_backward = Tags.get_forward_backward_by_key(way,data,'lanes:psv') + if psv_forward then + psv_forward = tonumber(psv_forward) + end + if psv_backward then + psv_backward = tonumber(psv_backward) + end + return psv_forward or 0, + psv_backward or 0 +end + +-- trims lane string with regard to supported lanes +local function process_lanes(turn_lanes,vehicle_lanes,first_count,second_count) + if turn_lanes then + if vehicle_lanes then + return applyAccessTokens(turn_lanes,vehicle_lanes) + elseif first_count ~= 0 or second_count ~= 0 then + return trimLaneString(turn_lanes, first_count, second_count) + else + return turn_lanes + end + end +end + +-- this is broken for left-sided driving. It needs to switch left and right in case of left-sided driving +function Guidance.get_turn_lanes(way,data) + local psv_fw, psv_bw = get_psv_counts(way,data) + local turn_lanes_fw, turn_lanes_bw = Tags.get_forward_backward_by_key(way,data,'turn:lanes') + local vehicle_lanes_fw, vehicle_lanes_bw = Tags.get_forward_backward_by_key(way,data,'vehicle:lanes') + + --note: backward lanes swap psv_bw and psv_fw + return process_lanes(turn_lanes_fw,vehicle_lanes_fw,psv_bw,psv_fw) or turn_lanes, + process_lanes(turn_lanes_bw,vehicle_lanes_bw,psv_fw,psv_bw) or turn_lanes +end + +return Guidance diff --git a/osrm/lib/handlers.lua b/osrm/lib/handlers.lua new file mode 100644 index 0000000..a455d33 --- /dev/null +++ b/osrm/lib/handlers.lua @@ -0,0 +1,521 @@ +-- Profile handlers dealing with various aspects of tag parsing +-- +-- You can run a selection you find useful in your profile, +-- or do you own processing if/when required. + + +local get_turn_lanes = require("lib/guidance").get_turn_lanes +local set_classification = require("lib/guidance").set_classification +local get_destination = require("lib/destination").get_destination +local Tags = require('lib/tags') + +Handlers = {} + +-- check that way has at least one tag that could imply routability- +-- we store the checked tags in data, to avoid fetching again later +function Handlers.handle_tag_prefetch(way,result,data,profile) + for key,v in pairs(profile.prefetch) do + data[key] = way:get_value_by_key( key ) + end + return next(data) ~= nil +end + +-- set default mode +function Handlers.handle_default_mode(way,result,data,profile) + result.forward_mode = profile.default_mode + result.backward_mode = profile.default_mode +end + +-- handles name, including ref and pronunciation +function Handlers.handle_names(way,result,data,profile) + -- parse the remaining tags + local name = way:get_value_by_key("name") + local pronunciation = way:get_value_by_key("name:pronunciation") + local ref = way:get_value_by_key("ref") + + -- Set the name that will be used for instructions + if name then + result.name = name + end + + if ref then + result.ref = canonicalizeStringList(ref, ";") + end + + if pronunciation then + result.pronunciation = pronunciation + end +end + +-- junctions +function Handlers.handle_roundabouts(way,result,data,profile) + local junction = way:get_value_by_key("junction"); + + if junction == "roundabout" then + result.roundabout = true + end + + -- See Issue 3361: roundabout-shaped not following roundabout rules. + -- This will get us "At Strausberger Platz do Maneuver X" instead of multiple quick turns. + -- In a new API version we can think of having a separate type passing it through to the user. + if junction == "circular" then + result.circular = true + end +end + +-- determine if this way can be used as a start/end point for routing +function Handlers.handle_startpoint(way,result,data,profile) + -- if profile specifies set of allowed start modes, then check for that + -- otherwise require default mode + if profile.allowed_start_modes then + result.is_startpoint = profile.allowed_start_modes[result.forward_mode] == true or + profile.allowed_start_modes[result.backward_mode] == true + else + result.is_startpoint = result.forward_mode == profile.default_mode or + result.backward_mode == profile.default_mode + end +end + +-- handle turn lanes +function Handlers.handle_turn_lanes(way,result,data,profile) + local forward, backward = get_turn_lanes(way,data) + + if forward then + result.turn_lanes_forward = forward + end + + if backward then + result.turn_lanes_backward = backward + end +end + +-- set the road classification based on guidance globals configuration +function Handlers.handle_classification(way,result,data,profile) + set_classification(data.highway,result,way) +end + +-- handle destination tags +function Handlers.handle_destinations(way,result,data,profile) + if data.is_forward_oneway or data.is_reverse_oneway then + local destination = get_destination(way, data.is_forward_oneway) + result.destinations = canonicalizeStringList(destination, ",") + end +end + +-- handling ferries and piers +function Handlers.handle_ferries(way,result,data,profile) + local route = data.route + if route then + local route_speed = profile.route_speeds[route] + if route_speed and route_speed > 0 then + local duration = way:get_value_by_key("duration") + if duration and durationIsValid(duration) then + result.duration = math.max( parseDuration(duration), 1 ) + end + result.forward_mode = mode.ferry + result.backward_mode = mode.ferry + result.forward_speed = route_speed + result.backward_speed = route_speed + end + end +end + +-- handling movable bridges +function Handlers.handle_movables(way,result,data,profile) + local bridge = data.bridge + if bridge then + local bridge_speed = profile.bridge_speeds[bridge] + if bridge_speed and bridge_speed > 0 then + local capacity_car = way:get_value_by_key("capacity:car") + if capacity_car ~= 0 then + result.forward_mode = profile.default_mode + result.backward_mode = profile.default_mode + local duration = way:get_value_by_key("duration") + if duration and durationIsValid(duration) then + result.duration = math.max( parseDuration(duration), 1 ) + else + result.forward_speed = bridge_speed + result.backward_speed = bridge_speed + end + end + end + end +end + +-- service roads +function Handlers.handle_service(way,result,data,profile) + local service = way:get_value_by_key("service") + if service then + -- Set don't allow access to certain service roads + if profile.service_tag_forbidden[service] then + result.forward_mode = mode.inaccessible + result.backward_mode = mode.inaccessible + return false + end + end +end + +-- all lanes restricted to hov vehicles? +function Handlers.has_all_designated_hov_lanes(lanes) + if not lanes then + return false + end + -- This gmatch call effectively splits the string on | chars. + -- we append an extra | to the end so that we can match the final part + for lane in (lanes .. '|'):gmatch("([^|]*)|") do + if lane and lane ~= "designated" then + return false + end + end + return true +end + +-- handle high occupancy vehicle tags +function Handlers.handle_hov(way,result,data,profile) + -- respect user-preference for HOV + if not profile.avoid.hov_lanes then + return + end + + local hov = way:get_value_by_key("hov") + if "designated" == hov then + result.forward_restricted = true + result.backward_restricted = true + end + + data.hov_lanes_forward, data.hov_lanes_backward = Tags.get_forward_backward_by_key(way,data,'hov:lanes') + local all_hov_forward = Handlers.has_all_designated_hov_lanes(data.hov_lanes_forward) + local all_hov_backward = Handlers.has_all_designated_hov_lanes(data.hov_lanes_backward) + + -- in this case we will use turn penalties instead of filtering out + if properties.weight_name == 'routability' then + if (all_hov_forward) then + result.forward_restricted = true + end + if (all_hov_backward) then + result.backward_restricted = true + end + return + end + + -- filter out ways where all lanes are hov only + if all_hov_forward then + result.forward_mode = mode.inaccessible + end + if all_hov_backward then + result.backward_mode = mode.inaccessible + end +end + +-- check accessibility by traversing our access tag hierarchy +function Handlers.handle_access(way,result,data,profile) + data.forward_access, data.backward_access = + Tags.get_forward_backward_by_set(way,data,profile.access_tags_hierarchy) + + if profile.access_tag_blacklist[data.forward_access] then + result.forward_mode = mode.inaccessible + end + + if profile.access_tag_blacklist[data.backward_access] then + result.backward_mode = mode.inaccessible + end + + if result.forward_mode == mode.inaccessible and result.backward_mode == mode.inaccessible then + return false + end + + if profile.restricted_access_tag_list[data.forward_access] then + result.forward_restricted = true + end + + if profile.restricted_access_tag_list[data.backward_access] then + result.backward_restricted = true + end +end + +-- handle speed (excluding maxspeed) +function Handlers.handle_speed(way,result,data,profile) + if result.forward_speed ~= -1 then + return -- abort if already set, eg. by a route + end + + local key,value,speed = Tags.get_constant_by_key_value(way,profile.speeds) + + if speed then + -- set speed by way type + result.forward_speed = speed + result.backward_speed = speed + else + -- Set the avg speed on ways that are marked accessible + if profile.access_tag_whitelist[data.forward_access] then + result.forward_speed = profile.default_speed + elseif data.forward_access and not profile.access_tag_blacklist[data.forward_access] then + result.forward_speed = profile.default_speed -- fallback to the avg speed if access tag is not blacklisted + elseif not data.forward_access and data.backward_access then + result.forward_mode = mode.inaccessible + end + + if profile.access_tag_whitelist[data.backward_access] then + result.backward_speed = profile.default_speed + elseif data.backward_access and not profile.access_tag_blacklist[data.backward_access] then + result.backward_speed = profile.default_speed -- fallback to the avg speed if access tag is not blacklisted + elseif not data.backward_access and data.forward_access then + result.backward_mode = mode.inaccessible + end + end + + if result.forward_speed == -1 and result.backward_speed == -1 and result.duration <= 0 then + return false + end +end + +-- reduce speed on bad surfaces +function Handlers.handle_surface(way,result,data,profile) + local surface = way:get_value_by_key("surface") + local tracktype = way:get_value_by_key("tracktype") + local smoothness = way:get_value_by_key("smoothness") + + if surface and profile.surface_speeds[surface] then + result.forward_speed = math.min(profile.surface_speeds[surface], result.forward_speed) + result.backward_speed = math.min(profile.surface_speeds[surface], result.backward_speed) + end + if tracktype and profile.tracktype_speeds[tracktype] then + result.forward_speed = math.min(profile.tracktype_speeds[tracktype], result.forward_speed) + result.backward_speed = math.min(profile.tracktype_speeds[tracktype], result.backward_speed) + end + if smoothness and profile.smoothness_speeds[smoothness] then + result.forward_speed = math.min(profile.smoothness_speeds[smoothness], result.forward_speed) + result.backward_speed = math.min(profile.smoothness_speeds[smoothness], result.backward_speed) + end +end + +-- scale speeds to get better average driving times +function Handlers.handle_penalties(way,result,data,profile) + -- heavily penalize a way tagged with all HOV lanes + -- in order to only route over them if there is no other option + local service_penalty = 1.0 + local service = way:get_value_by_key("service") + if service and profile.service_penalties[service] then + service_penalty = profile.service_penalties[service] + end + + local width_penalty = 1.0 + local width = math.huge + local lanes = math.huge + local width_string = way:get_value_by_key("width") + if width_string and tonumber(width_string:match("%d*")) then + width = tonumber(width_string:match("%d*")) + end + + local lanes_string = way:get_value_by_key("lanes") + if lanes_string and tonumber(lanes_string:match("%d*")) then + lanes = tonumber(lanes_string:match("%d*")) + end + + local is_bidirectional = result.forward_mode ~= mode.inaccessible and + result.backward_mode ~= mode.inaccessible + + if width <= 3 or (lanes <= 1 and is_bidirectional) then + width_penalty = 0.5 + end + + -- Handle high frequency reversible oneways (think traffic signal controlled, changing direction every 15 minutes). + -- Scaling speed to take average waiting time into account plus some more for start / stop. + local alternating_penalty = 1.0 + if data.oneway == "alternating" then + alternating_penalty = 0.4 + end + + local sideroad_penalty = 1.0 + data.sideroad = way:get_value_by_key("side_road") + if "yes" == data.sideroad or "rotary" == data.sideroad then + sideroad_penalty = profile.side_road_multiplier + end + + local forward_penalty = math.min(service_penalty, width_penalty, alternating_penalty, sideroad_penalty) + local backward_penalty = math.min(service_penalty, width_penalty, alternating_penalty, sideroad_penalty) + + if properties.weight_name == 'routability' then + if result.forward_speed > 0 then + result.forward_rate = (result.forward_speed * forward_penalty) / 3.6 + end + if result.backward_speed > 0 then + result.backward_rate = (result.backward_speed * backward_penalty) / 3.6 + end + if result.duration > 0 then + result.weight = result.duration / forward_penalty + end + end +end + +-- maxspeed and advisory maxspeed +function Handlers.handle_maxspeed(way,result,data,profile) + local keys = Sequence { 'maxspeed:advisory', 'maxspeed' } + local forward, backward = Tags.get_forward_backward_by_set(way,data,keys) + forward = Handlers.parse_maxspeed(forward,profile) + backward = Handlers.parse_maxspeed(backward,profile) + + if forward and forward > 0 then + result.forward_speed = forward * profile.speed_reduction + end + + if backward and backward > 0 then + result.backward_speed = backward * profile.speed_reduction + end +end + +function Handlers.parse_maxspeed(source,profile) + if not source then + return 0 + end + local n = tonumber(source:match("%d*")) + if n then + if string.match(source, "mph") or string.match(source, "mp/h") then + n = (n*1609)/1000 + end + else + -- parse maxspeed like FR:urban + source = string.lower(source) + n = profile.maxspeed_table[source] + if not n then + local highway_type = string.match(source, "%a%a:(%a+)") + n = profile.maxspeed_table_default[highway_type] + if not n then + n = 0 + end + end + end + return n +end + +-- handle oneways tags +function Handlers.handle_oneway(way,result,data,profile) + if not profile.oneway_handling then + return + end + + local oneway + if profile.oneway_handling == true then + oneway = Tags.get_value_by_prefixed_sequence(way,profile.restrictions,'oneway') or way:get_value_by_key("oneway") + elseif profile.oneway_handling == 'specific' then + oneway = Tags.get_value_by_prefixed_sequence(way,profile.restrictions,'oneway') + elseif profile.oneway_handling == 'conditional' then + -- Following code assumes that `oneway` and `oneway:conditional` tags have opposite values and takes weakest (always `no`). + -- So if we will have: + -- oneway=yes, oneway:conditional=no @ (condition1) + -- oneway=no, oneway:conditional=yes @ (condition2) + -- condition1 will be always true and condition2 will be always false. + if way:get_value_by_key("oneway:conditional") then + oneway = "no" + else + oneway = Tags.get_value_by_prefixed_sequence(way,profile.restrictions,'oneway') or way:get_value_by_key("oneway") + end + end + + data.oneway = oneway + + if oneway == "-1" then + data.is_reverse_oneway = true + result.forward_mode = mode.inaccessible + elseif oneway == "yes" or + oneway == "1" or + oneway == "true" then + data.is_forward_oneway = true + result.backward_mode = mode.inaccessible + elseif profile.oneway_handling == true then + local junction = way:get_value_by_key("junction") + if data.highway == "motorway" or + junction == "roundabout" or + junction == "circular" then + if oneway ~= "no" then + -- implied oneway + data.is_forward_oneway = true + result.backward_mode = mode.inaccessible + end + end + end +end + +function Handlers.handle_weights(way,result,data,profile) + if properties.weight_name == 'distance' then + result.weight = 0 + -- set weight rates to 1 for the distance weight, edge weights are distance / rate + if (result.forward_mode ~= mode.inaccessible and result.forward_speed > 0) then + result.forward_rate = 1 + end + if (result.backward_mode ~= mode.inaccessible and result.backward_speed > 0) then + result.backward_rate = 1 + end + end +end + +-- handle various that can block access +function Handlers.handle_blocked_ways(way,result,data,profile) + + -- areas + if profile.avoid.area and way:get_value_by_key("area") == "yes" then + return false + end + + -- toll roads + if profile.avoid.toll and way:get_value_by_key("toll") == "yes" then + return false + end + + -- don't route over steps + if profile.avoid.steps and data.highway == "steps" then + return false + end + + -- construction + -- TODO if highway is valid then we shouldn't check railway, and vica versa + if profile.avoid.construction and (data.highway == 'construction' or way:get_value_by_key('railway') == 'construction') then + return false + end + + -- Reversible oneways change direction with low frequency (think twice a day): + -- do not route over these at all at the moment because of time dependence. + -- Note: alternating (high frequency) oneways are handled below with penalty. + if profile.avoid.reversible and way:get_value_by_key("oneway") == "reversible" then + return false + end + + -- impassables + if profile.avoid.impassable then + if way:get_value_by_key("impassable") == "yes" then + return false + end + + if way:get_value_by_key("status") == "impassable" then + return false + end + end +end + +-- Call a sequence of handlers, aborting in case a handler returns false. Example: +-- +-- handlers = Sequence { +-- 'handle_tag_prefetch', +-- 'handle_default_mode', +-- 'handle_blocked_ways', +-- 'handle_access', +-- 'handle_speed', +-- 'handle_names' +-- } +-- +-- Handlers.run(handlers,way,result,data,profile) +-- +-- Each method in the list will be called on the Handlers object. +-- All handlers must accept the parameteres (way,result,data,profile) and return false +-- if the handler chain should be aborted. +-- To ensure the correct order of method calls, use a Sequence of handler names. + +function Handlers.run(handlers,way,result,data,profile) + for i,handler in ipairs(handlers) do + if Handlers[handler](way,result,data,profile) == false then + return false + end + end +end + +return Handlers diff --git a/osrm/lib/maxspeed.lua b/osrm/lib/maxspeed.lua new file mode 100644 index 0000000..0dd9b82 --- /dev/null +++ b/osrm/lib/maxspeed.lua @@ -0,0 +1,19 @@ +local math = math + +local MaxSpeed = {} + +function MaxSpeed.limit(way,max,maxf,maxb) + if maxf and maxf>0 then + way.forward_speed = math.min(way.forward_speed, maxf) + elseif max and max>0 then + way.forward_speed = math.min(way.forward_speed, max) + end + + if maxb and maxb>0 then + way.backward_speed = math.min(way.backward_speed, maxb) + elseif max and max>0 then + way.backward_speed = math.min(way.backward_speed, max) + end +end + +return MaxSpeed diff --git a/osrm/lib/pprint.lua b/osrm/lib/pprint.lua new file mode 100644 index 0000000..38e9db1 --- /dev/null +++ b/osrm/lib/pprint.lua @@ -0,0 +1,457 @@ +-- Easy way to print data structes +-- From https://github.com/jagt/pprint.lua, file is license as pubic domain + +local pprint = { VERSION = '0.1' } + +pprint.defaults = { + -- type display trigger, hide not useful datatypes by default + -- custom types are treated as table + show_nil = true, + show_boolean = true, + show_number = true, + show_string = true, + show_table = true, + show_function = false, + show_thread = false, + show_userdata = false, + -- additional display trigger + show_metatable = false, -- show metatable + show_all = false, -- override other show settings and show everything + use_tostring = false, -- use __tostring to print table if available + filter_function = nil, -- called like callback(value[,key, parent]), return truty value to hide + object_cache = 'local', -- cache blob and table to give it a id, 'local' cache per print, 'global' cache + -- per process, falsy value to disable (might cause infinite loop) + -- format settings + indent_size = 2, -- indent for each nested table level + level_width = 80, -- max width per indent level + wrap_string = true, -- wrap string when it's longer than level_width + wrap_array = false, -- wrap every array elements + sort_keys = true, -- sort table keys +} + +local TYPES = { + ['nil'] = 1, ['boolean'] = 2, ['number'] = 3, ['string'] = 4, + ['table'] = 5, ['function'] = 6, ['thread'] = 7, ['userdata'] = 8 +} + +-- seems this is the only way to escape these, as lua don't know how to map char '\a' to 'a' +local ESCAPE_MAP = { + ['\a'] = '\\a', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', + ['\t'] = '\\t', ['\v'] = '\\v', ['\\'] = '\\\\', +} + +-- generic utilities +local function escape(s) + s = s:gsub('([%c\\])', ESCAPE_MAP) + local dq = s:find('"') + local sq = s:find("'") + if dq and sq then + return s:gsub('"', '\\"'), '"' + elseif sq then + return s, '"' + else + return s, "'" + end +end + +local function is_plain_key(key) + return type(key) == 'string' and key:match('^[%a_][%a%d_]*$') +end + +local CACHE_TYPES = { + ['table'] = true, ['function'] = true, ['thread'] = true, ['userdata'] = true +} + +-- cache would be populated to be like: +-- { +-- function = { `fun1` = 1, _cnt = 1 }, -- object id +-- table = { `table1` = 1, `table2` = 2, _cnt = 2 }, +-- visited_tables = { `table1` = 7, `table2` = 8 }, -- visit count +-- } +-- use weakrefs to avoid accidentall adding refcount +local function cache_apperance(obj, cache, option) + if not cache.visited_tables then + cache.visited_tables = setmetatable({}, {__mode = 'k'}) + end + local t = type(obj) + + -- TODO can't test filter_function here as we don't have the ix and key, + -- might cause different results? + -- respect show_xxx and filter_function to be consistent with print results + if (not TYPES[t] and not option.show_table) + or (TYPES[t] and not option['show_'..t]) then + return + end + + if CACHE_TYPES[t] or TYPES[t] == nil then + if not cache[t] then + cache[t] = setmetatable({}, {__mode = 'k'}) + cache[t]._cnt = 0 + end + if not cache[t][obj] then + cache[t]._cnt = cache[t]._cnt + 1 + cache[t][obj] = cache[t]._cnt + end + end + if t == 'table' or TYPES[t] == nil then + if cache.visited_tables[obj] == false then + -- already printed, no need to mark this and its children anymore + return + elseif cache.visited_tables[obj] == nil then + cache.visited_tables[obj] = 1 + else + -- visited already, increment and continue + cache.visited_tables[obj] = cache.visited_tables[obj] + 1 + return + end + for k, v in pairs(obj) do + cache_apperance(k, cache, option) + cache_apperance(v, cache, option) + end + local mt = getmetatable(obj) + if mt and option.show_metatable then + cache_apperance(mt, cache, option) + end + end +end + +-- makes 'foo2' < 'foo100000'. string.sub makes substring anyway, no need to use index based method +local function str_natural_cmp(lhs, rhs) + while #lhs > 0 and #rhs > 0 do + local lmid, lend = lhs:find('%d+') + local rmid, rend = rhs:find('%d+') + if not (lmid and rmid) then return lhs < rhs end + + local lsub = lhs:sub(1, lmid-1) + local rsub = rhs:sub(1, rmid-1) + if lsub ~= rsub then + return lsub < rsub + end + + local lnum = tonumber(lhs:sub(lmid, lend)) + local rnum = tonumber(rhs:sub(rmid, rend)) + if lnum ~= rnum then + return lnum < rnum + end + + lhs = lhs:sub(lend+1) + rhs = rhs:sub(rend+1) + end + return lhs < rhs +end + +local function cmp(lhs, rhs) + local tleft = type(lhs) + local tright = type(rhs) + if tleft == 'number' and tright == 'number' then return lhs < rhs end + if tleft == 'string' and tright == 'string' then return str_natural_cmp(lhs, rhs) end + if tleft == tright then return str_natural_cmp(tostring(lhs), tostring(rhs)) end + + -- allow custom types + local oleft = TYPES[tleft] or 9 + local oright = TYPES[tright] or 9 + return oleft < oright +end + +-- setup option with default +local function make_option(option) + if option == nil then + option = {} + end + for k, v in pairs(pprint.defaults) do + if option[k] == nil then + option[k] = v + end + if option.show_all then + for t, _ in pairs(TYPES) do + option['show_'..t] = true + end + option.show_metatable = true + end + end + return option +end + +-- override defaults and take effects for all following calls +function pprint.setup(option) + pprint.defaults = make_option(option) +end + +-- format lua object into a string +function pprint.pformat(obj, option, printer) + option = make_option(option) + local buf = {} + local function default_printer(s) + table.insert(buf, s) + end + printer = printer or default_printer + + local cache + if option.object_cache == 'global' then + -- steal the cache into a local var so it's not visible from _G or anywhere + -- still can't avoid user explicitly referentce pprint._cache but it shouldn't happen anyway + cache = pprint._cache or {} + pprint._cache = nil + elseif option.object_cache == 'local' then + cache = {} + end + + local last = '' -- used for look back and remove trailing comma + local status = { + indent = '', -- current indent + len = 0, -- current line length + } + + local wrapped_printer = function(s) + printer(last) + last = s + end + + local function _indent(d) + status.indent = string.rep(' ', d + #(status.indent)) + end + + local function _n(d) + wrapped_printer('\n') + wrapped_printer(status.indent) + if d then + _indent(d) + end + status.len = 0 + return true -- used to close bracket correctly + end + + local function _p(s, nowrap) + status.len = status.len + #s + if not nowrap and status.len > option.level_width then + _n() + wrapped_printer(s) + status.len = #s + else + wrapped_printer(s) + end + end + + local formatter = {} + local function format(v) + local f = formatter[type(v)] + f = f or formatter.table -- allow patched type() + if option.filter_function and option.filter_function(v, nil, nil) then + return '' + else + return f(v) + end + end + + local function tostring_formatter(v) + return tostring(v) + end + + local function number_formatter(n) + return n == math.huge and '[[math.huge]]' or tostring(n) + end + + local function nop_formatter(v) + return '' + end + + local function make_fixed_formatter(t, has_cache) + if has_cache then + return function (v) + return string.format('[[%s %d]]', t, cache[t][v]) + end + else + return function (v) + return '[['..t..']]' + end + end + end + + local function string_formatter(s, force_long_quote) + local s, quote = escape(s) + local quote_len = force_long_quote and 4 or 2 + if quote_len + #s + status.len > option.level_width then + _n() + -- only wrap string when is longer than level_width + if option.wrap_string and #s + quote_len > option.level_width then + -- keep the quotes together + _p('[[') + while #s + status.len >= option.level_width do + local seg = option.level_width - status.len + _p(string.sub(s, 1, seg), true) + _n() + s = string.sub(s, seg+1) + end + _p(s) -- print the remaining parts + return ']]' + end + end + + return force_long_quote and '[['..s..']]' or quote..s..quote + end + + local function table_formatter(t) + if option.use_tostring then + local mt = getmetatable(t) + if mt and mt.__tostring then + return string_formatter(tostring(t), true) + end + end + + local print_header_ix = nil + local ttype = type(t) + if option.object_cache then + local cache_state = cache.visited_tables[t] + local tix = cache[ttype][t] + -- FIXME should really handle `cache_state == nil` + -- as user might add things through filter_function + if cache_state == false then + -- already printed, just print the the number + return string_formatter(string.format('%s %d', ttype, tix), true) + elseif cache_state > 1 then + -- appeared more than once, print table header with number + print_header_ix = tix + cache.visited_tables[t] = false + else + -- appeared exactly once, print like a normal table + end + end + + local tlen = #t + local wrapped = false + _p('{') + _indent(option.indent_size) + _p(string.rep(' ', option.indent_size - 1)) + if print_header_ix then + _p(string.format('--[[%s %d]] ', ttype, print_header_ix)) + end + for ix = 1,tlen do + local v = t[ix] + if formatter[type(v)] == nop_formatter or + (option.filter_function and option.filter_function(v, ix, t)) then + -- pass + else + if option.wrap_array then + wrapped = _n() + end + _p(format(v)..', ') + end + end + + -- hashmap part of the table, in contrast to array part + local function is_hash_key(k) + local numkey = tonumber(k) + if numkey ~= k or numkey > tlen then + return true + end + end + + local function print_kv(k, v, t) + -- can't use option.show_x as obj may contain custom type + if formatter[type(v)] == nop_formatter or + formatter[type(k)] == nop_formatter or + (option.filter_function and option.filter_function(v, k, t)) then + return + end + wrapped = _n() + if is_plain_key(k) then + _p(k, true) + else + _p('[') + -- [[]] type string in key is illegal, needs to add spaces inbetween + local k = format(k) + if string.match(k, '%[%[') then + _p(' '..k..' ', true) + else + _p(k, true) + end + _p(']') + end + _p(' = ', true) + _p(format(v), true) + _p(',', true) + end + + if option.sort_keys then + local keys = {} + for k, _ in pairs(t) do + if is_hash_key(k) then + table.insert(keys, k) + end + end + table.sort(keys, cmp) + for _, k in ipairs(keys) do + print_kv(k, t[k], t) + end + else + for k, v in pairs(t) do + if is_hash_key(k) then + print_kv(k, v, t) + end + end + end + + if option.show_metatable then + local mt = getmetatable(t) + if mt then + print_kv('__metatable', mt, t) + end + end + + _indent(-option.indent_size) + -- make { } into {} + last = string.gsub(last, '^ +$', '') + -- peek last to remove trailing comma + last = string.gsub(last, ',%s*$', ' ') + if wrapped then + _n() + end + _p('}') + + return '' + end + + -- set formatters + formatter['nil'] = option.show_nil and tostring_formatter or nop_formatter + formatter['boolean'] = option.show_boolean and tostring_formatter or nop_formatter + formatter['number'] = option.show_number and number_formatter or nop_formatter -- need to handle math.huge + formatter['function'] = option.show_function and make_fixed_formatter('function', option.object_cache) or nop_formatter + formatter['thread'] = option.show_thread and make_fixed_formatter('thread', option.object_cache) or nop_formatter + formatter['userdata'] = option.show_userdata and make_fixed_formatter('userdata', option.object_cache) or nop_formatter + formatter['string'] = option.show_string and string_formatter or nop_formatter + formatter['table'] = option.show_table and table_formatter or nop_formatter + + if option.object_cache then + -- needs to visit the table before start printing + cache_apperance(obj, cache, option) + end + + _p(format(obj)) + printer(last) -- close the buffered one + + -- put cache back if global + if option.object_cache == 'global' then + pprint._cache = cache + end + + return table.concat(buf) +end + +-- pprint all the arguments +function pprint.pprint( ... ) + local args = {...} + -- select will get an accurate count of array len, counting trailing nils + local len = select('#', ...) + for ix = 1,len do + pprint.pformat(args[ix], nil, io.write) + io.write('\n') + end +end + +setmetatable(pprint, { + __call = function (_, ...) + pprint.pprint(...) + end +}) + +return pprint diff --git a/osrm/lib/profile_debugger.lua b/osrm/lib/profile_debugger.lua new file mode 100644 index 0000000..1f5b150 --- /dev/null +++ b/osrm/lib/profile_debugger.lua @@ -0,0 +1,139 @@ +-- Enable calling our lua profile code directly from the lua command line, +-- which makes it easier to debug. +-- We simulate the normal C++ environment by defining the required globals and functions. + +-- See debug_example.lua for an example of how to require and use this file. + +-- for more convenient printing of tables +local pprint = require('lib/pprint') + + +-- globals that are normally set from C++ + +-- profiles code modifies this table +properties = {} + +-- should match values defined in include/extractor/guidance/road_classification.hpp +road_priority_class = { + motorway = 0, + trunk = 2, + primary = 4, + secondary = 6, + tertiary = 8, + main_residential = 10, + side_residential = 11, + link_road = 14, + bike_path = 16, + foot_path = 18, + connectivity = 31, +} + +-- should match values defined in include/extractor/travel_mode.hpp +mode = { + inaccessible = 0, + driving = 1, + cycling = 2, + walking = 3, + ferry = 4, + train = 5, + pushing_bike = 6, +} + +-- Mock C++ helper functions which are called from LUA. +-- TODO +-- Debugging LUA code that uses these will not work correctly +-- unless we reimplement the methods in LUA. + +function durationIsValid(str) + return true +end + +function parseDuration(str) + return 1 +end + +function canonicalizeStringList(str) + return str +end + + + +-- debug helper +local Debug = {} + +-- helpers for sorting associative array +function Debug.get_keys_sorted_by_value(tbl, sortFunction) + local keys = {} + for key in pairs(tbl) do + table.insert(keys, key) + end + + table.sort(keys, function(a, b) + return sortFunction(tbl[a], tbl[b]) + end) + + return keys +end + +-- helper for printing sorted array +function Debug.print_sorted(sorted,associative) + for _, key in ipairs(sorted) do + print(associative[key], key) + end +end + +function Debug.report_tag_fetches() + print("Tag fetches:") + sorted_counts = Debug.get_keys_sorted_by_value(Debug.tags.counts, function(a, b) return a > b end) + Debug.print_sorted(sorted_counts, Debug.tags.counts) + print(Debug.tags.total, 'total') +end + +function Debug.load_profile(profile) + require(profile) +end + +function Debug.reset_tag_fetch_counts() + Debug.tags = { + total = 0, + counts = {} + } +end + +function Debug.register_tag_fetch(k) + if Debug.tags.total then + Debug.tags.total = Debug.tags.total + 1 + else + Debug['tags']['total'] = 1 + end + + if Debug['tags']['counts'][k] then + Debug['tags']['counts'][k] = Debug['tags']['counts'][k] + 1 + else + Debug['tags']['counts'][k] = 1 + end + +end + +function Debug.way_function(way,result) + + -- setup result table + result.road_classification = {} + result.forward_speed = -1 + result.backward_speed = -1 + result.duration = 0 + + -- intercept tag function normally provided via C++ + function way:get_value_by_key(k) + Debug.register_tag_fetch(k) + return self[k] + end + + -- reset tag counts + Debug:reset_tag_fetch_counts() + + -- call the global method defined in the profile file + way_function(way,result) +end + +return Debug diff --git a/osrm/lib/sequence.lua b/osrm/lib/sequence.lua new file mode 100644 index 0000000..c1fba28 --- /dev/null +++ b/osrm/lib/sequence.lua @@ -0,0 +1,10 @@ +-- Sequence of items +-- Ordered, but have to loop through items to check for inclusion. +-- Currently the same as a table. + + +function Sequence(source) + return source +end + +return Sequence \ No newline at end of file diff --git a/osrm/lib/set.lua b/osrm/lib/set.lua new file mode 100644 index 0000000..bbd9719 --- /dev/null +++ b/osrm/lib/set.lua @@ -0,0 +1,23 @@ +-- Set of items +-- Fast check for inclusion, but unordered. +-- +-- Instead of having to do: +-- whitelist = { 'apple'=true, 'cherries'=true, 'melons'=true } +-- +-- you can do: +-- whitelist = Set { 'apple', 'cherries', 'melons' } +-- +-- and then use it as: +-- print( whitelist['cherries'] ) => true + +function Set(source) + set = {} + if source then + for i,v in ipairs(source) do + set[v] = true + end + end + return set +end + +return Set \ No newline at end of file diff --git a/osrm/lib/tags.lua b/osrm/lib/tags.lua new file mode 100644 index 0000000..a1a8f0a --- /dev/null +++ b/osrm/lib/tags.lua @@ -0,0 +1,124 @@ +-- Helpers for searching and parsing tags + +local Tags = {} + +-- return [forward,backward] values for a specific tag. +-- e.g. for maxspeed search forward: +-- maxspeed:forward +-- maxspeed +-- and backward: +-- maxspeed:backward +-- maxspeed + +function Tags.get_forward_backward_by_key(way,data,key) + local forward = way:get_value_by_key(key .. ':forward') + local backward = way:get_value_by_key(key .. ':backward') + + if forward and backward then + return forward, backward + end + + local common = way:get_value_by_key(key) + return forward or common, + backward or common +end + +-- return [forward,backward] values, searching a +-- prioritized sequence of tags +-- e.g. for the sequence [maxspeed,advisory] search forward: +-- maxspeed:forward +-- maxspeed +-- advisory:forward +-- advisory +-- and for backward: +-- maxspeed:backward +-- maxspeed +-- advisory:backward +-- advisory + +function Tags.get_forward_backward_by_set(way,data,keys) + local forward, backward + for i,key in ipairs(keys) do + if not forward then + forward = way:get_value_by_key(key .. ':forward') + end + if not backward then + backward = way:get_value_by_key(key .. ':backward') + end + if not forward or not backward then + local common = way:get_value_by_key(key) + forward = forward or common + backward = backward or common + end + if forward and backward then + break + end + end + + return forward, backward +end + +-- look through a sequence of keys combined with a prefix +-- e.g. for the sequence [motorcar,motor_vehicle,vehicle] and the prefix 'oneway' search for: +-- oneway:motorcar +-- oneway:motor_vehicle +-- oneway:vehicle + +function Tags.get_value_by_prefixed_sequence(way,seq,prefix) + local v + for i,key in ipairs(seq) do + v = way:get_value_by_key(prefix .. ':' .. key) + if v then + return v + end + end +end + +-- look through a sequence of keys combined with a postfix +-- e.g. for the sequence [motorcar,motor_vehicle,vehicle] and the postfix 'oneway' search for: +-- motorcar:oneway +-- motor_vehicle:oneway +-- vehicle:oneway + +function Tags.get_value_by_postfixed_sequence(way,seq,postfix) + local v + for i,key in ipairs(seq) do + v = way:get_value_by_key(key .. ':' .. postfix) + if v then + return v + end + end +end + +-- check if key-value pairs are set in a way and return a +-- corresponding constant if it is. e.g. for this input: +-- +-- local speeds = { +-- highway = { +-- residential = 20, +-- primary = 40 +-- }, +-- amenity = { +-- parking = 10 +-- } +-- } +-- +-- we would check whether the following key-value combinations +-- are set, and return the corresponding constant: +-- +-- highway = residential => 20 +-- highway = primary => 40 +-- amenity = parking => 10 + +function Tags.get_constant_by_key_value(way,lookup) + for key,set in pairs(lookup) do + local way_value = way:get_value_by_key(key) + for value,t in pairs(set) do + if way_value == value then + return key,value,t + end + end + end +end + +return Tags diff --git a/osrm/prepare_maps.sh b/osrm/prepare_maps.sh new file mode 100755 index 0000000..5709d89 --- /dev/null +++ b/osrm/prepare_maps.sh @@ -0,0 +1,9 @@ +#!/bin/bash +REGION="russia"; +DISTRICT="siberian-fed-district-latest"; + +mkdir pbf +wget "https://download.geofabrik.de/$REGION/$DISTRICT.osm.pbf" -O "pbf/$DISTRICT.osm.pbf" + +osrm-extract "./pbf/$DISTRICT.osm.pbf" -p ./rules.lua +osrm-contract "./pbf/$DISTRICT.osrm" diff --git a/osrm/rules.lua b/osrm/rules.lua new file mode 100644 index 0000000..04cbe8e --- /dev/null +++ b/osrm/rules.lua @@ -0,0 +1,422 @@ +-- Car profile + +api_version = 1 + +local find_access_tag = require("lib/access").find_access_tag +local Set = require('lib/set') +local Sequence = require('lib/sequence') +local Handlers = require("lib/handlers") +local next = next -- bind to local for speed + +penalty_table = { + ["service"] = 0.5, +} + +-- set profile properties +properties.max_speed_for_map_matching = 15 -- 180kmph -> m/s +properties.use_turn_restrictions = false +properties.continue_straight_at_waypoint = false +properties.left_hand_driving = false +-- For routing based on duration, but weighted for preferring certain roads +properties.weight_name = 'routability' +-- For shortest duration without penalties for accessibility +--properties.weight_name = 'duration' +-- For shortest distance without penalties for accessibility +--properties.weight_name = 'distance' + +local profile = { + default_mode = mode.driving, + default_speed = 10, + oneway_handling = true, + + side_road_multiplier = 0.8, + turn_penalty = 12, + speed_reduction = 0.8, + traffic_light_penalty = 2, + u_turn_penalty = 20, + restricted_penalty = 3000, + -- Note^: abstract value but in seconds correlates approximately to 50 min + -- meaning that a route through a local access way is > 50 min faster than around + + -- Note: this biases right-side driving. + -- Should be inverted for left-driving countries. + turn_bias = properties.left_hand_driving and 1/1.075 or 1.075, + + -- a list of suffixes to suppress in name change instructions + suffix_list = { + 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East' + }, + + barrier_whitelist = Set { + 'cattle_grid', + 'border_control', + 'checkpoint', + 'toll_booth', + 'sally_port', + 'gate', + 'lift_gate', + 'no', + 'entrance' + }, + + access_tag_whitelist = Set { + 'yes', + 'residential', + 'primary', + 'secondary', + 'motorcar', + 'motor_vehicle', + 'vehicle', + 'track', + 'permissive', + 'designated', + 'hov' + }, + + access_tag_blacklist = Set { + -- 'no', + -- 'agricultural', + -- 'forestry', + -- 'emergency', + -- 'psv' + }, + + restricted_access_tag_list = Set { + -- 'private', + -- 'delivery', + -- 'destination' + }, + + access_tags_hierarchy = Sequence { + 'motorcar', + 'motor_vehicle', + 'vehicle', + 'residential', + 'track', + 'foot', + 'bicycle', + 'access' + }, + + service_tag_forbidden = Set { + 'emergency_access' + }, + + restrictions = Sequence { + -- 'motorcar', + -- 'motor_vehicle', + -- 'vehicle' + }, + + avoid = Set { + -- 'area', + -- 'toll', -- uncomment this to avoid tolls + -- 'reversible', + 'impassable', + -- 'hov_lanes', + -- 'steps' + }, + + speeds = Sequence { + highway = { + motorway = 80, + cycleway = 110, + motorway_link = 80, + trunk = 80, + trunk_link = 100, + primary = 100, + primary_link = 100, + secondary = 90, + secondary_link = 90, + tertiary = 80, + tertiary_link = 80, + unclassified = 80, + residential = 80, + living_street = 75, + service = 75, + road = 90, + track = 95, + path = 70, + steps = 15, + pedestrian = 70, + footway = 80, +footpath = 100, +foot_path = 100, + pier = 95, + unpaved = 95 + } + }, + + service_penalties = { + -- alley = 0.5, + -- parking = 0.5, + -- parking_aisle = 0.5, + -- driveway = 0.5, + -- ["drive-through"] = 0.5, + -- ["drive-thru"] = 0.5 + }, + + route_speeds = { + ferry = 5, + shuttle_train = 10 + }, + + bridge_speeds = { + movable = nil + }, + + -- 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 = nil, + compacted = nil, + fine_gravel = nil, + + paving_stones = nil, + metal = nil, + bricks = nil, + + grass = 50, + wood = nil, + sett = nil, + grass_paver = 80, + gravel = 80, + unpaved = nil, + ground = 80, + dirt = 60, + pebblestone = nil, + tartan = 50, + + cobblestone = 60, + clay = 60, + + earth = 60, + stone = 60, + rocky = 50, + sand = 40, + + mud = 40 + }, + + -- 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 = { + ["ch:rural"] = 80, + ["ch:trunk"] = 100, + ["ch:motorway"] = 120, + ["de:living_street"] = 7, + ["ru:living_street"] = 20, + ["ru:urban"] = 60, + ["ua:urban"] = 60, + ["at:rural"] = 100, + ["de:rural"] = 100, + ["at:trunk"] = 100, + ["cz:trunk"] = 0, + ["ro:trunk"] = 100, + ["cz:motorway"] = 0, + ["de:motorway"] = 0, + ["ru:motorway"] = 110, + ["gb:nsl_single"] = (60*1609)/1000, + ["gb:nsl_dual"] = (70*1609)/1000, + ["gb:motorway"] = (70*1609)/1000, + ["uk:nsl_single"] = (60*1609)/1000, + ["uk:nsl_dual"] = (70*1609)/1000, + ["uk:motorway"] = (70*1609)/1000, + ["nl:rural"] = 80, + ["nl:trunk"] = 100, + ["none"] = 140 + } +} + +function get_name_suffix_list(vector) + for index,suffix in ipairs(profile.suffix_list) do + vector:Add(suffix) + end +end + +function get_restrictions(vector) + for i,v in ipairs(profile.restrictions) do + vector:Add(v) + end +end + +function node_function (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 not profile.barrier_whitelist[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 + result.traffic_lights = true + end +end + +function way_function(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') + } + + -- 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 + 'handle_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 + 'handle_blocked_ways', + + -- determine access status by checking our hierarchy of + -- access tags, e.g: motorcar, motor_vehicle, vehicle + 'handle_access', + + -- check whether forward/backward directions are routable + 'handle_oneway', + + -- check whether forward/backward directions are routable + 'handle_destinations', + + -- check whether we're using a special transport mode + 'handle_ferries', + 'handle_movables', + + -- handle service road restrictions + 'handle_service', + + -- handle hov + 'handle_hov', + + -- compute speed taking into account way type, maxspeed tags, etc. + 'handle_speed', + 'handle_surface', + 'handle_maxspeed', + 'handle_penalties', + + -- handle turn lanes and road classification, used for guidance + 'handle_turn_lanes', + 'handle_classification', + + -- handle various other flags + -- 'handle_roundabouts', + 'handle_startpoint', + + -- set name, ref and pronunciation + 'handle_names', + + -- set weight properties of the way + 'handle_weights' + } + + Handlers.run(handlers,way,result,data,profile) +end + +function turn_function (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 = profile.turn_bias + + if turn.turn_type ~= turn_type.no_turn then + if turn.angle >= 0 then + turn.duration = turn_penalty / (1 + math.exp( -((13 / turn_bias) * turn.angle/180 - 6.5*turn_bias))) + else + turn.duration = turn_penalty / (1 + math.exp( -((13 * turn_bias) * -turn.angle/180 - 6.5/turn_bias))) + end + + if turn.direction_modifier == direction_modifier.u_turn then + turn.duration = turn.duration + profile.u_turn_penalty + end + + if turn.has_traffic_light then + turn.duration = turn.duration + profile.traffic_light_penalty + end + + -- for distance based routing we don't want to have penalties based on turn angle + if properties.weight_name == 'distance' then + turn.weight = 0 + else + turn.weight = turn.duration + end + if 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 + profile.restricted_penalty + end + end + end +end diff --git a/osrm/start_server.sh b/osrm/start_server.sh new file mode 100644 index 0000000..fbcf1c1 --- /dev/null +++ b/osrm/start_server.sh @@ -0,0 +1,11 @@ +#!/bin/bash +IP="37.192.131.144" +PORT=5000 +PATH="./pbf/" +REGION="siberian-fed-district-latest" + +while : +do + osrm-routed -i $IP -p $PORT "$PATH/$REGION.osrm" + sleep 600 +done