name = "bicycle.fastest" generationDate = "2021-01-27T15:51:22" description = "The fastest route to your destination (Profile for a normal bicycle)" --[[ Calculate the actual factor.forward and factor.backward for a segment with the given properties ]] function factor(tags, result) -- Cleanup the relation tags to make them usable with this profile tags = remove_relation_prefix(tags, "fastest") -- initialize the result table on the default values result.forward_speed = 0 result.backward_speed = 0 result.forward = 0 result.backward = 0 result.canstop = true result.attributes_to_keep = {} -- not actually used anymore, but the code generation still uses this local parameters = default_parameters() parameters.timeNeeded = 1 local oneway = bicycle_oneway(parameters, tags, result) tags.oneway = oneway -- An aspect describing oneway should give either 'both', 'against' or 'width' -- forward calculation. We set the meta tag '_direction' to 'width' to indicate that we are going forward. The other functions will pick this up tags["_direction"] = "with" local access_forward = bicycle_legal_access(parameters, tags, result) if(oneway == "against") then -- no 'oneway=both' or 'oneway=with', so we can only go back over this segment -- we overwrite the 'access_forward'-value with no; whatever it was... access_forward = "no" end if(access_forward ~= nil and access_forward ~= "no") then tags.access = access_forward -- might be relevant, e.g. for 'access=dismount' for bicycles result.forward_speed = min({ legal_maxspeed_be(parameters, tags, result), parameters["defaultSpeed"] }) tags.speed = result.forward_speed local priority = calculate_priority(parameters, tags, result, access_forward, oneway, result.forward_speed) if (priority <= 0) then result.forward_speed = 0 else result.forward = 1 / priority end end -- backward calculation tags["_direction"] = "against" -- indicate the backward direction to priority calculation local access_backward = bicycle_legal_access(parameters, tags, result) if(oneway == "with") then -- no 'oneway=both' or 'oneway=against', so we can only go forward over this segment -- we overwrite the 'access_forward'-value with no; whatever it was... access_backward = "no" end if(access_backward ~= nil and access_backward ~= "no") then tags.access = access_backward result.backward_speed = min({ legal_maxspeed_be(parameters, tags, result), parameters["defaultSpeed"] }) tags.speed = result.backward_speed local priority = calculate_priority(parameters, tags, result, access_backward, oneway, result.backward_speed) if (priority <= 0) then result.backward_speed = 0 else result.backward = 1 / priority end end end --[[ Generates the factor according to the priorities and the parameters for this behaviour Note: 'result' is not actually used ]] function calculate_priority(parameters, tags, result, access, oneway, speed) local distance = 1 local priority = 1 * speed return priority end function default_parameters() local parameters = {} parameters.defaultSpeed = 15 parameters.timeNeeded = 0 parameters.distance = 0 parameters.comfort = 0 return parameters end --[[ Gives, for each type of highway, whether or not a normal bicycle can enter legally. Note that legal access is a bit 'grey' in the case of roads marked private and permissive, in which case these values are returned Unit: 'designated': Access is allowed and even specifically for bicycles 'yes': bicycles are allowed here 'permissive': bicycles are allowed here, but this might be a private road or service where usage is allowed, but could be retracted one day by the owner 'dismount': cycling here is not allowed, but walking with the bicycle is 'destination': cycling is allowed here, but only if truly necessary to reach the destination 'private': this is a private road, only go here if the destination is here 'no': do not cycle here Created by Originally defined in bicycle.legal_access.json Uses tags: access, highway, service, bicycle, anyways:bicycle, anyways:access, anyways:construction Used parameters: Number of combintations: 54 Returns values: ]] function bicycle_legal_access(parameters, tags, result) local result if (tags["highway"] ~= nil) then local v v = tags["highway"] if (v == "cycleway") then result = "designated" elseif (v == "residential") then result = "yes" elseif (v == "living_street") then result = "yes" elseif (v == "service") then result = "yes" elseif (v == "services") then result = "yes" elseif (v == "track") then result = "yes" elseif (v == "crossing") then result = "dismount" elseif (v == "footway") then result = "dismount" elseif (v == "pedestrian") then result = "dismount" elseif (v == "corridor") then result = "dismount" elseif (v == "construction") then result = "dismount" elseif (v == "steps") then result = "dismount" elseif (v == "path") then result = "yes" elseif (v == "primary") then result = "yes" elseif (v == "primary_link") then result = "yes" elseif (v == "secondary") then result = "yes" elseif (v == "secondary_link") then result = "yes" elseif (v == "tertiary") then result = "yes" elseif (v == "tertiary_link") then result = "yes" elseif (v == "unclassified") then result = "yes" elseif (v == "road") then result = "yes" end end if (tags["service"] ~= nil) then local v0 v0 = tags["service"] if (v0 == "parking_aisle") then result = "permissive" elseif (v0 == "driveway") then result = "private" elseif (v0 == "alley") then result = "yes" elseif (v0 == "bus") then result = "no" end end if (tags["access"] ~= nil) then local v1 v1 = tags["access"] if (v1 == "no") then result = "no" elseif (v1 == "customers") then result = "private" elseif (v1 == "private") then result = "private" elseif (v1 == "permissive") then result = "permissive" elseif (v1 == "destination") then result = "destination" elseif (v1 == "delivery") then result = "destination" elseif (v1 == "service") then result = "destination" elseif (v1 == "permit") then result = "destination" end end if (tags["bicycle"] ~= nil) then local v2 v2 = tags["bicycle"] if (v2 == "yes") then result = "yes" elseif (v2 == "no") then result = "no" elseif (v2 == "use_sidepath") then result = "no" elseif (v2 == "designated") then result = "designated" elseif (v2 == "permissive") then result = "permissive" elseif (v2 == "private") then result = "private" elseif (v2 == "official") then result = "designated" elseif (v2 == "dismount") then result = "dismount" elseif (v2 == "permit") then result = "destination" end end if (tags["anyways:construction"] ~= nil) then local v3 v3 = tags["anyways:construction"] if (v3 == "yes") then result = "no" end end if (tags["anyways:access"] ~= nil) then local v4 v4 = tags["anyways:access"] if (v4 == "no") then result = "no" elseif (v4 == "destination") then result = "destination" elseif (v4 == "yes") then result = "yes" end end if (tags["anyways:bicycle"] ~= nil) then result = tags["anyways:bicycle"] end if (result == nil) then result = "no" end return result end --[[ Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction Unit: both: direction is allowed in both direction with: this is a oneway street with direction allowed with the grain of the way against: oneway street with direction against the way Created by Originally defined in bicycle.oneway.json Uses tags: oneway, oneway:bicycle, junction, cycleway, cycleway:left Used parameters: Number of combintations: 32 Returns values: ]] function bicycle_oneway(parameters, tags, result) local result if (tags["oneway"] ~= nil) then local v5 v5 = tags["oneway"] if (v5 == "yes") then result = "with" elseif (v5 == "no") then result = "both" elseif (v5 == "1") then result = "with" elseif (v5 == "-1") then result = "against" end end if (tags["cycleway:left"] ~= nil) then local v6 v6 = tags["cycleway:left"] if (v6 == "no") then result = "with" elseif (v6 == "none") then result = "with" elseif (v6 == "yes") then result = "both" elseif (v6 == "lane") then result = "both" elseif (v6 == "track") then result = "both" elseif (v6 == "shared_lane") then result = "both" elseif (v6 == "share_busway") then result = "both" elseif (v6 == "opposite_lane") then result = "both" elseif (v6 == "opposite_track") then result = "both" elseif (v6 == "opposite") then result = "both" end end if (tags["cycleway"] ~= nil) then local v7 v7 = tags["cycleway"] if (v7 == "right") then result = "against" elseif (v7 == "opposite_lane") then result = "both" elseif (v7 == "track") then result = "both" elseif (v7 == "lane") then result = "both" elseif (v7 == "opposite") then result = "both" elseif (v7 == "opposite_share_busway") then result = "both" elseif (v7 == "opposite_track") then result = "both" end end if (tags["junction"] ~= nil) then local v8 v8 = tags["junction"] if (v8 == "roundabout") then result = "with" end end if (tags["oneway:bicycle"] ~= nil) then local v9 v9 = tags["oneway:bicycle"] if (v9 == "yes") then result = "with" elseif (v9 == "no") then result = "both" elseif (v9 == "1") then result = "with" elseif (v9 == "-1") then result = "against" end end if (result == nil) then result = "both" end return result end --[[ Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h) Unit: km/h Created by Originally defined in legal_maxspeed_be.json Uses tags: maxspeed, highway, designation Used parameters: Number of combintations: 26 Returns values: ]] function legal_maxspeed_be(parameters, tags, result) local result if (tags["highway"] ~= nil) then local v10 v10 = tags["highway"] if (v10 == "cycleway") then result = 30 elseif (v10 == "footway") then result = 20 elseif (v10 == "crossing") then result = 20 elseif (v10 == "pedestrian") then result = 15 elseif (v10 == "path") then result = 15 elseif (v10 == "corridor") then result = 5 elseif (v10 == "residential") then result = 30 elseif (v10 == "living_street") then result = 20 elseif (v10 == "service") then result = 30 elseif (v10 == "services") then result = 30 elseif (v10 == "track") then result = 50 elseif (v10 == "unclassified") then result = 50 elseif (v10 == "road") then result = 50 elseif (v10 == "motorway") then result = 120 elseif (v10 == "motorway_link") then result = 120 elseif (v10 == "primary") then result = 90 elseif (v10 == "primary_link") then result = 90 elseif (v10 == "secondary") then result = 50 elseif (v10 == "secondary_link") then result = 50 elseif (v10 == "tertiary") then result = 50 elseif (v10 == "tertiary_link") then result = 50 end end if (tags["designation"] ~= nil) then local v11 v11 = tags["designation"] if (v11 == "towpath") then result = 30 end end if (tags["maxspeed"] ~= nil) then result = parse(tags["maxspeed"]) end if (result == nil) then result = 30 end return result end failed_profile_tests = false --[[ Unit test of a behaviour function for an itinero 2.0 profile ]] function unit_test_profile(profile_function, profile_name, index, expected, tags) -- Note: we don't actually use 'profile_function' local result = {} local profile_failed = false factor(tags, result) local forward_access = result.forward_speed > 0 and result.forward > 0; local backward_access = result.backward_speed > 0 and result.backward > 0; if (not forward_access and not backward_access) then if (expected.access == "no" or expected.speed <= 0 or expected.priority <= 0) then -- All is fine, we can't access this thing anyway return end profile_failed = true print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but forward and backward are 0 (for either speed or factor)") end if (expected.oneway == "with") then if (backward_access) then -- we can go against the direction, not good print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is possible") profile_failed = true; end if (not forward_access) then print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is not possible") end end if (expected.oneway == "against") then if (forward_access) then -- we can go against the direction, not good print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is possible") end if (not backward_access) then print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is not possible") end end if (math.abs(result.forward_speed - expected.speed) >= 0.001 and math.abs(result.backward_speed - expected.speed) >= 0.001) then print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.forward_speed .. " forward and " .. result.backward_speed .. " backward") profile_failed = true; end if (math.abs(result.forward - expected.priority) >= 0.001 and math.abs(result.backward - expected.priority) >= 0.001) then print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".priority: expected " .. expected.priority .. " but got " .. result.forward .. " forward and " .. result.backward .. " backward") profile_failed = true; end if(profile_failed) then failed_profile_tests = true; debug_table(tags, "tags: ") debug_table(expected, "expected: ") debug_table(result, "result: ") end end function inv(n) return 1/n end function double_compare(a, b) if (b == nil) then return false end if (type(a) ~= "number") then a = parse(a) end if(type(b) ~= "number") then b = parse(b) end if (a == b) then return true end return math.abs(a - b) < 0.0001 end function debug_table(table, prefix) if (prefix == nil) then prefix = "" end for k, v in pairs(table) do if (type(v) == "table") then debug_table(v, " ") else print(prefix .. tostring(k) .. " = " .. tostring(v)) end end print("") end function debug_table_str(table, prefix) if (prefix == nil) then prefix = "" end local str = ""; for k, v in pairs(table) do if (type(v) == "table") then str = str .. "," .. debug_table_str(v, " ") else str = str .. "," .. (prefix .. tostring(k) .. " = " .. tostring(v)) end end return str end function parse(string) if (string == nil) then return 0 end if (type(string) == "number") then return string end if (string == "yes" or string == "true") then return 1 end if (string == "no" or string == "false") then return 0 end if (type(string) == "boolean") then if (string) then return 1 else return 0 end end if(string:match("%d+:%d+")) then -- duration in minute local duration = 0 for part in string:gmatch "%d+" do duration = duration * 60 + tonumber(part) end return duration end return tonumber(string) end function eq(a, b) if (a == b) then return "yes" else return "no" end end function string_start(strt, s) return string.sub(s, 1, string.len(strt)) == strt end -- every key starting with "_relation::XXX" is rewritten to "_relation:XXX" function remove_relation_prefix(tags, name) local new_tags = {} for k, v in pairs(tags) do local prefix = "_relation:" .. name .. ":"; if (string_start(prefix, k)) then local new_key = "_relation:" .. string.sub(k, string.len(prefix) + 1) -- plus 1: sub uses one-based indexing to select the start new_tags[new_key] = v else new_tags[k] = v end end return new_tags end function min(list) local min for _, value in pairs(list) do if(value ~= nil) then if (min == nil) then min = value elseif (value < min) then min = value end end end return min; end function test_all() -- Behaviour tests -- unit_test_profile(behaviour_bicycle_fastest, "fastest", 0, {access = "no", speed = 0, oneway = "both", priority = inv(0) }, {}) unit_test_profile(behaviour_bicycle_fastest, "fastest", 1, {access = "designated", speed = 15, oneway = "both", priority = inv(15) }, {highway = "cycleway"}) unit_test_profile(behaviour_bicycle_fastest, "fastest", 2, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "residential"}) unit_test_profile(behaviour_bicycle_fastest, "fastest", 3, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "pedestrian", bicycle = "yes"}) unit_test_profile(behaviour_bicycle_fastest, "fastest", 4, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "unclassified", ["cycleway:left"] = "track", oneway = "yes", ["oneway:bicycle"] = "no"}) unit_test_profile(behaviour_bicycle_fastest, "fastest", 5, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "service"}) unit_test_profile(behaviour_bicycle_fastest, "fastest", 6, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "tertiary", access = "yes", maxspeed = "50"}) unit_test_profile(behaviour_bicycle_fastest, "fastest", 7, {access = "yes", speed = 15, oneway = "with", priority = inv(15) }, {highway = "residential", junction = "roundabout"}) end if (itinero == nil) then itinero = {} itinero.log = print -- Itinero is not defined -> we are running from a lua interpreter -> the tests are intended runTests = true else print = itinero.log end test_all() if (not failed_tests and not failed_profile_tests and print ~= nil) then print("Tests OK") end