diff --git a/CHANGELOG.md b/CHANGELOG.md index a7c882794a4..79978766cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # 5.10.0 - - Changes from 5.9: + - Changes from 5.9: + - Profiles: + - New version 2 profile API which cleans up a number of things and makes it easier to for profiles to include each other. Profiles using the old version 0 and 1 APIs are still supported. + - New required `setup()` function that must return a configuration hash. Storing configuration in globals is deprecated. + - Passes the config hash returned in `setup()` as an argument to `process_node/way/segment/turn`. + - Properties are now set in `.properties` in the config hash returend by setup(). + - initialize raster sources in `setup()` instead of in a separate callback. + - Renames the `sources` helper to `raster`. + - Renames `way_functions` to `process_way` (same for node, segment and turn). + - Removes `get_restrictions()`. Instead set `.restrictions` in the config hash in `setup()`. + - Removes `get_name_suffix_list()`. Instead set `.suffix_list` in the config hash in `setup()`. + - Renames `Handlers` to `WayHandlers`. + - Pass functions instead of strings to `WayHandlers.run()`, so it's possible to mix in your own functions. + - Reorders arguments to `WayHandlers` functions to match `process_way()`. + - Profiles must return a hash of profile functions. This makes it easier for profiles to include each other. # 5.9.0 - Changes from 5.8: diff --git a/docs/profiles.md b/docs/profiles.md index a2c32034aa6..28a0947b23e 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -1,31 +1,97 @@ -OSRM supports "profiles". Configurations representing different routing behaviours for (typically) different transport modes. A profile describes whether or not we route along a particular type of way, or over a particular node in the OpenStreetMap data, and also how quickly we'll be travelling when we do. This feeds into the way the routing graph is created and thus influences the output routes. +# OSRM profiles +OSRM supports "profiles". Profiles representing routing behavior for different transport modes like car, bike and foot. You can also create profiles for variations like a fastest/shortest car profile or fastest/safest/greenest bicycles profile. + + A profile describes whether or not it's possible to route along a particular type of way, whether we can pass a particular node, and how quickly we'll be traveling when we do. This feeds into the way the routing graph is created and thus influences the output routes. ## Available profiles +Out-of-the-box OSRM comes with profiles for car, bicycle and foot. You can easily modify these or create new ones if you like. + +Profiles have a 'lua' extension, and are places in 'profiles' directory. + +When running OSRM preprocessing commands you specify the profile with the --profile (or the shorthand -p) option, for example: + +`osrm-extract --profile ../profiles/car.lua planet-latest.osm.pbf` + +## Processing flow +It's important to understand that profiles are used when preprocessing the OSM data, NOT at query time when routes are computed. + +This means that after modifying a profile **you will need to extract, contract and reload the data again** and to see changes in the routing results. See [Processing Flow](https://github.com/Project-OSRM/osrm-backend/wiki/Processing-Flow) for more. + +## Profiles are written in LUA +Profiles are not just configuration files. They are scripts written in the [LUA scripting language](http://www.lua.org). The reason for this is that OpenStreetMap data is complex, and it's not possible to simply define tag mappings. LUA scripting offers a powerful way to handle all the possible tag combinations found in OpenStreetMap nodes and ways. + +## Basic structure of profiles +A profile will process every node and way in the OSM input data to determine what ways are routable in which direction, at what speed, etc. + +A profile will typically: + +- Define api version (required) +- Require library files (optional) +- Define setup function (required) +- Define process functions (some are required) +- Return functions table (required) + +A profile can also define various local functions it needs. + +Looking at [car.lua](../profiles/car.lua) as an example, at the top of the file the api version is defined and then required library files are included. + +Then follows the `setup` functions, which is called once when the profile is loaded. It returns a big hash table of configurations, specifying things like what speed to use for different way types. The configurations are used later in the various processing functions. Many adjustments can be done just be modifying this configuration table. -Out-of-the-box OSRM comes with several different profiles, including car, bicycle and foot. +The setup function is also where you can do other setup, like loading elevation data source if you want to consider that when processing ways. -Profile configuration files have a 'lua' extension, and are found under the 'profiles' subdirectory. -Alternatively commands will take a lua profile specified with an explicit -p param, for example: +Then comes the `process_node` and `process_way` functions, which are called for each OSM node and way when extracting OpenStreetMap data with `osrm-extract`. -`osrm-extract -p ../profiles/car.lua planet-latest.osm.pbf` +The `process_turn` function processes every possible turn in the network, and sets a penalty depending on the angle and turn of the movement. -And then **you will need to extract and contract again** (A change to the profile will typically affect the extract step as well as the contract step. See [Processing Flow](https://github.com/Project-OSRM/osrm-backend/wiki/Processing-Flow)) +Profiles can also define a `process_segment` function to handle differences in speed along an OSM way, for example to handle elevation. As you can see, this is not currently used in the car profile. -## lua scripts? +At the end of the file, a table if returned with references to the setup and processing functions the profile has defined. -Profiles are not just configuration files. They are scripts written in the "lua" scripting language ( http://www.lua.org ) The reason for this, is that OpenStreetMap data is not sufficiently straightforward, to simply define tag mappings. Lua scripting offers a powerful way of coping with the complexity of different node,way,relation,tag combinations found within OpenStreetMap data. +## Understanding speed, weight and rate +When computing a route from A to B there can be different measure of what is the best route. That's why there's a need for different profiles. -## Basic structure of a profile +Because speeds very on different types of roads, the shortest and the fastest route are typically different. But there are many other possible preferences. For example a user might prefer a bicycle route that follow parks or other green areas, even though both duration and distance are a bit longer. -You can understand these lua scripts enough to make interesting modifications, without needing to get to grips with how they work completely. +To handle this, OSRM doesn't simply choose the ways with the highest speed. Instead it uses the concept of `weight` and `rate`. The rate is an abstract measure that you can assign to ways as you like to make some ways preferable to others. Routing will prefer ways with high rate. -Towards the top of the file, a profile (such as [car.lua](../profiles/car.lua)) will typically define various configurations as global variables. A lot of these are look-up hashes of one sort or another. +The weight of a way normally computed as length / rate. The weight can be thought of as the resistance or cost when passing the way. Routing will prefer ways with low weight. -As you scroll down the file you'll see local variables, and then local functions, and finally... +You can also set the weight of a way to a fixed value, In this case it's not calculated based on the length or rate, and the rate is ignored. -`way_function` and `node_function` are the important functions which are called when extracting OpenStreetMap data with `osrm-extract`. +You should set the speed to you best estimate of the actual speed that will be used on a particular way. This will result in the best estimated travel times. -The following global properties can be set in your profile: +If you want to prefer certain ways due to other factors than the speed, adjust the rate accordingly. If you adjust the speed, the time time estimation will be skewed. + +If you set the same rate on all ways, the result will be shortest path routing. +If you set rate = speed on all ways, the result will be fastest path routing. +If you want to prioritize certain street, increase the rate on these. + +## Elements +### api_version +A profile should set api_version at the top of your profile. This is done to ensure that older profiles are still supported when the api changes. If api_version is not defined, 0 will be assumed. The current api version is 2. + +### Library files +The folder [profiles/lib/](../profiles/lib/) contains LUA library files for handling many common processing tasks. + +File | Notes +------------------|------------------------------ +way_handlers.lua | Functions for processing way tags +tags.lua | Functions for general parsing of OSM tags +set.lua | Defines the Set helper for handling sets of values +sequence.lua | Defines the Sequence helper for handling sequences of values +access.lua | Function for finding relevant access tags +destination.lua | Function for finding relevant destination tags +destination.lua | Function for determining maximum speed +guidance.lua | Function for processing guidance attributes + +They all return a table of functions when you use `require` to load them. You can either store this table and reference it's functions later, of if you need only a single you can store that directly. + +### setup() +The `setup` function is called once when the profile is loaded and must return a table of configurations. It's also where you can do other global setup, like loading data sources that are used during processing. + +Note that processing of data is parallelized and several unconnected LUA interpreters will be running at the same time. The `setup` function will be called once for each. Each LUA iinterpreter will have it's own set of globals. + +The following global properties can be set under `properties` in the hash you return in the `setup` function: Attribute | Type | Notes -------------------------------------|----------|---------------------------------------------------------------------------- @@ -36,17 +102,40 @@ use_turn_restrictions | Boolean | Are turn instructions followed continue_straight_at_waypoint | Boolean | Must the route continue straight on at a via point, or are U-turns allowed? (default `true`) 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 `segment_function` will be called for all segments (default `false`) +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`) + +### process_node(profile, node, result) +Process an OSM node to determine whether this node is a barrier or can be passed and whether passing it incurs a delay. -## way_function +Argument | Description +---------|------------------------------------------------------- +profile | The configuration table you returned in `setup`. +node | The input node to process (read-only). +result | The output that you will modify. -Given an OpenStreetMap way, the `way_function` will either return nothing (meaning we are not going to route over this way at all), or it will set up a result hash to be returned. The most important thing it will do is set the value of `result.forward_speed` and `result.backward_speed` as a suitable integer value representing the speed for traversing the way. +The following attributes can be set on `result`: + +Attribute | Type | Notes +----------------|---------|--------------------------------------------------------- +barrier | Boolean | Is it an impassable barrier? +traffic_lights | Boolean | Is it a traffic light (incurs delay in `process_turn`)? -All other calculations stem from that, including the returned timings in driving directions, but also, less directly, it feeds into the actual routing decisions the engine will take (a way with a slow traversal speed, may be less favoured than a way with fast traversal speed, but it depends how long it is, and... what it connects to in the rest of the network graph) +## process_way(profile, way, result) +Given an OpenStreetMap way, the `process_way` function will either return nothing (meaning we are not going to route over this way at all), or it will set up a result hash. -Using the power of the scripting language you wouldn't typically see something as simple as a `result.forward_speed = 20` line within the `way_function`. Instead a `way_function` will examine the tagging (e.g. `way:get_value_by_key("highway")` and many others), process this information in various ways, calling other local functions, referencing the global variables and look-up hashes, before arriving at the result. +Argument | Description +---------|------------------------------------------------------- +profile | The configuration table you returned in `setup`. +node | The input way to process (read-only). +result | The output that you will modify. -The following attributes can be set on the result in `way_function`: +Importantly it will set `result.forward_mode` and `result.backward_mode` to indicate the travel mode in each direction, as well as set `result.forward_speed` and `result.backward_speed` to integer values representing the speed for traversing the way. + +It will also set a number of other attributes on `result`. + +Using the power of the scripting language you wouldn't typically see something as simple as a `result.forward_speed = 20` line within the `process_way` function. Instead `process_way` will examine the tag set on the way, process this information in various ways, calling other local functions and referencing the configuration in `profile`, etc, before arriving at the result. + +The following attributes can be set on the result in `process_way`: Attribute | Type | Notes ----------------------------------------|----------|-------------------------------------------------------------------------- @@ -60,7 +149,7 @@ forward_classes | Table | Mark this way as being of a backward_classes | Table | " " duration | Float | Alternative setter for duration of the whole way in both directions weight | Float | Alternative setter for weight of the whole way in both directions -turn_lanes_forward | String | Directions for individual lanes (normalised OSM `turn:lanes` value) +turn_lanes_forward | String | Directions for individual lanes (normalized OSM `turn:lanes` value) turn_lanes_backward | String | " " forward_restricted | Boolean | Is this a restricted access road? (e.g. private, or deliveries only; used to enable high turn penalty, so that way is only chosen for start/end of route) backward_restricted | Boolean | " " @@ -78,32 +167,19 @@ road_classification.road_priority_class | Enum | Guidance: order in priority road_classification.may_be_ignored | Boolean | Guidance: way is non-highway road_classification.num_lanes | Unsigned | Guidance: total number of lanes in way -### Guidance - -The guidance parameters in profiles are currently a work in progress. They can and will change. -Please be aware of this when using guidance configuration possibilities. - -Guidance uses road classes to decide on when/if to emit specific instructions and to discover which road is obvious when following a route. -Classification uses three flags and a priority-category. -The flags indicate whether a road is a motorway (required for on/off ramps), a link type (the ramps itself, if also a motorway) and whether a road may be omittted in considerations (is considered purely for connectivity). -The priority-category influences the decision which road is considered the obvious choice and which roads can be seen as fork. -Forks can be emitted between roads of similar priority category only. Obvious choices follow a major priority road, if the priority difference is large. - -## node_function +### process_segment(profile, segment) +The `process_segment` function is called for every segment of OSM ways. A segment is a straight line between two OSM nodes. -The following attributes can be set on the result in `node_function`: +On OpenStreetMap way cannot have different tags on different parts of a way. Instead you would split the way into several smaller ways. However many ways are long. For example, many ways pass hills without any change in tags. -Attribute | Type | Notes -----------------|---------|------------------------------------------------------- -barrier | Boolean | Is it an impassable barrier? -traffic_lights | Boolean | Is it a traffic light (incurs delay in `turn_function`)? +Processing each segment of an OSM way makes it possible to have different speeds on different parts of a way based on external data like data about elevation, pollution, noise or scenic value and adjust weight and duration of the segment. -## segment_function +In the `process_segment` you don't have access to OSM tags. Instead you use the geographical location of the start and end point of the way to lookup other data source, like elevation data. See [rasterbot.lua](../profiles/rasterbot.lua) for an example. -The following attributes can be read and set on the result in `segment_function`: +The following attributes can be read and set on the result in `process_segment`: Attribute | Read/write? | Type | Notes --------------------|-------------|---------|------------------------------------------------------ +-------------------|-------------|---------|---------------------------------------- source.lon | Read | Float | Co-ordinates of segment start source.lat | Read | Float | " " target.lon | Read | Float | Co-ordinates of segment end @@ -112,17 +188,88 @@ target.distance | Read | Float | Length of segment weight | Read/write | Float | Routing weight for this segment duration | Read/write | Float | Duration for this segment -## turn_function +### process_turn(profile, turn) +The `process_turn` function is called for every possible turn in the network. Based on the angle and type of turn you assign the weight and duration of the movement. -The following attributes can be read and set on the result in `turn_function`: +The following attributes can be read and set on the result in `process_turn`: Attribute | Read/write? | Type | Notes -------------------|-------------|---------|------------------------------------------------------ direction_modifier | Read | Enum | Geometry of turn. Defined in `include/extractor/guidance/turn_instruction.hpp` turn_type | Read | Enum | Priority of turn. Defined in `include/extractor/guidance/turn_instruction.hpp` has_traffic_light | Read | Boolean | Is a traffic light present at this turn? -source_restricted | Read | Boolean | Is it from a restricted access road? (See definition in `way_function`) -target_restricted | Read | Boolean | Is it to a restricted access road? (See definition in `way_function`) +source_restricted | Read | Boolean | Is it from a restricted access road? (See definition in `process_way`) +target_restricted | Read | Boolean | Is it to a restricted access road? (See definition in `process_way`) angle | Read | Float | Angle of turn in degrees (`0-360`: `0`=u-turn, `180`=straight on) duration | Read/write | Float | Penalty to be applied for this turn (duration in deciseconds) weight | Read/write | Float | Penalty to be applied for this turn (routing weight) + +## Guidance +The guidance parameters in profiles are currently a work in progress. They can and will change. +Please be aware of this when using guidance configuration possibilities. + +Guidance uses road classes to decide on when/if to emit specific instructions and to discover which road is obvious when following a route. +Classification uses three flags and a priority-category. +The flags indicate whether a road is a motorway (required for on/off ramps), a link type (the ramps itself, if also a motorway) and whether a road may be omitted in considerations (is considered purely for connectivity). +The priority-category influences the decision which road is considered the obvious choice and which roads can be seen as fork. +Forks can be emitted between roads of similar priority category only. Obvious choices follow a major priority road, if the priority difference is large. + +### Using raster data +OSRM has build-in support for loading an interpolating raster data in ASCII format. This can be used e.g. for factoring in elevation when computing routes. + +Use `raster:load()` in your `setup` function to load data and store the source in your configuration hash: + +```lua +function setup() + return { + raster_source = raster:load( + "rastersource.asc", -- file to load + 0, -- longitude min + 0.1, -- longitude max + 0, -- latitude min + 0.1, -- latitude max + 5, -- number of rows + 4 -- number of columns + ) + } +end +``` + +The input data must an ASCII file with rows of integers. e.g.: + +``` +0 0 0 0 +0 0 0 250 +0 0 250 500 +0 0 0 250 +0 0 0 0 +``` + +In your `segment_function` you can then access the raster source and use `raster:query()` to query to find the nearest data point, or `raster:interpolate()` to interpolate a value based on nearby data points. + +You must check whether the result is valid before use it. + +Example: + +```lua +function process_segment (profile, segment) + local sourceData = raster:query(profile.raster_source, segment.source.lon, segment.source.lat) + local targetData = raster:query(profile.raster_source, segment.target.lon, segment.target.lat) + + local invalid = sourceData.invalid_data() + if sourceData.datum ~= invalid and targetData.datum ~= invalid then + -- use values to adjust weight and duration + [...] +end +``` + +See [rasterbot.lua](../profiles/rasterbot.lua) and [rasterbotinterp.lua](../profiles/rasterbotinterp.lua) for examples. + +### Helper functions +There are a few helper functions defined in the global scope that profiles can use: + +durationIsValid +parseDuration +trimLaneString +applyAccessTokens +canonicalizeStringList \ No newline at end of file diff --git a/features/bicycle/alley.feature b/features/bicycle/alley.feature index 3d3e9fbfc59..74a87b98da6 100644 --- a/features/bicycle/alley.feature +++ b/features/bicycle/alley.feature @@ -2,10 +2,9 @@ Feature: Bicycle - Route around alleys Background: - Given the profile file + Given the profile file "bicycle" initialized with """ - require 'bicycle' - properties.weight_name = 'cyclability' + profile.properties.weight_name = 'cyclability' """ Scenario: Bicycle - Avoid taking alleys diff --git a/features/bicycle/bridge.feature b/features/bicycle/bridge.feature index a02d586be72..7cac161074e 100644 --- a/features/bicycle/bridge.feature +++ b/features/bicycle/bridge.feature @@ -4,7 +4,7 @@ Feature: Bicycle - Handle cycling Background: Given the profile "bicycle" - Scenario: Bicycle - Use a ferry route + Scenario: Bicycle - Use a movable bridge Given the node map """ a b c diff --git a/features/bicycle/safety.feature b/features/bicycle/safety.feature index 4966270eb57..b09099c0966 100644 --- a/features/bicycle/safety.feature +++ b/features/bicycle/safety.feature @@ -2,10 +2,9 @@ Feature: Bicycle - Adds penalties to unsafe roads Background: - Given the profile file + Given the profile file "bicycle" initialized with """ - require 'bicycle' - properties.weight_name = 'cyclability' + profile.properties.weight_name = 'cyclability' """ Scenario: Bike - Apply penalties to ways without cycleways diff --git a/features/bicycle/turn_penalty.feature b/features/bicycle/turn_penalty.feature index 7ef4283b8e0..d0c144e320f 100644 --- a/features/bicycle/turn_penalty.feature +++ b/features/bicycle/turn_penalty.feature @@ -7,10 +7,9 @@ Feature: Turn Penalties Scenario: Bicycle - Turn penalties on cyclability - Given the profile file + Given the profile file "bicycle" initialized with """ - require 'bicycle' - properties.weight_name = 'cyclability' + profile.properties.weight_name = 'cyclability' """ Given the node map diff --git a/features/car/side_bias.feature b/features/car/side_bias.feature index 708e45c058d..155a6f49b55 100644 --- a/features/car/side_bias.feature +++ b/features/car/side_bias.feature @@ -1,18 +1,11 @@ @routing @testbot @sidebias Feature: Testbot - side bias - Background: - Given the profile file + Scenario: Left-hand bias + Given the profile file "car" initialized with """ - require 'testbot' - properties.left_hand_driving = true - """ - - Scenario: Left hand bias - Given the profile file "car" extended with - """ - properties.left_hand_driving = true - profile.turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 + profile.left_hand_driving = true + profile.turn_bias = 1/1.075 """ Given the node map """ @@ -31,11 +24,11 @@ Feature: Testbot - side bias | d | a | bd,ab,ab | 24s +-1 | | d | c | bd,bc,bc | 27s +-1 | - Scenario: Right hand bias - Given the profile file "car" extended with + Scenario: Right-hand bias + Given the profile file "car" initialized with """ - properties.left_hand_driving = false - profile.turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 + profile.left_hand_driving = true + profile.turn_bias = 1.075 """ And the node map """ @@ -56,6 +49,11 @@ Feature: Testbot - side bias | d | c | bd,bc,bc | 24s +-1 | Scenario: Roundabout exit counting for left sided driving + Given the profile file "testbot" initialized with + """ + profile.left_hand_driving = true + profile.turn_bias = 1/1.075 + """ And a grid size of 10 meters And the node map """ diff --git a/features/car/weight.feature b/features/car/weight.feature index b303b894350..6889a2b1f13 100644 --- a/features/car/weight.feature +++ b/features/car/weight.feature @@ -1,11 +1,9 @@ @routing @car @weight Feature: Car - weights - Background: Use specific speeds - Given the profile "car" - Scenario: Only routes down service road when that's the destination - Given the node map + Given the profile "car" + And the node map """ a--b--c | @@ -25,7 +23,8 @@ Feature: Car - weights | a | d | abc,bdf,bdf | 18 km/h | 71.7 | Scenario: Does not jump off the highway to go down service road - Given the node map + Given the profile "car" + And the node map """ a | @@ -63,10 +62,9 @@ Feature: Car - weights | a | e | ab,be,be | 14 km/h | 112 | Scenario: Distance weights - Given the profile file "car" extended with + Given the profile file "car" initialized with """ - api_version = 1 - properties.weight_name = 'distance' + profile.properties.weight_name = 'distance' """ Given the node map diff --git a/features/guidance/anticipate-lanes.feature b/features/guidance/anticipate-lanes.feature index 3a51cac262b..e60f3870fe6 100644 --- a/features/guidance/anticipate-lanes.feature +++ b/features/guidance/anticipate-lanes.feature @@ -582,9 +582,9 @@ Feature: Turn Lane Guidance @anticipate Scenario: No Lanes for Roundabouts, see #2626 - Given the profile file "car" extended with + Given the profile file "car" initialized with """ - properties.left_hand_driving = true + profile.left_hand_driving = true """ And the node map """ diff --git a/features/guidance/roundabout-left-sided.feature b/features/guidance/roundabout-left-sided.feature index a2143efbc34..86e084413a3 100644 --- a/features/guidance/roundabout-left-sided.feature +++ b/features/guidance/roundabout-left-sided.feature @@ -3,11 +3,10 @@ Feature: Basic Roundabout Background: Given a grid size of 10 meters - Given the profile file - """ - require 'car' - properties.left_hand_driving = true - """ + Given the profile file "car" initialized with + """ + profile.properties.left_hand_driving = true + """ Scenario: Roundabout exit counting for left sided driving And a grid size of 10 meters diff --git a/features/options/contract/edge-weight-updates-over-factor.feature b/features/options/contract/edge-weight-updates-over-factor.feature index 92658299701..ef5110e01bb 100644 --- a/features/options/contract/edge-weight-updates-over-factor.feature +++ b/features/options/contract/edge-weight-updates-over-factor.feature @@ -33,15 +33,25 @@ Feature: osrm-contract command line option: edge-weight-updates-over-factor Scenario: Logging using weigts as durations for non-duration profile - Given the profile file "testbot" extended with + Given the profile file """ - properties.weight_name = 'steps' - function way_function(way, result) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.properties.weight_name = 'steps' + return profile + end + + functions.process_way = function(profile, way, result) result.forward_mode = mode.driving result.backward_mode = mode.driving result.weight = 1 result.duration = 1 end + + return functions """ And the data has been saved to disk diff --git a/features/options/extract/lua.feature b/features/options/extract/lua.feature index b1c74b07fc6..b515d450dd6 100644 --- a/features/options/extract/lua.feature +++ b/features/options/extract/lua.feature @@ -12,15 +12,20 @@ Feature: osrm-extract lua ways:get_nodes() And the data has been saved to disk Scenario: osrm-extract - Passing base file - Given the profile file "testbot" extended with + Given the profile file """ - function way_function(way, result) + functions = require('testbot') + + function way_function(profile, way, result) for _, node in ipairs(way:get_nodes()) do print('node id ' .. node:id()) end result.forward_mode = mode.driving result.forward_speed = 1 end + + functions.process_way = way_function + return functions """ When I run "osrm-extract --profile {profile_file} {osm_file}" Then it should exit successfully diff --git a/features/options/profiles/invalid_version.feature b/features/options/profiles/invalid_version.feature new file mode 100644 index 00000000000..6168f6717c9 --- /dev/null +++ b/features/options/profiles/invalid_version.feature @@ -0,0 +1,40 @@ +Feature: Invalid profile API versions + + Background: + Given a grid size of 100 meters + + Scenario: Profile API version too low + Given the profile file + """ + api_version = -1 + """ + And the node map + """ + ab + """ + And the ways + | nodes | + | ab | + And the data has been saved to disk + + When I try to run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit with an error + And stderr should contain "Invalid profile API version" + + Scenario: Profile API version too high + Given the profile file + """ + api_version = 3 + """ + And the node map + """ + ab + """ + And the ways + | nodes | + | ab | + And the data has been saved to disk + + When I try to run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit with an error + And stderr should contain "Invalid profile API version" diff --git a/features/options/profiles/version0.feature b/features/options/profiles/version0.feature index dce78934956..8d477b8bfb6 100644 --- a/features/options/profiles/version0.feature +++ b/features/options/profiles/version0.feature @@ -1,84 +1,93 @@ Feature: Profile API version 0 - Background: - Given a grid size of 100 meters - - Scenario: Not-defined API version + Scenario: Profile api version 0 Given the profile file - """ -function way_function(way, result) - result.forward_mode = mode.driving - result.forward_speed = 1 -end - """ + """ + api_version = 0 + -- set profile properties + properties.u_turn_penalty = 20 + properties.traffic_signal_penalty = 2 + properties.max_speed_for_map_matching = 180/3.6 + properties.use_turn_restrictions = true + properties.continue_straight_at_waypoint = true + properties.left_hand_driving = false + properties.weight_name = 'duration' + function node_function (node, result) + print ('node_function ' .. node:id()) + end + function way_function(way, result) + result.name = way:get_value_by_key('name') + result.forward_mode = mode.driving + result.backward_mode = mode.driving + result.forward_speed = 36 + result.backward_speed = 36 + print ('way_function ' .. way:id() .. ' ' .. result.name) + end + function turn_function (angle) + print('turn_function ' .. angle) + return angle == 0 and 0 or 42 + end + function segment_function (source, target, distance, weight) + print ('segment_function ' .. source.lon .. ' ' .. source.lat) + end + """ And the node map - """ - ab - """ + """ + a + b c d + e + """ And the ways | nodes | - | ab | + | ac | + | cb | + | cd | + | ce | And the data has been saved to disk - When I try to run "osrm-extract --profile {profile_file} {osm_file}" + When I run "osrm-extract --profile {profile_file} {osm_file}" Then it should exit successfully - And stderr should not contain "Invalid profile API version" - - Scenario: Out-bound API version - Given the profile file - """ -api_version = 2 - """ - And the node map - """ - ab - """ - And the ways - | nodes | - | ab | - And the data has been saved to disk - - When I try to run "osrm-extract --profile {profile_file} {osm_file}" - Then it should exit with an error - And stderr should contain "Invalid profile API version" + And stdout should contain "node_function" + And stdout should contain "way_function" + And stdout should contain "turn_function" + And stdout should contain "segment_function" + When I route I should get + | from | to | route | time | + | a | b | ac,cb,cb | 24.2s | + | a | d | ac,cd,cd | 24.2s | + | a | e | ac,ce | 20s | - Scenario: Basic profile function calls and property values + Scenario: Profile version undefined, assume version 0 Given the profile file - """ -api_version = 0 - --- set profile properties -properties.u_turn_penalty = 20 -properties.traffic_signal_penalty = 2 -properties.max_speed_for_map_matching = 180/3.6 -properties.use_turn_restrictions = true -properties.continue_straight_at_waypoint = true -properties.left_hand_driving = false -properties.weight_name = 'duration' - -function node_function (node, result) - print ('node_function ' .. node:id()) -end - -function way_function(way, result) - result.name = way:get_value_by_key('name') - result.forward_mode = mode.driving - result.backward_mode = mode.driving - result.forward_speed = 36 - result.backward_speed = 36 - print ('way_function ' .. way:id() .. ' ' .. result.name) -end - -function turn_function (angle) - print('turn_function ' .. angle) - return angle == 0 and 0 or 42 -end - -function segment_function (source, target, distance, weight) - print ('segment_function ' .. source.lon .. ' ' .. source.lat) -end - """ + """ + -- set profile properties + properties.u_turn_penalty = 20 + properties.traffic_signal_penalty = 2 + properties.max_speed_for_map_matching = 180/3.6 + properties.use_turn_restrictions = true + properties.continue_straight_at_waypoint = true + properties.left_hand_driving = false + properties.weight_name = 'duration' + function node_function (node, result) + print ('node_function ' .. node:id()) + end + function way_function(way, result) + result.name = way:get_value_by_key('name') + result.forward_mode = mode.driving + result.backward_mode = mode.driving + result.forward_speed = 36 + result.backward_speed = 36 + print ('way_function ' .. way:id() .. ' ' .. result.name) + end + function turn_function (angle) + print('turn_function ' .. angle) + return angle == 0 and 0 or 42 + end + function segment_function (source, target, distance, weight) + print ('segment_function ' .. source.lon .. ' ' .. source.lat) + end + """ And the node map """ a @@ -104,4 +113,4 @@ end | from | to | route | time | | a | b | ac,cb,cb | 24.2s | | a | d | ac,cd,cd | 24.2s | - | a | e | ac,ce | 20s | + | a | e | ac,ce | 20s | \ No newline at end of file diff --git a/features/options/profiles/version1.feature b/features/options/profiles/version1.feature index a73ae66be2b..12f35c6be3a 100644 --- a/features/options/profiles/version1.feature +++ b/features/options/profiles/version1.feature @@ -6,41 +6,108 @@ Feature: Profile API version 1 Scenario: Basic profile function calls and property values Given the profile file """ -api_version = 1 - --- set profile properties -properties.max_speed_for_map_matching = 180/3.6 -properties.use_turn_restrictions = true -properties.continue_straight_at_waypoint = true -properties.weight_name = 'test_version1' -properties.weight_precision = 2 - -assert(properties.max_turn_weight == 327.67) - -function node_function (node, result) - print ('node_function ' .. node:id()) -end - -function way_function(way, result) - result.name = way:get_value_by_key('name') - result.weight = 10 - result.forward_mode = mode.driving - result.backward_mode = mode.driving - result.forward_speed = 36 - result.backward_speed = 36 - print ('way_function ' .. way:id() .. ' ' .. result.name) -end - -function turn_function (turn) - print('turn_function', turn.angle, turn.turn_type, turn.direction_modifier, turn.has_traffic_light) - turn.weight = turn.angle == 0 and 0 or 4.2 - turn.duration = turn.weight -end - -function segment_function (segment) - print ('segment_function ' .. segment.source.lon .. ' ' .. segment.source.lat) -end + api_version = 1 + + -- set profile properties + properties.max_speed_for_map_matching = 180/3.6 + properties.use_turn_restrictions = true + properties.continue_straight_at_waypoint = true + properties.weight_name = 'test_version1' + properties.weight_precision = 2 + + assert(properties.max_turn_weight == 327.67) + + function node_function (node, result) + print(node, result) + print ('node_function ' .. node:id()) + end + + function way_function(way, result) + result.name = way:get_value_by_key('name') + result.weight = 10 + result.forward_mode = mode.driving + result.backward_mode = mode.driving + result.forward_speed = 36 + result.backward_speed = 36 + print ('way_function ' .. way:id() .. ' ' .. result.name) + end + + function turn_function (turn) + print('turn_function', turn.angle, turn.turn_type, turn.direction_modifier, turn.has_traffic_light) + turn.weight = turn.angle == 0 and 0 or 4.2 + turn.duration = turn.weight + end + + function segment_function (segment) + print ('segment_function ' .. segment.source.lon .. ' ' .. segment.source.lat) + end + """ + And the node map """ + a + bcd + e + """ + And the ways + | nodes | + | ac | + | cb | + | cd | + | ce | + And the data has been saved to disk + + When I run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit successfully + And stdout should contain "node_function" + And stdout should contain "way_function" + And stdout should contain "turn_function" + And stdout should contain "segment_function" + + When I route I should get + | from | to | route | time | + | a | b | ac,cb,cb | 19.2s | + | a | d | ac,cd,cd | 19.2s | + | a | e | ac,ce | 20s | + + Scenario: Basic profile function calls and property values + Given the profile file + """ + api_version = 1 + + -- set profile properties + properties.max_speed_for_map_matching = 180/3.6 + properties.use_turn_restrictions = true + properties.continue_straight_at_waypoint = true + properties.weight_name = 'test_version1' + properties.weight_precision = 2 + + assert(properties.max_turn_weight == 327.67) + + function node_function (node, result) + print(node, result) + print ('node_function ' .. node:id()) + end + + function way_function(way, result) + result.name = way:get_value_by_key('name') + result.weight = 10 + result.forward_mode = mode.driving + result.backward_mode = mode.driving + result.forward_speed = 36 + result.backward_speed = 36 + print ('way_function ' .. way:id() .. ' ' .. result.name) + end + + function turn_function (turn) + print('turn_function', turn.angle, turn.turn_type, turn.direction_modifier, turn.has_traffic_light) + turn.weight = turn.angle == 0 and 0 or 4.2 + turn.duration = turn.weight + end + + function segment_function (segment) + print ('segment_function ' .. segment.source.lon .. ' ' .. segment.source.lat) + end + """ And the node map """ a @@ -67,3 +134,99 @@ end | a | b | ac,cb,cb | 19.2s | | a | d | ac,cd,cd | 19.2s | | a | e | ac,ce | 20s | + + Scenario: Weighting based on raster sources + Given the profile file + """ + api_version = 1 + + properties.force_split_edges = true + + function source_function() + local path = os.getenv('OSRM_RASTER_SOURCE') + if not path then + path = 'rastersource.asc' + end + raster_source = sources:load( + path, + 0, -- lon_min + 0.1, -- lon_max + 0, -- lat_min + 0.1, -- lat_max + 5, -- nrows + 4 -- ncols + ) + end + + function way_function (way, result) + result.name = way:get_value_by_key('name') + result.forward_mode = mode.cycling + result.backward_mode = mode.cycling + result.forward_speed = 15 + result.backward_speed = 15 + end + + function segment_function (segment) + local sourceData = sources:query(raster_source, segment.source.lon, segment.source.lat) + local targetData = sources:query(raster_source, segment.target.lon, segment.target.lat) + io.write('evaluating segment: ' .. sourceData.datum .. ' ' .. targetData.datum .. '\n') + local invalid = sourceData.invalid_data() + local scaled_weight = segment.weight + local scaled_duration = segment.duration + + if sourceData.datum ~= invalid and targetData.datum ~= invalid then + local slope = (targetData.datum - sourceData.datum) / segment.distance + scaled_weight = scaled_weight / (1.0 - (slope * 5.0)) + scaled_duration = scaled_duration / (1.0 - (slope * 5.0)) + io.write(' slope: ' .. slope .. '\n') + io.write(' was weight: ' .. segment.weight .. '\n') + io.write(' new weight: ' .. scaled_weight .. '\n') + io.write(' was duration: ' .. segment.duration .. '\n') + io.write(' new duration: ' .. scaled_duration .. '\n') + end + + segment.weight = scaled_weight + segment.duration = scaled_duration + end + """ + And the node locations + | node | lat | lon | + | a | 0.1 | 0.1 | + | b | 0.05 | 0.1 | + | c | 0.0 | 0.1 | + | d | 0.05 | 0.03 | + | e | 0.05 | 0.066 | + | f | 0.075 | 0.066 | + And the ways + | nodes | highway | + | ab | primary | + | ad | primary | + | bc | primary | + | dc | primary | + | de | primary | + | eb | primary | + | df | primary | + | fb | primary | + And the raster source + """ + 0 0 0 0 + 0 0 0 250 + 0 0 250 500 + 0 0 0 250 + 0 0 0 0 + """ + And the data has been saved to disk + + When I route I should get + | from | to | route | speed | + | a | b | ab,ab | 8 km/h | + | b | a | ab,ab | 22 km/h | + | a | c | ab,bc,bc | 12 km/h | + | b | c | bc,bc | 22 km/h | + | a | d | ad,ad | 15 km/h | + | d | c | dc,dc | 15 km/h | + | d | e | de,de | 10 km/h | + | e | b | eb,eb | 10 km/h | + | d | f | df,df | 15 km/h | + | f | b | fb,fb | 7 km/h | + | d | b | de,eb,eb | 10 km/h | diff --git a/features/options/profiles/version2.feature b/features/options/profiles/version2.feature new file mode 100644 index 00000000000..a2c54f8d0fd --- /dev/null +++ b/features/options/profiles/version2.feature @@ -0,0 +1,87 @@ +Feature: Profile API version 2 + + Background: + Given a grid size of 100 meters + + Scenario: Basic profile function calls and property values + Given the profile file + """ + 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 + limit = require("lib/maxspeed").limit + + + function setup() + return { + properties = { + max_speed_for_map_matching = 180/3.6, + use_turn_restrictions = true, + continue_straight_at_waypoint = true, + weight_name = 'test_version2', + weight_precision = 2 + } + } + end + + function process_node(profile, node, result) + print ('process_node ' .. node:id()) + end + + function process_way(profile, way, result) + result.name = way:get_value_by_key('name') + result.weight = 10 + result.forward_mode = mode.driving + result.backward_mode = mode.driving + result.forward_speed = 36 + result.backward_speed = 36 + print ('process_way ' .. way:id() .. ' ' .. result.name) + end + + function process_turn (profile, turn) + print('process_turn', turn.angle, turn.turn_type, turn.direction_modifier, turn.has_traffic_light) + turn.weight = turn.angle == 0 and 0 or 4.2 + turn.duration = turn.weight + end + + function process_segment (profile, segment) + print ('process_segment ' .. segment.source.lon .. ' ' .. segment.source.lat) + end + + return { + setup = setup, + process_node = process_node, + process_way = process_way, + process_segment = process_segment, + process_turn = process_turn + } + """ + And the node map + """ + a + bcd + e + """ + And the ways + | nodes | + | ac | + | cb | + | cd | + | ce | + And the data has been saved to disk + + When I run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit successfully + And stdout should contain "process_node" + And stdout should contain "process_way" + And stdout should contain "process_turn" + And stdout should contain "process_segment" + + When I route I should get + | from | to | route | time | + | a | b | ac,cb,cb | 19.2s | + | a | d | ac,cd,cd | 19.2s | + | a | e | ac,ce | 20s | diff --git a/features/step_definitions/data.js b/features/step_definitions/data.js index 9ed80f4f337..d191d62b0d5 100644 --- a/features/step_definitions/data.js +++ b/features/step_definitions/data.js @@ -248,17 +248,20 @@ module.exports = function () { fs.writeFile(this.penaltiesCacheFile, data, callback); }); - this.Given(/^the profile file(?: "([^"]*)" extended with)?$/, (profile, data, callback) => { + this.Given(/^the profile file(?: "([^"]*)" initialized with)?$/, (profile, data, callback) => { const lua_profiles_path = this.PROFILES_PATH.split(path.sep).join('/'); let text = 'package.path = "' + lua_profiles_path + '/?.lua;" .. package.path\n'; if (profile == null) { text += data + '\n'; } else { - text += 'local f = assert(io.open("' + lua_profiles_path + '/' + profile + '.lua", "r"))\n'; - text += 'local s = f:read("*all") .. [[\n' + data + '\n]]\n'; - text += 'f:close()\n'; - text += 'local m = assert(loadstring and loadstring(s) or load(s))\n'; - text += 'm()\n'; + text += 'local functions = require("' + profile + '")\n'; + text += 'functions.setup_parent = functions.setup\n'; + text += 'functions.setup = function()\n'; + text += 'local profile = functions.setup_parent()\n'; + text += data + '\n'; + text += 'return profile\n'; + text += 'end\n'; + text += 'return functions\n'; } this.profileFile = this.profileCacheFile; // TODO: Don't overwrite if it exists diff --git a/features/testbot/distance_matrix.feature b/features/testbot/distance_matrix.feature index 83460f93c14..2dde7de2c5d 100644 --- a/features/testbot/distance_matrix.feature +++ b/features/testbot/distance_matrix.feature @@ -221,15 +221,24 @@ Feature: Basic Distance Matrix | 4 | 30 +-1 | 40 +-1 | 70 +-1 | 0 | Scenario: Testbot - Travel time matrix based on segment durations - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - function segment_function (segment) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.traffic_signal_penalty = 0 + profile.u_turn_penalty = 0 + return profile + end + + functions.process_segment = function(profile, segment) segment.weight = 2 segment.duration = 11 end + + return functions """ And the node map @@ -254,16 +263,25 @@ Feature: Basic Distance Matrix Scenario: Testbot - Travel time matrix for alternative loop paths - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_precision = 3 - function segment_function (segment) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.traffic_signal_penalty = 0 + profile.u_turn_penalty = 0 + profile.weight_precision = 3 + return profile + end + + functions.process_segment = function(profile, segment) segment.weight = 777 segment.duration = 3 end + + return functions """ And the node map """ diff --git a/features/testbot/nil.feature b/features/testbot/nil.feature index 6d84a0909ed..af8a0b9883a 100644 --- a/features/testbot/nil.feature +++ b/features/testbot/nil.feature @@ -1,29 +1,27 @@ @routing @testbot @nil Feature: Testbot - Check assigning nil values Scenario: Assign nil values to all way strings - Given the profile file "testbot" extended with + Given the profile file """ - function way_function (way, result) - result.name = "name" - result.ref = "ref" - result.destinations = "destinations" - result.pronunciation = "pronunciation" - result.turn_lanes_forward = "turn_lanes_forward" - result.turn_lanes_backward = "turn_lanes_backward" + functions = require('testbot') - result.name = nil - result.ref = nil - result.destinations = nil - result.exits = nil - result.pronunciation = nil - result.turn_lanes_forward = nil - result.turn_lanes_backward = nil + function way_function(profile, way, result) + result.name = nil + result.ref = nil + result.destinations = nil + result.exits = nil + result.pronunciation = nil + result.turn_lanes_forward = nil + result.turn_lanes_backward = nil - result.forward_speed = 10 - result.backward_speed = 10 - result.forward_mode = mode.driving - result.backward_mode = mode.driving + result.forward_speed = 10 + result.backward_speed = 10 + result.forward_mode = mode.driving + result.backward_mode = mode.driving end + + functions.process_way = way_function + return functions """ Given the node map """ diff --git a/features/testbot/projection.feature b/features/testbot/projection.feature index 805ddc233c5..49a5d8df18e 100644 --- a/features/testbot/projection.feature +++ b/features/testbot/projection.feature @@ -39,13 +39,17 @@ Feature: Projection to nearest point on road Scenario: Projection results negative duration - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - function segment_function (segment) + functions = require('testbot') + + function segment_function(profile, segment) segment.weight = 5.5 segment.duration = 2.8 end + + functions.process_segment = segment_function + return functions """ Given the node locations diff --git a/features/testbot/traffic_speeds.feature b/features/testbot/traffic_speeds.feature index 0030ec19c39..d89c8134372 100644 --- a/features/testbot/traffic_speeds.feature +++ b/features/testbot/traffic_speeds.feature @@ -84,12 +84,11 @@ Feature: Traffic - speeds Scenario: Weighting based on speed file weights, ETA based on file durations - Given the profile file "testbot" extended with + Given the profile file "testbot" initialized with """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_precision = 2 + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_precision = 2 """ And the contract extra arguments "--segment-speed-file {speeds_file}" And the customize extra arguments "--segment-speed-file {speeds_file}" diff --git a/features/testbot/traffic_speeds_overflow.feature b/features/testbot/traffic_speeds_overflow.feature index f747b85fbaf..3885b524308 100644 --- a/features/testbot/traffic_speeds_overflow.feature +++ b/features/testbot/traffic_speeds_overflow.feature @@ -8,12 +8,11 @@ Feature: Traffic - speeds edge cases And the ways | nodes | highway | | ab | primary | - And the profile file "testbot" extended with + And the profile file "testbot" initialized with """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_precision = 2 + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_precision = 2 """ And the contract extra arguments "--segment-speed-file {speeds_file}" And the customize extra arguments "--segment-speed-file {speeds_file}" diff --git a/features/testbot/turn_penalty.feature b/features/testbot/turn_penalty.feature index bd6604e31f8..b5461e18a33 100644 --- a/features/testbot/turn_penalty.feature +++ b/features/testbot/turn_penalty.feature @@ -1,12 +1,9 @@ @routing @testbot @turn_penalty Feature: Turn Penalties - Background: + Scenario: Turns should incur a delay that depend on the angle Given the profile "turnbot" Given a grid size of 200 meters - - - Scenario: Turns should incur a delay that depend on the angle Given the node map """ c d e diff --git a/features/testbot/weight.feature b/features/testbot/weight.feature index 17367f38fc8..686b0dc17e6 100644 --- a/features/testbot/weight.feature +++ b/features/testbot/weight.feature @@ -60,18 +60,27 @@ Feature: Weight tests Scenario: Step weights -- way_function: fail if no weight or weight_per_meter property - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_name = 'steps' - function way_function(way, result) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_name = 'steps' + return profile + end + + functions.process_way = function(profile, way, result) result.forward_mode = mode.driving result.backward_mode = mode.driving result.forward_speed = 42 result.backward_speed = 42 end + + return functions """ And the node map """ @@ -87,18 +96,27 @@ Feature: Weight tests And it should exit with an error Scenario: Step weights -- way_function: second way wins - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_name = 'steps' - function way_function(way, result) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_name = 'steps' + return profile + end + + functions.process_way = function(profile, way, result) result.forward_mode = mode.driving result.backward_mode = mode.driving result.duration = 42 result.weight = 35 end + + return functions """ Given the node map @@ -119,19 +137,28 @@ Feature: Weight tests | h,a | , | 140m +-1 | 35,0 | 42s,0s | Scenario: Step weights -- way_function: higher weight_per_meter is preferred - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_name = 'steps' - function way_function(way, result) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_name = 'steps' + return profile + end + + functions.process_way = function(profile, way, result) result.forward_mode = mode.driving result.backward_mode = mode.driving result.duration = 42 result.forward_rate = 1 result.backward_rate = 0.5 end + + return functions """ Given the node map @@ -155,22 +182,32 @@ Feature: Weight tests | h,f | , | 40m | 80,0 | 12s,0s | Scenario: Step weights -- segment_function - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_name = 'steps' - function way_function(way, result) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_name = 'steps' + return profile + end + + functions.process_way = function(profile, way, result) result.forward_mode = mode.driving result.backward_mode = mode.driving result.weight = 42 result.duration = 3 end - function segment_function (segment) + + functions.process_segment = function(profile, segment) segment.weight = 1 segment.duration = 11 end + + return functions """ Given the node map @@ -195,28 +232,39 @@ Feature: Weight tests Scenario: Step weights -- segment_function and turn_function with weight precision - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_name = 'steps' - properties.weight_precision = 3 - function way_function(way, result) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_name = 'steps' + profile.properties.weight_precision = 3 + return profile + end + + functions.process_way = function(profile, way, result) result.forward_mode = mode.driving result.backward_mode = mode.driving result.weight = 42 result.duration = 3 end - function segment_function (segment) + + functions.process_segment = function(profile, segment) segment.weight = 1.11 segment.duration = 100 end - function turn_function (turn) + + functions.process_turn = function(profile, turn) print (turn.angle) turn.weight = 2 + turn.angle / 100 turn.duration = turn.angle end + + return functions """ Given the node map @@ -241,22 +289,32 @@ Feature: Weight tests @traffic @speed Scenario: Step weights -- segment_function with speed and turn updates - Given the profile file "testbot" extended with + Given the profile file """ - api_version = 1 - properties.traffic_signal_penalty = 0 - properties.u_turn_penalty = 0 - properties.weight_name = 'steps' - function way_function(way, result) + local functions = require('testbot') + functions.setup_testbot = functions.setup + + functions.setup = function() + local profile = functions.setup_testbot() + profile.properties.traffic_signal_penalty = 0 + profile.properties.u_turn_penalty = 0 + profile.properties.weight_name = 'steps' + return profile + end + + functions.process_way = function(profile, way, result) result.forward_mode = mode.driving result.backward_mode = mode.driving result.weight = 42 result.duration = 3 end - function segment_function (segment) + + functions.process_segment = function(profile, segment) segment.weight = 10 segment.duration = 11 end + + return functions """ And the node map @@ -289,10 +347,9 @@ Feature: Weight tests @traffic @speed Scenario: Step weights -- segment_function with speed and turn updates with fallback to durations - Given the profile file "testbot" extended with + Given the profile file "testbot" initialized with """ - api_version = 1 - properties.weight_precision = 3 + profile.properties.weight_precision = 3 """ And the node map diff --git a/include/extractor/raster_source.hpp b/include/extractor/raster_source.hpp index 1c0473a35f7..57a082d0e40 100644 --- a/include/extractor/raster_source.hpp +++ b/include/extractor/raster_source.hpp @@ -125,10 +125,10 @@ class RasterSource int _ymax); }; -class SourceContainer +class RasterContainer { public: - SourceContainer() = default; + RasterContainer() = default; int LoadRasterSource(const std::string &path_string, double xmin, diff --git a/include/extractor/scripting_environment.hpp b/include/extractor/scripting_environment.hpp index 58ba319fd92..585422fc886 100644 --- a/include/extractor/scripting_environment.hpp +++ b/include/extractor/scripting_environment.hpp @@ -54,7 +54,6 @@ class ScriptingEnvironment virtual std::vector GetNameSuffixList() = 0; virtual std::vector GetRestrictions() = 0; - virtual void SetupSources() = 0; virtual void ProcessTurn(ExtractionTurn &turn) = 0; virtual void ProcessSegment(ExtractionSegment &segment) = 0; diff --git a/include/extractor/scripting_environment_lua.hpp b/include/extractor/scripting_environment_lua.hpp index 42c53084edf..af91b2a69ab 100644 --- a/include/extractor/scripting_environment_lua.hpp +++ b/include/extractor/scripting_environment_lua.hpp @@ -23,7 +23,7 @@ struct LuaScriptingContext final void ProcessWay(const osmium::Way &, ExtractionWay &result); ProfileProperties properties; - SourceContainer sources; + RasterContainer raster_sources; sol::state state; bool has_turn_penalty_function; @@ -37,6 +37,7 @@ struct LuaScriptingContext final sol::function segment_function; int api_version; + sol::table profile_table; }; /** @@ -50,7 +51,7 @@ class Sol2ScriptingEnvironment final : public ScriptingEnvironment { public: static const constexpr int SUPPORTED_MIN_API_VERSION = 0; - static const constexpr int SUPPORTED_MAX_API_VERSION = 1; + static const constexpr int SUPPORTED_MAX_API_VERSION = 2; explicit Sol2ScriptingEnvironment(const std::string &file_name); ~Sol2ScriptingEnvironment() override = default; @@ -59,9 +60,10 @@ class Sol2ScriptingEnvironment final : public ScriptingEnvironment LuaScriptingContext &GetSol2Context(); + std::vector GetStringListFromTable(const std::string &table_name); + std::vector GetStringListFromFunction(const std::string &function_name); std::vector GetNameSuffixList() override; std::vector GetRestrictions() override; - void SetupSources() override; void ProcessTurn(ExtractionTurn &turn) override; void ProcessSegment(ExtractionSegment &segment) override; diff --git a/profiles/bicycle.lua b/profiles/bicycle.lua index 4fe77950127..ae81284b93a 100644 --- a/profiles/bicycle.lua +++ b/profiles/bicycle.lua @@ -1,199 +1,196 @@ -api_version = 1 - -- Bicycle profile -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 -local limit = require("lib/maxspeed").limit - --- these need to be global because they are accesed externaly -properties.max_speed_for_map_matching = 110/3.6 -- kmph -> m/s -properties.use_turn_restrictions = false -properties.continue_straight_at_waypoint = false -properties.weight_name = 'duration' ---properties.weight_name = 'cyclability' - --- Set to true if you need to call the node_function for every node. --- Generally can be left as false to avoid unnecessary Lua calls --- (which slow down pre-processing). -properties.call_tagless_node_function = false - - -local default_speed = 15 -local walking_speed = 6 - -local profile = { - default_mode = mode.cycling, - default_speed = 15, - oneway_handling = true, - traffic_light_penalty = 2, - u_turn_penalty = 20, - turn_penalty = 6, - turn_bias = 1.4, - use_public_transport = true, - - allowed_start_modes = Set { - mode.cycling, - mode.pushing_bike - }, - - barrier_whitelist = Set { - 'sump_buster', - 'bus_trap', - 'cycle_barrier', - 'bollard', - 'entrance', - 'cattle_grid', - 'border_control', - 'toll_booth', - 'sally_port', - 'gate', - 'no', - 'block' - }, - - access_tag_whitelist = Set { - 'yes', - 'permissive', - 'designated' - }, - - access_tag_blacklist = Set { - 'no', - 'private', - 'agricultural', - 'forestry', - 'delivery' - }, - - restricted_access_tag_list = Set { }, - - restricted_highway_whitelist = Set { }, - - access_tags_hierarchy = Sequence { - 'bicycle', - 'vehicle', - 'access' - }, - - restrictions = Set { - 'bicycle' - }, - - cycleway_tags = Set { - 'track', - 'lane', - 'opposite', - 'opposite_lane', - 'opposite_track', - 'share_busway', - 'sharrow', - 'shared', - 'shared_lane' - }, - - -- reduce the driving speed by 30% for unsafe roads - -- only used for cyclability metric - unsafe_highway_list = { - primary = 0.7, - secondary = 0.75, - tertiary = 0.8, - primary_link = 0.7, - secondary_link = 0.75, - tertiary_link = 0.8, - }, - - service_penalties = { - alley = 0.5, - }, - - bicycle_speeds = { - cycleway = default_speed, - primary = default_speed, - primary_link = default_speed, - secondary = default_speed, - secondary_link = default_speed, - tertiary = default_speed, - tertiary_link = default_speed, - residential = default_speed, - unclassified = default_speed, - living_street = default_speed, - road = default_speed, - service = default_speed, - track = 12, - path = 12 - }, - - pedestrian_speeds = { - footway = walking_speed, - pedestrian = walking_speed, - steps = 2 - }, - - railway_speeds = { - train = 10, - railway = 10, - subway = 10, - light_rail = 10, - monorail = 10, - tram = 10 - }, - - platform_speeds = { - platform = walking_speed - }, - - amenity_speeds = { - parking = 10, - parking_entrance = 10 - }, - - man_made_speeds = { - pier = walking_speed - }, - - route_speeds = { - ferry = 5 - }, - - bridge_speeds = { - movable = 5 - }, - - surface_speeds = { - asphalt = default_speed, - ["cobblestone:flattened"] = 10, - paving_stones = 10, - compacted = 10, - cobblestone = 6, - unpaved = 6, - fine_gravel = 6, - gravel = 6, - pebblestone = 6, - ground = 6, - dirt = 6, - earth = 6, - grass = 6, - mud = 3, - sand = 3, - sett = 10 - }, - - tracktype_speeds = { - }, - - smoothness_speeds = { - }, - - avoid = Set { - 'impassable', - 'construction', - 'proposed' - } -} +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 +limit = require("lib/maxspeed").limit + +function setup() + local default_speed = 15 + local walking_speed = 6 + + return { + properties = { + u_turn_penalty = 20, + traffic_light_penalty = 2, + --weight_name = 'cyclability', + weight_name = 'duration', + process_call_tagless_node = false, + max_speed_for_map_matching = 110/3.6, -- kmph -> m/s + use_turn_restrictions = false, + continue_straight_at_waypoint = false + }, + + default_mode = mode.cycling, + default_speed = default_speed, + walking_speed = walking_speed, + oneway_handling = true, + turn_penalty = 6, + turn_bias = 1.4, + use_public_transport = true, + + allowed_start_modes = Set { + mode.cycling, + mode.pushing_bike + }, + + barrier_whitelist = Set { + 'sump_buster', + 'bus_trap', + 'cycle_barrier', + 'bollard', + 'entrance', + 'cattle_grid', + 'border_control', + 'toll_booth', + 'sally_port', + 'gate', + 'no', + 'block' + }, + + access_tag_whitelist = Set { + 'yes', + 'permissive', + 'designated' + }, + + access_tag_blacklist = Set { + 'no', + 'private', + 'agricultural', + 'forestry', + 'delivery' + }, + + restricted_access_tag_list = Set { }, + + restricted_highway_whitelist = Set { }, + + access_tags_hierarchy = Sequence { + 'bicycle', + 'vehicle', + 'access' + }, + + restrictions = Set { + 'bicycle' + }, + + cycleway_tags = Set { + 'track', + 'lane', + 'opposite', + 'opposite_lane', + 'opposite_track', + 'share_busway', + 'sharrow', + 'shared', + 'shared_lane' + }, + + -- reduce the driving speed by 30% for unsafe roads + -- only used for cyclability metric + unsafe_highway_list = { + primary = 0.7, + secondary = 0.75, + tertiary = 0.8, + primary_link = 0.7, + secondary_link = 0.75, + tertiary_link = 0.8, + }, + + service_penalties = { + alley = 0.5, + }, + + bicycle_speeds = { + cycleway = default_speed, + primary = default_speed, + primary_link = default_speed, + secondary = default_speed, + secondary_link = default_speed, + tertiary = default_speed, + tertiary_link = default_speed, + residential = default_speed, + unclassified = default_speed, + living_street = default_speed, + road = default_speed, + service = default_speed, + track = 12, + path = 12 + }, + + pedestrian_speeds = { + footway = walking_speed, + pedestrian = walking_speed, + steps = 2 + }, + + railway_speeds = { + train = 10, + railway = 10, + subway = 10, + light_rail = 10, + monorail = 10, + tram = 10 + }, + + platform_speeds = { + platform = walking_speed + }, + + amenity_speeds = { + parking = 10, + parking_entrance = 10 + }, + + man_made_speeds = { + pier = walking_speed + }, + + route_speeds = { + ferry = 5 + }, + + bridge_speeds = { + movable = 5 + }, + + surface_speeds = { + asphalt = default_speed, + ["cobblestone:flattened"] = 10, + paving_stones = 10, + compacted = 10, + cobblestone = 6, + unpaved = 6, + fine_gravel = 6, + gravel = 6, + pebblestone = 6, + ground = 6, + dirt = 6, + earth = 6, + grass = 6, + mud = 3, + sand = 3, + sett = 10 + }, + + tracktype_speeds = { + }, + + smoothness_speeds = { + }, + + avoid = Set { + 'impassable', + 'construction' + } + } +end local function parse_maxspeed(source) if not source then @@ -209,13 +206,7 @@ local function parse_maxspeed(source) return n end -function get_restrictions(vector) - for i,v in ipairs(profile.restrictions) do - vector:Add(v) - end -end - -function node_function (node, result) +function process_node (profile, node, result) -- parse access and barrier tags local highway = node:get_value_by_key("highway") local is_crossing = highway and highway == "crossing" @@ -243,41 +234,8 @@ function node_function (node, result) 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'), - } - - 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 - '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', - } - - if Handlers.run(handlers,way,result,data,profile) == false then - return - end - - -- initial routability check, filters out buildings, boundaries, etc +function handle_bicycle_tags(profile,way,result,data) + -- initial routability check, filters out buildings, boundaries, etc local route = way:get_value_by_key("route") local man_made = way:get_value_by_key("man_made") local railway = way:get_value_by_key("railway") @@ -293,13 +251,13 @@ function way_function (way, result) (not public_transport or public_transport=='') and (not bridge or bridge=='') then - return + return false end -- access local access = find_access_tag(way, profile.access_tags_hierarchy) if access and profile.access_tag_blacklist[access] then - return + return false end -- other tags @@ -371,8 +329,8 @@ function way_function (way, result) way_type_allows_pushing = true elseif access and profile.access_tag_whitelist[access] then -- unknown way, but valid access tag - result.forward_speed = default_speed - result.backward_speed = default_speed + result.forward_speed = profile.default_speed + result.backward_speed = profile.default_speed way_type_allows_pushing = true end @@ -447,18 +405,18 @@ function way_function (way, result) push_backward_speed = profile.man_made_speeds[man_made] else if foot == 'yes' then - push_forward_speed = walking_speed + push_forward_speed = profile.walking_speed if not implied_oneway then - push_backward_speed = walking_speed + push_backward_speed = profile.walking_speed end elseif foot_forward == 'yes' then - push_forward_speed = walking_speed + push_forward_speed = profile.walking_speed elseif foot_backward == 'yes' then - push_backward_speed = walking_speed + push_backward_speed = profile.walking_speed elseif way_type_allows_pushing then - push_forward_speed = walking_speed + push_forward_speed = profile.walking_speed if not implied_oneway then - push_backward_speed = walking_speed + push_backward_speed = profile.walking_speed end end end @@ -481,8 +439,8 @@ function way_function (way, result) if bicycle == "dismount" then result.forward_mode = mode.pushing_bike result.backward_mode = mode.pushing_bike - result.forward_speed = walking_speed - result.backward_speed = walking_speed + result.forward_speed = profile.walking_speed + result.backward_speed = profile.walking_speed end @@ -502,7 +460,7 @@ function way_function (way, result) -- convert duration into cyclability - if properties.weight_name == 'cyclability' then + if profile.properties.weight_name == 'cyclability' then local safety_penalty = profile.unsafe_highway_list[data.highway] or 1. local is_unsafe = safety_penalty < 1 local forward_is_unsafe = is_unsafe and not has_cycleway_right @@ -534,29 +492,59 @@ function way_function (way, result) result.weight = result.duration / forward_penalty end end +end +function process_way (profile, way, result) + -- the initial filtering of ways based on presence of tags + -- affects processing times significantly, because all ways + -- have to be checked. + -- to increase performance, prefetching and initial tag check + -- is done 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'), + } 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, + + -- our main handler + handle_bicycle_tags, + -- compute speed taking into account way type, maxspeed tags, etc. - 'handle_surface', + WayHandlers.surface, -- handle turn lanes and road classification, used for guidance - 'handle_classification', - - -- handle various other flags - 'handle_roundabouts', + WayHandlers.classification, -- handle allowed start/end modes - 'handle_startpoint', + WayHandlers.startpoint, + + -- handle roundabouts + WayHandlers.roundabouts, -- set name, ref and pronunciation - 'handle_names' + WayHandlers.names } - Handlers.run(handlers,way,result,data,profile) - + WayHandlers.run(profile,way,result,data,handlers) end -function turn_function(turn) +function process_turn(profile, turn) -- compute turn penalty as angle^2, with a left/right bias local normalized_angle = turn.angle / 90.0 if normalized_angle >= 0.0 then @@ -566,17 +554,20 @@ function turn_function(turn) end if turn.direction_modifier == direction_modifier.uturn then - turn.duration = turn.duration + profile.u_turn_penalty + turn.duration = turn.duration + profile.properties.u_turn_penalty end if turn.has_traffic_light then - turn.duration = turn.duration + profile.traffic_light_penalty + turn.duration = turn.duration + profile.properties.traffic_light_penalty end - if properties.weight_name == 'cyclability' then - turn.weight = turn.duration - -- 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 + if profile.properties.weight_name == 'cyclability' then + turn.weight = turn.duration end end + +return { + setup = setup, + process_way = process_way, + process_node = process_node, + process_turn = process_turn +} diff --git a/profiles/car.lua b/profiles/car.lua index 7418f732fbe..ceb26f498bd 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -1,282 +1,268 @@ -- 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 - --- set profile properties -properties.max_speed_for_map_matching = 180/3.6 -- 180kmph -> m/s -properties.use_turn_restrictions = true -properties.continue_straight_at_waypoint = true -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' - --- Set to true if you need to call the node_function for every node. --- Generally can be left as false to avoid unnecessary Lua calls --- (which slow down pre-processing). -properties.call_tagless_node_function = false - - -local profile = { - default_mode = mode.driving, - default_speed = 10, - oneway_handling = true, - - side_road_multiplier = 0.8, - turn_penalty = 7.5, - speed_reduction = 0.8, - traffic_light_penalty = 2, - u_turn_penalty = 20, - - -- 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', - 'motorcar', - 'motor_vehicle', - 'vehicle', - 'permissive', - 'designated', - 'hov' - }, - - access_tag_blacklist = Set { - 'no', - 'agricultural', - 'forestry', - 'emergency', - 'psv', - 'customers', - 'private', - 'delivery', - 'destination' - }, - - 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' - }, - - 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 +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 +limit = require("lib/maxspeed").limit + +function setup() + local use_left_hand_driving = false + return { + properties = { + max_speed_for_map_matching = 180/3.6, -- 180kmph -> m/s + left_hand_driving = use_left_hand_driving, + -- 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, + 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, + + -- Note: this biases right-side driving. + -- Should be inverted for left-driving countries. + turn_bias = use_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', + 'motorcar', + 'motor_vehicle', + 'vehicle', + 'permissive', + 'designated', + 'hov' + }, + + access_tag_blacklist = Set { + 'no', + 'agricultural', + 'forestry', + 'emergency', + 'psv', + 'customers', + 'private', + 'delivery', + 'destination' + }, + + 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' + }, + + 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', + }, + + 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 = { + ["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 } - }, - - 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', - }, - - 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 = { - ["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) +function process_node (profile, node, result) -- parse access and barrier tags local access = find_access_tag(node, profile.access_tags_hierarchy) if access then @@ -303,7 +289,7 @@ function node_function (node, result) end end -function way_function(way, result) +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. @@ -335,61 +321,61 @@ function way_function(way, result) 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', + 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 - 'handle_blocked_ways', + WayHandlers.blocked_ways, -- determine access status by checking our hierarchy of -- access tags, e.g: motorcar, motor_vehicle, vehicle - 'handle_access', + WayHandlers.access, -- check whether forward/backward directions are routable - 'handle_oneway', + WayHandlers.oneway, -- check a road's destination - 'handle_destinations', + WayHandlers.destinations, -- check whether we're using a special transport mode - 'handle_ferries', - 'handle_movables', + WayHandlers.ferries, + WayHandlers.movables, -- handle service road restrictions - 'handle_service', + WayHandlers.service, -- handle hov - 'handle_hov', + WayHandlers.hov, -- compute speed taking into account way type, maxspeed tags, etc. - 'handle_speed', - 'handle_surface', - 'handle_maxspeed', - 'handle_penalties', + WayHandlers.speed, + WayHandlers.surface, + WayHandlers.maxspeed, + WayHandlers.penalties, -- compute class labels - 'handle_classes', + WayHandlers.classes, -- handle turn lanes and road classification, used for guidance - 'handle_turn_lanes', - 'handle_classification', + WayHandlers.turn_lanes, + WayHandlers.classification, -- handle various other flags - 'handle_roundabouts', - 'handle_startpoint', + WayHandlers.roundabouts, + WayHandlers.startpoint, -- set name, ref and pronunciation - 'handle_names', + WayHandlers.names, -- set weight properties of the way - 'handle_weights' + WayHandlers.weights } - Handlers.run(handlers,way,result,data,profile) + WayHandlers.run(profile,way,result,data,handlers) end -function turn_function (turn) +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. @@ -397,7 +383,7 @@ function turn_function (turn) local turn_bias = profile.turn_bias if turn.has_traffic_light then - turn.duration = profile.traffic_light_penalty + turn.duration = profile.properties.traffic_light_penalty end if turn.turn_type ~= turn_type.no_turn then @@ -408,21 +394,28 @@ function turn_function (turn) end if turn.direction_modifier == direction_modifier.u_turn then - turn.duration = turn.duration + profile.u_turn_penalty + 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 properties.weight_name == 'distance' then + if profile.properties.weight_name == 'distance' then turn.weight = 0 else turn.weight = turn.duration end - - if properties.weight_name == 'routability' then + + 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 = properties.max_turn_weight; + turn.weight = constants.max_turn_weight end end end + +return { + setup = setup, + process_way = process_way, + process_node = process_node, + process_turn = process_turn +} diff --git a/profiles/foot.lua b/profiles/foot.lua index c715b0c2ae7..b5633b55aff 100644 --- a/profiles/foot.lua +++ b/profiles/foot.lua @@ -1,149 +1,143 @@ -- Foot 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 - -properties.max_speed_for_map_matching = 40/3.6 -- kmph -> m/s -properties.use_turn_restrictions = false -properties.continue_straight_at_waypoint = false -properties.weight_name = 'duration' ---properties.weight_name = 'routability' - --- Set to true if you need to call the node_function for every node. --- Generally can be left as false to avoid unnecessary Lua calls --- (which slow down pre-processing). -properties.call_tagless_node_function = false - -local walking_speed = 5 - -local profile = { - default_mode = mode.walking, - default_speed = walking_speed, - oneway_handling = 'specific', -- respect 'oneway:foot' but not 'oneway' - traffic_light_penalty = 2, - u_turn_penalty = 2, - - barrier_whitelist = Set { - 'cycle_barrier', - 'bollard', - 'entrance', - 'cattle_grid', - 'border_control', - 'toll_booth', - 'sally_port', - 'gate', - 'no', - 'kerb', - 'block' - }, - - 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 { }, - - access_tags_hierarchy = Sequence { - 'foot', - 'access' - }, - - 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', - 'construction', - '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, - footway = walking_speed, - pier = walking_speed, +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_light_penalty = 2, + u_turn_penalty = 2, + continue_straight_at_waypoint = false, + use_turn_restrictions = false, }, - railway = { - platform = walking_speed + default_mode = mode.walking, + default_speed = walking_speed, + oneway_handling = 'specific', -- respect 'oneway:foot' but not 'oneway' + + barrier_whitelist = Set { + 'cycle_barrier', + 'bollard', + 'entrance', + 'cattle_grid', + 'border_control', + 'toll_booth', + 'sally_port', + 'gate', + 'no', + 'kerb', + 'block' }, - amenity = { - parking = walking_speed, - parking_entrance= walking_speed + access_tag_whitelist = Set { + 'yes', + 'foot', + 'permissive', + 'designated' }, - man_made = { - pier = walking_speed + access_tag_blacklist = Set { + 'no', + 'agricultural', + 'forestry', + 'private', + 'delivery', }, - leisure = { - track = walking_speed - } - }, + restricted_access_tag_list = Set { }, - route_speeds = { - ferry = 5 - }, + restricted_highway_whitelist = Set { }, - bridge_speeds = { - }, + access_tags_hierarchy = Sequence { + 'foot', + 'access' + }, - 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 - }, + restrictions = Sequence { + 'foot' + }, - tracktype_speeds = { - }, + -- list of suffixes to suppress in name change instructions + suffix_list = Set { + 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East' + }, - smoothness_speeds = { - } -} + avoid = Set { + 'impassable' + }, + + 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, + 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 + }, -function node_function (node, result) + 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 @@ -171,7 +165,7 @@ function node_function (node, result) end -- main entry point for processsing a way -function way_function(way, result) +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. @@ -208,59 +202,66 @@ function way_function(way, result) 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 - 'handle_default_mode', + 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 - 'handle_blocked_ways', + WayHandlers.blocked_ways, -- determine access status by checking our hierarchy of -- access tags, e.g: motorcar, motor_vehicle, vehicle - 'handle_access', + WayHandlers.access, -- check whether forward/backward directons are routable - 'handle_oneway', + WayHandlers.oneway, -- check whether forward/backward directons are routable - 'handle_destinations', + WayHandlers.destinations, -- check whether we're using a special transport mode - 'handle_ferries', - 'handle_movables', + WayHandlers.ferries, + WayHandlers.movables, -- compute speed taking into account way type, maxspeed tags, etc. - 'handle_speed', - 'handle_surface', + WayHandlers.speed, + WayHandlers.surface, -- handle turn lanes and road classification, used for guidance - 'handle_classification', + WayHandlers.classification, -- handle various other flags - 'handle_roundabouts', - 'handle_startpoint', + WayHandlers.roundabouts, + WayHandlers.startpoint, -- set name, ref and pronunciation - 'handle_names' + WayHandlers.names } - Handlers.run(handlers,way,result,data,profile) + WayHandlers.run(profile,way,result,data,handlers) end -function turn_function (turn) +function process_turn (profile, turn) turn.duration = 0. if turn.direction_modifier == direction_modifier.u_turn then - turn.duration = turn.duration + profile.u_turn_penalty + turn.duration = turn.duration + profile.properties.u_turn_penalty end if turn.has_traffic_light then - turn.duration = profile.traffic_light_penalty + turn.duration = profile.properties.traffic_light_penalty end - if properties.weight_name == 'routability' then + 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 +} diff --git a/profiles/lib/profile_debugger.lua b/profiles/lib/profile_debugger.lua index 1f5b150dd1c..6f5d49dde0c 100644 --- a/profiles/lib/profile_debugger.lua +++ b/profiles/lib/profile_debugger.lua @@ -90,7 +90,8 @@ function Debug.report_tag_fetches() end function Debug.load_profile(profile) - require(profile) + Debug.functions = require(profile) + Debug.profile = Debug.functions.setup() end function Debug.reset_tag_fetch_counts() @@ -115,7 +116,7 @@ function Debug.register_tag_fetch(k) end -function Debug.way_function(way,result) +function Debug.process_way(way,result) -- setup result table result.road_classification = {} @@ -132,8 +133,8 @@ function Debug.way_function(way,result) -- reset tag counts Debug:reset_tag_fetch_counts() - -- call the global method defined in the profile file - way_function(way,result) + -- call the way processsing function + Debug.functions.process_way(Debug.profile,way,result) end return Debug diff --git a/profiles/lib/sequence.lua b/profiles/lib/sequence.lua index c1fba283dbe..9cac7884a36 100644 --- a/profiles/lib/sequence.lua +++ b/profiles/lib/sequence.lua @@ -1,7 +1,7 @@ -- Sequence of items -- Ordered, but have to loop through items to check for inclusion. -- Currently the same as a table. - +-- Adds the convenience function append() to append to the sequnce. function Sequence(source) return source diff --git a/profiles/lib/handlers.lua b/profiles/lib/way_handlers.lua similarity index 88% rename from profiles/lib/handlers.lua rename to profiles/lib/way_handlers.lua index 28b2981da5a..61ebefd8b23 100644 --- a/profiles/lib/handlers.lua +++ b/profiles/lib/way_handlers.lua @@ -9,25 +9,26 @@ local set_classification = require("lib/guidance").set_classification local get_destination = require("lib/destination").get_destination local Tags = require('lib/tags') -Handlers = {} +WayHandlers = {} -- 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) +function WayHandlers.tag_prefetch(profile,way,result,data) 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) +function WayHandlers.default_mode(profile,way,result,data) 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) +function WayHandlers.names(profile,way,result,data) -- parse the remaining tags local name = way:get_value_by_key("name") local pronunciation = way:get_value_by_key("name:pronunciation") @@ -53,7 +54,7 @@ function Handlers.handle_names(way,result,data,profile) end -- junctions -function Handlers.handle_roundabouts(way,result,data,profile) +function WayHandlers.roundabouts(profile,way,result,data) local junction = way:get_value_by_key("junction"); if junction == "roundabout" then @@ -69,7 +70,7 @@ function Handlers.handle_roundabouts(way,result,data,profile) end -- determine if this way can be used as a start/end point for routing -function Handlers.handle_startpoint(way,result,data,profile) +function WayHandlers.startpoint(profile,way,result,data) -- if profile specifies set of allowed start modes, then check for that -- otherwise require default mode if profile.allowed_start_modes then @@ -82,7 +83,7 @@ function Handlers.handle_startpoint(way,result,data,profile) end -- handle turn lanes -function Handlers.handle_turn_lanes(way,result,data,profile) +function WayHandlers.turn_lanes(profile,way,result,data) local forward, backward = get_turn_lanes(way,data) if forward then @@ -95,12 +96,12 @@ function Handlers.handle_turn_lanes(way,result,data,profile) end -- set the road classification based on guidance globals configuration -function Handlers.handle_classification(way,result,data,profile) +function WayHandlers.classification(profile,way,result,data) set_classification(data.highway,result,way) end -- handle destination tags -function Handlers.handle_destinations(way,result,data,profile) +function WayHandlers.destinations(profile,way,result,data) if data.is_forward_oneway or data.is_reverse_oneway then local destination = get_destination(way, data.is_forward_oneway) result.destinations = canonicalizeStringList(destination, ",") @@ -108,7 +109,7 @@ function Handlers.handle_destinations(way,result,data,profile) end -- handling ferries and piers -function Handlers.handle_ferries(way,result,data,profile) +function WayHandlers.ferries(profile,way,result,data) local route = data.route if route then local route_speed = profile.route_speeds[route] @@ -126,7 +127,7 @@ function Handlers.handle_ferries(way,result,data,profile) end -- handling movable bridges -function Handlers.handle_movables(way,result,data,profile) +function WayHandlers.movables(profile,way,result,data) local bridge = data.bridge if bridge then local bridge_speed = profile.bridge_speeds[bridge] @@ -148,7 +149,7 @@ function Handlers.handle_movables(way,result,data,profile) end -- service roads -function Handlers.handle_service(way,result,data,profile) +function WayHandlers.service(profile,way,result,data) local service = way:get_value_by_key("service") if service then -- Set don't allow access to certain service roads @@ -161,7 +162,7 @@ function Handlers.handle_service(way,result,data,profile) end -- all lanes restricted to hov vehicles? -function Handlers.has_all_designated_hov_lanes(lanes) +function WayHandlers.has_all_designated_hov_lanes(lanes) if not lanes then return false end @@ -176,7 +177,7 @@ function Handlers.has_all_designated_hov_lanes(lanes) end -- handle high occupancy vehicle tags -function Handlers.handle_hov(way,result,data,profile) +function WayHandlers.hov(profile,way,result,data) -- respect user-preference for HOV if not profile.avoid.hov_lanes then return @@ -189,11 +190,11 @@ function Handlers.handle_hov(way,result,data,profile) 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) + local all_hov_forward = WayHandlers.has_all_designated_hov_lanes(data.hov_lanes_forward) + local all_hov_backward = WayHandlers.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 profile.properties.weight_name == 'routability' then if (all_hov_forward) then result.forward_restricted = true end @@ -213,7 +214,7 @@ function Handlers.handle_hov(way,result,data,profile) end -- check accessibility by traversing our access tag hierarchy -function Handlers.handle_access(way,result,data,profile) +function WayHandlers.access(profile,way,result,data) data.forward_access, data.backward_access = Tags.get_forward_backward_by_set(way,data,profile.access_tags_hierarchy) @@ -242,7 +243,7 @@ function Handlers.handle_access(way,result,data,profile) end -- handle speed (excluding maxspeed) -function Handlers.handle_speed(way,result,data,profile) +function WayHandlers.speed(profile,way,result,data) if result.forward_speed ~= -1 then return -- abort if already set, eg. by a route end @@ -278,7 +279,7 @@ function Handlers.handle_speed(way,result,data,profile) end -- add class information -function Handlers.handle_classes(way,result,data,profile) +function WayHandlers.classes(profile,way,result,data) local forward_toll, backward_toll = Tags.get_forward_backward_by_key(way, data, "toll") local forward_route, backward_route = Tags.get_forward_backward_by_key(way, data, "route") @@ -310,7 +311,7 @@ function Handlers.handle_classes(way,result,data,profile) end -- reduce speed on bad surfaces -function Handlers.handle_surface(way,result,data,profile) +function WayHandlers.surface(profile,way,result,data) local surface = way:get_value_by_key("surface") local tracktype = way:get_value_by_key("tracktype") local smoothness = way:get_value_by_key("smoothness") @@ -330,7 +331,7 @@ function Handlers.handle_surface(way,result,data,profile) end -- scale speeds to get better average driving times -function Handlers.handle_penalties(way,result,data,profile) +function WayHandlers.penalties(profile,way,result,data) -- 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 @@ -375,7 +376,7 @@ function Handlers.handle_penalties(way,result,data,profile) 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 profile.properties.weight_name == 'routability' then if result.forward_speed > 0 then result.forward_rate = (result.forward_speed * forward_penalty) / 3.6 end @@ -389,11 +390,11 @@ function Handlers.handle_penalties(way,result,data,profile) end -- maxspeed and advisory maxspeed -function Handlers.handle_maxspeed(way,result,data,profile) +function WayHandlers.maxspeed(profile,way,result,data) 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) + forward = WayHandlers.parse_maxspeed(forward,profile) + backward = WayHandlers.parse_maxspeed(backward,profile) if forward and forward > 0 then result.forward_speed = forward * profile.speed_reduction @@ -404,7 +405,7 @@ function Handlers.handle_maxspeed(way,result,data,profile) end end -function Handlers.parse_maxspeed(source,profile) +function WayHandlers.parse_maxspeed(source,profile) if not source then return 0 end @@ -429,7 +430,7 @@ function Handlers.parse_maxspeed(source,profile) end -- handle oneways tags -function Handlers.handle_oneway(way,result,data,profile) +function WayHandlers.oneway(profile,way,result,data) if not profile.oneway_handling then return end @@ -476,8 +477,8 @@ function Handlers.handle_oneway(way,result,data,profile) end end -function Handlers.handle_weights(way,result,data,profile) - if properties.weight_name == 'distance' then +function WayHandlers.weights(profile,way,result,data) + if profile.properties.weight_name == 'distance' then result.weight = -1 -- 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 @@ -490,7 +491,7 @@ function Handlers.handle_weights(way,result,data,profile) end -- handle various that can block access -function Handlers.handle_blocked_ways(way,result,data,profile) +function WayHandlers.blocked_ways(profile,way,result,data) -- areas if profile.avoid.area and way:get_value_by_key("area") == "yes" then @@ -554,27 +555,27 @@ 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' +-- WayHandlers.tag_prefetch, +-- WayHandlers.default_mode, +-- WayHandlers.blocked_ways, +-- WayHandlers.access, +-- WayHandlers.speed, +-- WayHandlers.names -- } -- --- Handlers.run(handlers,way,result,data,profile) +-- WayHandlers.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 +-- Each method in the list will be called on the WayHandlers object. +-- All handlers must accept the parameteres (profile,way,result,data) 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) +function WayHandlers.run(profile,way,result,data,handlers) for i,handler in ipairs(handlers) do - if Handlers[handler](way,result,data,profile) == false then + if handler(profile,way,result,data) == false then return false end end end -return Handlers +return WayHandlers diff --git a/profiles/rasterbot.lua b/profiles/rasterbot.lua index 2c2a02f21dc..1c3c0c26e43 100644 --- a/profiles/rasterbot.lua +++ b/profiles/rasterbot.lua @@ -1,19 +1,30 @@ -api_version = 1 -- Rasterbot profile -properties.force_split_edges = true +api_version = 2 --- Set to true if you need to call the node_function for every node. --- Generally can be left as false to avoid unnecessary Lua calls --- (which slow down pre-processing). -properties.call_tagless_node_function = false +function setup() + local raster_path = os.getenv('OSRM_RASTER_SOURCE') or "rastersource.asc" --- Minimalist node_ and way_functions in order to test source_ and segment_functions - -function node_function (node, result) + return { + properties = { + force_split_edges = true, + process_call_tagless_node = false, + }, + + raster_source = raster:load( + raster_path, + 0, -- lon_min + 0.1, -- lon_max + 0, -- lat_min + 0.1, -- lat_max + 5, -- nrows + 4 -- ncols + ) + } end -function way_function (way, result) +-- Minimalist process_ways in order to test source_ and process_segments +function process_way (profile, way, result) local highway = way:get_value_by_key("highway") local name = way:get_value_by_key("name") @@ -28,25 +39,9 @@ function way_function (way, result) result.backward_speed = 15 end -function source_function () - local path = os.getenv('OSRM_RASTER_SOURCE') - if not path then - path = "rastersource.asc" - end - raster_source = sources:load( - path, - 0, -- lon_min - 0.1, -- lon_max - 0, -- lat_min - 0.1, -- lat_max - 5, -- nrows - 4 -- ncols - ) -end - -function segment_function (segment) - local sourceData = sources:query(raster_source, segment.source.lon, segment.source.lat) - local targetData = sources:query(raster_source, segment.target.lon, segment.target.lat) +function process_segment (profile, segment) + local sourceData = raster:query(profile.raster_source, segment.source.lon, segment.source.lat) + local targetData = raster:query(profile.raster_source, segment.target.lon, segment.target.lat) io.write("evaluating segment: " .. sourceData.datum .. " " .. targetData.datum .. "\n") local invalid = sourceData.invalid_data() local scaled_weight = segment.weight @@ -66,3 +61,9 @@ function segment_function (segment) segment.weight = scaled_weight segment.duration = scaled_duration end + +return { + setup = setup, + process_way = process_way, + process_segment = process_segment +} diff --git a/profiles/rasterbotinterp.lua b/profiles/rasterbotinterp.lua index bcc4f11b516..8e920fd817e 100644 --- a/profiles/rasterbotinterp.lua +++ b/profiles/rasterbotinterp.lua @@ -1,50 +1,10 @@ -api_version = 1 --- Rasterbot profile +-- Rasterbot with interpolation profile --- Set to true if you need to call the node_function for every node. --- Generally can be left as false to avoid unnecessary Lua calls --- (which slow down pre-processing). -properties.call_tagless_node_function = false +functions = require('rasterbot') --- Minimalist node_ and way_functions in order to test source_ and segment_functions - -function node_function (node, result) -end - -function way_function (way, result) - local highway = way:get_value_by_key("highway") - local name = way:get_value_by_key("name") - - if name then - result.name = name - end - - result.forward_mode = mode.cycling - result.backward_mode = mode.cycling - - result.forward_speed = 15 - result.backward_speed = 15 -end - -function source_function () - local path = os.getenv('OSRM_RASTER_SOURCE') - if not path then - path = "rastersource.asc" - end - raster_source = sources:load( - path, - 0, -- lon_min - 0.1, -- lon_max - 0, -- lat_min - 0.1, -- lat_max - 5, -- nrows - 4 -- ncols - ) -end - -function segment_function (segment) - local sourceData = sources:interpolate(raster_source, segment.source.lon, segment.source.lat) - local targetData = sources:interpolate(raster_source, segment.target.lon, segment.target.lat) +functions.process_segment = function(profile, segment) + local sourceData = raster:interpolate(profile.raster_source, segment.source.lon, segment.source.lat) + local targetData = raster:interpolate(profile.raster_source, segment.target.lon, segment.target.lat) io.write("evaluating segment: " .. sourceData.datum .. " " .. targetData.datum .. "\n") local invalid = sourceData.invalid_data() local scaled_weight = segment.weight @@ -64,3 +24,5 @@ function segment_function (segment) segment.weight = scaled_weight segment.duration = scaled_duration end + +return functions \ No newline at end of file diff --git a/profiles/testbot.lua b/profiles/testbot.lua index 1461845ede6..d6aa09d8514 100644 --- a/profiles/testbot.lua +++ b/profiles/testbot.lua @@ -1,49 +1,35 @@ -api_version = 1 -- Testbot profile -- Moves at fixed, well-known speeds, practical for testing speed and travel times: - -- Primary road: 36km/h = 36000m/3600s = 100m/10s -- Secondary road: 18km/h = 18000m/3600s = 100m/20s -- Tertiary road: 12km/h = 12000m/3600s = 100m/30s -speed_profile = { - ["primary"] = 36, - ["secondary"] = 18, - ["tertiary"] = 12, - ["steps"] = 6, - ["default"] = 24 -} - --- these settings are read directly by osrm - -properties.continue_straight_at_waypoint = true -properties.use_turn_restrictions = true -properties.max_speed_for_map_matching = 30/3.6 --km -> m/s -properties.weight_name = 'duration' - --- Set to true if you need to call the node_function for every node. --- Generally can be left as false to avoid unnecessary Lua calls --- (which slow down pre-processing). -properties.call_tagless_node_function = false - -local uturn_penalty = 20 -local traffic_light_penalty = 7 -- seconds - -function limit_speed(speed, limits) - -- don't use ipairs(), since it stops at the first nil value - for i=1, #limits do - limit = limits[i] - if limit ~= nil and limit > 0 then - if limit < speed then - return limit -- stop at first speedlimit that's smaller than speed - end - end - end - return speed +api_version = 2 + +function setup() + return { + properties = { + continue_straight_at_waypoint = true, + max_speed_for_map_matching = 30/3.6, --km -> m/s + weight_name = 'duration', + process_call_tagless_node = false, + uturn_penalty = 20, + traffic_light_penalty = 7, -- seconds + use_turn_restrictions = true + }, + + default_speed = 24, + speeds = { + primary = 36, + secondary = 18, + tertiary = 12, + steps = 6, + } + } end -function node_function (node, result) +function process_node (profile, node, result) local traffic_signal = node:get_value_by_key("highway") if traffic_signal and traffic_signal == "traffic_signals" then @@ -52,7 +38,7 @@ function node_function (node, result) end end -function way_function (way, result) +function process_way (profile, way, result) local highway = way:get_value_by_key("highway") local name = way:get_value_by_key("name") local oneway = way:get_value_by_key("oneway") @@ -75,7 +61,7 @@ function way_function (way, result) result.forward_mode = mode.route result.backward_mode = mode.route else - local speed_forw = speed_profile[highway] or speed_profile['default'] + local speed_forw = profile.speeds[highway] or profile.default_speed local speed_back = speed_forw if highway == "river" then @@ -122,12 +108,19 @@ function way_function (way, result) end end -function turn_function (turn) +function process_turn (profile, turn) if turn.direction_modifier == direction_modifier.uturn then - turn.duration = uturn_penalty - turn.weight = uturn_penalty + turn.duration = profile.properties.uturn_penalty + turn.weight = profile.properties.uturn_penalty end if turn.has_traffic_light then - turn.duration = turn.duration + traffic_light_penalty + turn.duration = turn.duration + profile.properties.traffic_light_penalty end end + +return { + setup = setup, + process_way = process_way, + process_node = process_node, + process_turn = process_turn +} diff --git a/profiles/turnbot.lua b/profiles/turnbot.lua index cf8a81ecf61..c85b118b01f 100644 --- a/profiles/turnbot.lua +++ b/profiles/turnbot.lua @@ -1,10 +1,10 @@ -api_version = 1 - -- Testbot, with turn penalty -- Used for testing turn penalties -require 'testbot' +functions = require 'testbot' -function turn_function (turn) - turn.duration = 20 * math.abs(turn.angle) / 180 +functions.process_turn = function(profile, turn) + turn.duration = 20 * math.abs(turn.angle) / 180 end + +return functions \ No newline at end of file diff --git a/src/extractor/extractor.cpp b/src/extractor/extractor.cpp index 58d54983d0f..80a01fb9bb5 100644 --- a/src/extractor/extractor.cpp +++ b/src/extractor/extractor.cpp @@ -224,9 +224,6 @@ Extractor::ParseOSMData(ScriptingEnvironment &scripting_environment, turn_lane_map, scripting_environment.GetProfileProperties()); - // setup raster sources - scripting_environment.SetupSources(); - std::string generator = header.get("generator"); if (generator.empty()) { diff --git a/src/extractor/raster_source.cpp b/src/extractor/raster_source.cpp index d5be13f1486..012f347a8ff 100644 --- a/src/extractor/raster_source.cpp +++ b/src/extractor/raster_source.cpp @@ -79,7 +79,7 @@ RasterDatum RasterSource::GetRasterInterpolate(const int lon, const int lat) con } // Load raster source into memory -int SourceContainer::LoadRasterSource(const std::string &path_string, +int RasterContainer::LoadRasterSource(const std::string &path_string, double xmin, double xmax, double ymin, @@ -125,7 +125,7 @@ int SourceContainer::LoadRasterSource(const std::string &path_string, } // External function for looking up nearest data point from a specified source -RasterDatum SourceContainer::GetRasterDataFromSource(unsigned int source_id, double lon, double lat) +RasterDatum RasterContainer::GetRasterDataFromSource(unsigned int source_id, double lon, double lat) { if (LoadedSources.size() < source_id + 1) { @@ -146,7 +146,7 @@ RasterDatum SourceContainer::GetRasterDataFromSource(unsigned int source_id, dou // External function for looking up interpolated data from a specified source RasterDatum -SourceContainer::GetRasterInterpolateFromSource(unsigned int source_id, double lon, double lat) +RasterContainer::GetRasterInterpolateFromSource(unsigned int source_id, double lon, double lat) { if (LoadedSources.size() < source_id + 1) { diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index 013a15c9ab0..324711a1668 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -216,15 +216,13 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) "sharp_left", extractor::guidance::DirectionModifier::SharpLeft); - context.state.new_usertype("sources", + context.state.new_usertype("raster", "load", - &SourceContainer::LoadRasterSource, + &RasterContainer::LoadRasterSource, "query", - &SourceContainer::GetRasterDataFromSource, + &RasterContainer::GetRasterDataFromSource, "interpolate", - &SourceContainer::GetRasterInterpolateFromSource); - - context.state.new_enum("constants", "precision", COORDINATE_PRECISION); + &RasterContainer::GetRasterInterpolateFromSource); context.state.new_usertype( "ProfileProperties", @@ -421,8 +419,10 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) context.state.new_usertype( "RasterDatum", "datum", &RasterDatum::datum, "invalid_data", &RasterDatum::get_invalid); + // the "properties" global is only used in v1 of the api, but we don't know + // the version until we have read the file. so we have to declare it in any case. + // we will then clear it for v2 profiles after reading the file context.state["properties"] = &context.properties; - context.state["sources"] = &context.sources; // // end of register block @@ -430,24 +430,15 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) util::luaAddScriptFolderToLoadPath(context.state.lua_state(), file_name.c_str()); - context.state.script_file(file_name); - - // cache references to functions for faster execution - context.turn_function = context.state["turn_function"]; - context.node_function = context.state["node_function"]; - context.way_function = context.state["way_function"]; - context.segment_function = context.state["segment_function"]; - - context.has_turn_penalty_function = context.turn_function.valid(); - context.has_node_function = context.node_function.valid(); - context.has_way_function = context.way_function.valid(); - context.has_segment_function = context.segment_function.valid(); + sol::optional function_table = context.state.script_file(file_name); // Check profile API version auto maybe_version = context.state.get>("api_version"); if (maybe_version) - { context.api_version = *maybe_version; + else + { + context.api_version = 0; } if (context.api_version < SUPPORTED_MIN_API_VERSION || @@ -459,14 +450,140 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) " are supported." + SOURCE_REF); } - // Assert that version-dependent properties were not changed by profile + util::Log() << "Using profile api version " << context.api_version; + + // version-dependent parts of the api switch (context.api_version) { + case 2: + { + // clear global not used in v2 + context.state["properties"] = sol::nullopt; + + // check function table + if (function_table == sol::nullopt) + throw util::exception("Profile must return a function table."); + + // setup helpers + context.state["raster"] = &context.raster_sources; + + // set constants + context.state.new_enum("constants", + "precision", + COORDINATE_PRECISION, + "max_turn_weight", + std::numeric_limits::max()); + + // call initialize function + sol::function setup_function = function_table.value()["setup"]; + if (!setup_function.valid()) + throw util::exception("Profile must have an setup() function."); + sol::optional profile_table = setup_function(); + if (profile_table == sol::nullopt) + throw util::exception("Profile setup() must return a table."); + else + context.profile_table = profile_table.value(); + + // store functions + context.turn_function = function_table.value()["process_turn"]; + context.node_function = function_table.value()["process_node"]; + context.way_function = function_table.value()["process_way"]; + context.segment_function = function_table.value()["process_segment"]; + + context.has_turn_penalty_function = context.turn_function.valid(); + context.has_node_function = context.node_function.valid(); + context.has_way_function = context.way_function.valid(); + context.has_segment_function = context.segment_function.valid(); + + // read properties from 'profile.properties' table + sol::table properties = context.profile_table["properties"]; + if (properties.valid()) + { + sol::optional weight_name = properties["weight_name"]; + if (weight_name != sol::nullopt) + context.properties.SetWeightName(weight_name.value()); + + sol::optional traffic_signal_penalty = + properties["traffic_signal_penalty"]; + if (traffic_signal_penalty != sol::nullopt) + context.properties.SetTrafficSignalPenalty(traffic_signal_penalty.value()); + + sol::optional u_turn_penalty = properties["u_turn_penalty"]; + if (u_turn_penalty != sol::nullopt) + context.properties.SetUturnPenalty(u_turn_penalty.value()); + + sol::optional max_speed_for_map_matching = + properties["max_speed_for_map_matching"]; + if (max_speed_for_map_matching != sol::nullopt) + context.properties.SetMaxSpeedForMapMatching(max_speed_for_map_matching.value()); + + sol::optional continue_straight_at_waypoint = + properties["continue_straight_at_waypoint"]; + if (continue_straight_at_waypoint != sol::nullopt) + context.properties.continue_straight_at_waypoint = + continue_straight_at_waypoint.value(); + + sol::optional use_turn_restrictions = properties["use_turn_restrictions"]; + if (use_turn_restrictions != sol::nullopt) + context.properties.use_turn_restrictions = use_turn_restrictions.value(); + + sol::optional left_hand_driving = properties["left_hand_driving"]; + if (left_hand_driving != sol::nullopt) + context.properties.left_hand_driving = left_hand_driving.value(); + + sol::optional weight_precision = properties["weight_precision"]; + if (weight_precision != sol::nullopt) + context.properties.weight_precision = weight_precision.value(); + + sol::optional force_split_edges = properties["force_split_edges"]; + if (force_split_edges != sol::nullopt) + context.properties.force_split_edges = force_split_edges.value(); + } + break; + } case 1: + { + // cache references to functions for faster execution + context.turn_function = context.state["turn_function"]; + context.node_function = context.state["node_function"]; + context.way_function = context.state["way_function"]; + context.segment_function = context.state["segment_function"]; + + context.has_turn_penalty_function = context.turn_function.valid(); + context.has_node_function = context.node_function.valid(); + context.has_way_function = context.way_function.valid(); + context.has_segment_function = context.segment_function.valid(); + + // setup helpers + context.state["sources"] = &context.raster_sources; + + // set constants + context.state.new_enum("constants", "precision", COORDINATE_PRECISION); + BOOST_ASSERT(context.properties.GetUturnPenalty() == 0); BOOST_ASSERT(context.properties.GetTrafficSignalPenalty() == 0); + + // call source_function if present + sol::function source_function = context.state["source_function"]; + if (source_function.valid()) + { + source_function(); + } + break; + } case 0: + // cache references to functions for faster execution + context.turn_function = context.state["turn_function"]; + context.node_function = context.state["node_function"]; + context.way_function = context.state["way_function"]; + context.segment_function = context.state["segment_function"]; + + context.has_turn_penalty_function = context.turn_function.valid(); + context.has_node_function = context.node_function.valid(); + context.has_way_function = context.way_function.valid(); + context.has_segment_function = context.segment_function.valid(); + BOOST_ASSERT(context.properties.GetWeightName() == "duration"); break; } @@ -542,48 +659,62 @@ void Sol2ScriptingEnvironment::ProcessElements( } } -std::vector Sol2ScriptingEnvironment::GetNameSuffixList() +std::vector +Sol2ScriptingEnvironment::GetStringListFromFunction(const std::string &function_name) { auto &context = GetSol2Context(); BOOST_ASSERT(context.state.lua_state() != nullptr); - std::vector suffixes_vector; - - sol::function get_name_suffix_list = context.state["get_name_suffix_list"]; - - if (get_name_suffix_list.valid()) + std::vector strings; + sol::function function = context.state[function_name]; + if (function.valid()) { - get_name_suffix_list(suffixes_vector); + function(strings); } - - return suffixes_vector; + return strings; } -std::vector Sol2ScriptingEnvironment::GetRestrictions() +std::vector +Sol2ScriptingEnvironment::GetStringListFromTable(const std::string &table_name) { auto &context = GetSol2Context(); BOOST_ASSERT(context.state.lua_state() != nullptr); - std::vector restrictions; - - sol::function get_restrictions = context.state["get_restrictions"]; - - if (get_restrictions.valid()) + std::vector strings; + sol::table table = context.profile_table[table_name]; + if (table.valid()) { - get_restrictions(restrictions); + for (auto &&pair : table) + { + strings.push_back(pair.second.as()); + }; } - - return restrictions; + return strings; } -void Sol2ScriptingEnvironment::SetupSources() +std::vector Sol2ScriptingEnvironment::GetNameSuffixList() { auto &context = GetSol2Context(); - BOOST_ASSERT(context.state.lua_state() != nullptr); - - sol::function source_function = context.state["source_function"]; + switch (context.api_version) + { + case 2: + return Sol2ScriptingEnvironment::GetStringListFromTable("suffix_list"); + case 1: + return Sol2ScriptingEnvironment::GetStringListFromFunction("get_name_suffix_list"); + default: + return {}; + } +} - if (source_function.valid()) +std::vector Sol2ScriptingEnvironment::GetRestrictions() +{ + auto &context = GetSol2Context(); + switch (context.api_version) { - source_function(); + case 2: + return Sol2ScriptingEnvironment::GetStringListFromTable("restrictions"); + case 1: + return Sol2ScriptingEnvironment::GetStringListFromFunction("get_restrictions"); + default: + return {}; } } @@ -593,6 +724,21 @@ void Sol2ScriptingEnvironment::ProcessTurn(ExtractionTurn &turn) switch (context.api_version) { + case 2: + if (context.has_turn_penalty_function) + { + context.turn_function(context.profile_table, turn); + + // Turn weight falls back to the duration value in deciseconds + // or uses the extracted unit-less weight value + if (context.properties.fallback_to_duration) + turn.weight = turn.duration; + else + // cap turn weight to max turn weight, which depend on weight precision + turn.weight = std::min(turn.weight, context.properties.GetMaxTurnWeight()); + } + + break; case 1: if (context.has_turn_penalty_function) { @@ -644,6 +790,9 @@ void Sol2ScriptingEnvironment::ProcessSegment(ExtractionSegment &segment) { switch (context.api_version) { + case 2: + context.segment_function(context.profile_table, segment); + break; case 1: context.segment_function(segment); break; @@ -660,14 +809,32 @@ void LuaScriptingContext::ProcessNode(const osmium::Node &node, ExtractionNode & { BOOST_ASSERT(state.lua_state() != nullptr); - node_function(node, result); + switch (api_version) + { + case 2: + node_function(profile_table, node, result); + break; + case 1: + case 0: + node_function(node, result); + break; + } } void LuaScriptingContext::ProcessWay(const osmium::Way &way, ExtractionWay &result) { BOOST_ASSERT(state.lua_state() != nullptr); - way_function(way, result); + switch (api_version) + { + case 2: + way_function(profile_table, way, result); + break; + case 1: + case 0: + way_function(way, result); + break; + } } } } diff --git a/unit_tests/extractor/raster_source.cpp b/unit_tests/extractor/raster_source.cpp index cbfac64336d..1721f3d393d 100644 --- a/unit_tests/extractor/raster_source.cpp +++ b/unit_tests/extractor/raster_source.cpp @@ -22,7 +22,7 @@ int normalize(double coord) { return static_cast(coord * COORDINATE_PRECISI BOOST_AUTO_TEST_CASE(raster_test) { - SourceContainer sources; + RasterContainer sources; int source_id = sources.LoadRasterSource(OSRM_FIXTURES_DIR "/raster_data.asc", 1, 1.09, 1, 1.09, 10, 10); BOOST_CHECK_EQUAL(source_id, 0);