[osrm] 02/07: Imported Upstream version 5.4.0+ds

Bas Couwenberg sebastic at debian.org
Tue Oct 4 15:14:57 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository osrm.

commit 619a71f982a4c2e283b0b9b19bbb3d44b5280eeb
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Oct 4 16:29:27 2016 +0200

    Imported Upstream version 5.4.0+ds
---
 .github/PULL_REQUEST_TEMPLATE.md                   |  12 +
 .travis.yml                                        |  27 +-
 CHANGELOG.md                                       |  78 ++-
 CMakeLists.txt                                     |  48 +-
 README.md                                          |   5 +
 cucumber.js                                        |  10 +-
 docs/http.md                                       |  49 +-
 docs/profiles.md                                   |  13 +
 docs/releasing.md                                  |  64 +--
 example/example.cpp                                |  10 +-
 features/bicycle/names.feature                     |   4 +-
 features/bicycle/ref.feature                       |   8 +-
 features/car/access.feature                        |  41 +-
 features/car/ferry.feature                         |  16 +-
 features/car/names.feature                         |  57 +-
 features/car/restrictions.feature                  |  48 +-
 features/car/service.feature                       |  13 +
 features/car/summaries.feature                     |  94 ++++
 features/car/traffic_speeds.feature                | 106 +++-
 features/car/traffic_turn_penalties.feature        |  28 +-
 features/foot/names.feature                        |   2 +-
 features/foot/ref.feature                          |   6 +-
 features/guidance/advanced-lanes.feature           | 236 +++++++++
 features/guidance/anticipate-lanes.feature         | 343 +++++++++---
 features/guidance/bugs.feature                     |  35 ++
 features/guidance/collapse-detail.feature          |  53 ++
 features/guidance/collapse.feature                 | 280 ++++++++--
 features/guidance/dedicated-turn-roads.feature     | 190 ++++++-
 features/guidance/destination-signs.feature        |  20 +-
 features/guidance/fork.feature                     |   6 +-
 features/guidance/merge.feature                    |  76 ++-
 features/guidance/new-name.feature                 | 177 ++++++-
 features/guidance/perception.feature               | 126 +++++
 features/guidance/ramp.feature                     |  30 +-
 features/guidance/roundabout-bike.feature          |  31 --
 features/guidance/roundabout-turn-bike.feature     |  37 ++
 features/guidance/roundabout-turn.feature          | 122 +++++
 features/guidance/roundabout.feature               | 225 +++-----
 features/guidance/staggered-intersections.feature  |  92 ++++
 features/guidance/suffix-changes.feature           |  23 +-
 features/guidance/suppressed.feature               |   4 +-
 features/guidance/turn-lanes.feature               | 204 +++++--
 features/guidance/turn.feature                     | 211 +++++++-
 features/lib/hash.js                               |  31 ++
 features/{support/build_osm.js => lib/osm.js}      |   8 +-
 features/lib/osrm_loader.js                        | 169 ++++++
 features/lib/table_diff.js                         |  54 ++
 features/lib/try_connect.js                        |  13 +
 features/lib/utils.js                              |  17 +
 features/options/contract/datasources.feature      |  10 +-
 features/options/contract/files.feature            |  13 +-
 features/options/contract/help.feature             |   8 +-
 features/options/contract/invalid.feature          |   4 +-
 features/options/contract/version.feature          |   4 +-
 features/options/extract/files.feature             |  14 +-
 features/options/extract/help.feature              |   6 +-
 features/options/extract/invalid.feature           |   4 +-
 features/options/extract/version.feature           |   4 +-
 features/options/routed/files.feature              |   2 +-
 features/options/routed/help.feature               |   6 +-
 features/options/routed/invalid.feature            |  10 +-
 features/options/routed/version.feature            |   4 +-
 features/raster/extract.feature                    |  19 +-
 features/raster/weights.feature                    |  12 +-
 features/step_definitions/data.js                  |  60 +--
 features/step_definitions/distance_matrix.js       |  11 +-
 features/step_definitions/hooks.js                 |  18 -
 features/step_definitions/matching.js              |   4 +-
 features/step_definitions/nearest.js               |  11 +-
 features/step_definitions/options.js               |  56 +-
 features/step_definitions/requests.js              |   5 +-
 features/step_definitions/routability.js           |   8 +-
 features/step_definitions/trip.js                  |  12 +-
 features/support/cache.js                          | 184 +++++++
 features/support/config.js                         | 127 -----
 features/support/data.js                           | 255 +++------
 features/support/data_classes.js                   |  40 +-
 features/support/env.js                            | 130 +++--
 features/support/exception_classes.js              | 132 -----
 features/support/exceptions.js                     |  15 -
 features/support/hash.js                           |  43 --
 features/support/hooks.js                          |  73 ++-
 features/support/http.js                           |  12 +-
 features/support/launch.js                         |   5 -
 features/support/launch_classes.js                 | 164 ------
 features/support/log.js                            |  90 ----
 features/support/route.js                          |  23 +-
 features/support/run.js                            |  70 ++-
 features/support/shared_steps.js                   |  29 +-
 features/support/table_patch.js                    |  11 -
 features/testbot/bad.feature                       |   4 +-
 features/testbot/bearing_param.feature             |  18 +-
 features/testbot/bugs.feature                      |   5 -
 features/testbot/matching.feature                  |  14 +-
 features/testbot/side_bias.feature                 |  83 +++
 features/testbot/summary.feature                   |   6 +-
 features/testbot/traffic_turn_penalties.feature    |   4 +-
 features/testbot/utf.feature                       |  15 +
 features/testbot/via.feature                       |   2 +-
 fuzz/CMakeLists.txt                                |  58 ++
 fuzz/escape_json.cc                                |  18 +
 fuzz/match_parameters.cc                           |  23 +
 fuzz/nearest_parameters.cc                         |  23 +
 fuzz/request_parser.cc                             |  28 +
 fuzz/route_parameters.cc                           |  23 +
 fuzz/table_parameters.cc                           |  23 +
 fuzz/tile_parameters.cc                            |  23 +
 fuzz/trip_parameters.cc                            |  23 +
 fuzz/uri_decode.cc                                 |  21 +
 fuzz/url_parser.cc                                 |  21 +
 fuzz/util.hpp                                      |  16 +
 include/engine/api/route_api.hpp                   |  21 +-
 include/engine/api/route_parameters.hpp            |  15 +
 include/engine/datafacade/datafacade_base.hpp      |   2 +
 include/engine/datafacade/internal_datafacade.hpp  |  11 +-
 include/engine/datafacade/shared_datafacade.hpp    |  16 +-
 include/engine/engine.hpp                          |  14 +-
 include/engine/engine_config.hpp                   |   2 +
 include/engine/geospatial_query.hpp                | 131 +++--
 include/engine/guidance/assemble_geometry.hpp      |  16 +-
 include/engine/guidance/assemble_leg.hpp           |  38 +-
 include/engine/guidance/assemble_steps.hpp         |  12 +-
 include/engine/guidance/lane_processing.hpp        |   6 +
 include/engine/guidance/leg_geometry.hpp           |   1 +
 include/engine/guidance/post_processing.hpp        |  17 +-
 include/engine/guidance/route_step.hpp             |   8 +-
 include/engine/guidance/step_maneuver.hpp          |   7 +-
 include/engine/internal_route_result.hpp           |   3 +
 include/engine/plugins/nearest.hpp                 |   5 +-
 include/engine/routing_algorithms/routing_base.hpp |  41 +-
 include/extractor/edge_based_graph_factory.hpp     |  43 +-
 include/extractor/extraction_containers.hpp        |  15 +-
 include/extractor/extraction_helper_functions.hpp  |   6 +
 include/extractor/extraction_way.hpp               |   5 +
 include/extractor/extractor.hpp                    |  17 +-
 include/extractor/extractor_callbacks.hpp          |  34 +-
 include/extractor/extractor_config.hpp             |   1 -
 include/extractor/guidance/classification_data.hpp |  84 ---
 include/extractor/guidance/constants.hpp           |  15 +-
 .../extractor/guidance/intersection_generator.hpp  |  42 +-
 .../extractor/guidance/intersection_handler.hpp    |  19 +-
 .../guidance/intersection_scenario_three_way.hpp   |   5 -
 include/extractor/guidance/motorway_handler.hpp    |  14 +-
 include/extractor/guidance/road_classification.hpp | 127 +++++
 include/extractor/guidance/roundabout_handler.hpp  |   9 +-
 .../{motorway_handler.hpp => sliproad_handler.hpp} |  31 +-
 include/extractor/guidance/toolkit.hpp             | 350 ++----------
 include/extractor/guidance/turn_analysis.hpp       |   8 +-
 include/extractor/guidance/turn_handler.hpp        |  22 +-
 include/extractor/guidance/turn_instruction.hpp    |   8 +-
 .../extractor/guidance/turn_lane_augmentation.hpp  |   2 +
 include/extractor/guidance/turn_lane_data.hpp      |  11 +-
 include/extractor/guidance/turn_lane_handler.hpp   | 100 +++-
 include/extractor/guidance/turn_lane_matcher.hpp   |  16 +-
 include/extractor/guidance/turn_lane_types.hpp     |  13 +-
 include/extractor/internal_extractor_edge.hpp      |  11 +-
 include/extractor/node_based_edge.hpp              |  37 +-
 include/extractor/profile_properties.hpp           |   3 +-
 include/extractor/query_node.hpp                   |   6 +-
 include/extractor/restriction_parser.hpp           |   6 +-
 include/extractor/scripting_environment.hpp        |  70 ++-
 include/extractor/scripting_environment_lua.hpp    |  80 +++
 include/extractor/suffix_table.hpp                 |   8 +-
 .../engine_config.hpp => osrm/exception.hpp}       |  45 +-
 include/osrm/osrm.hpp                              |  12 +-
 include/util/attributes.hpp                        |  13 +
 include/util/bearing.hpp                           |   2 +-
 include/util/debug.hpp                             |  40 +-
 include/util/exception.hpp                         |  27 +
 include/util/guidance/toolkit.hpp                  | 131 ++++-
 include/util/io.hpp                                |   2 +-
 include/util/name_table.hpp                        |   6 +
 include/util/node_based_graph.hpp                  |  14 +-
 include/util/static_rtree.hpp                      |   9 +-
 include/util/strong_typedef.hpp                    |  52 +-
 include/util/typedefs.hpp                          |   5 +-
 package.json                                       |   2 +
 profiles/bicycle.lua                               |  22 +-
 profiles/car.lua                                   | 195 ++++---
 profiles/foot.lua                                  |   9 +-
 profiles/lhs.lua                                   |  20 +
 profiles/lib/guidance.lua                          | 127 +++++
 profiles/rasterbot.lua                             |   6 +-
 profiles/rasterbotinterp.lua                       |   6 +-
 profiles/rhs.lua                                   |  20 +
 profiles/turnbot.lua                               |   3 +-
 scripts/format.sh                                  |  27 +-
 scripts/gdb_printers.py                            |  63 +++
 src/contractor/contractor.cpp                      |  84 +--
 src/engine/api/json_factory.cpp                    |  21 +-
 src/engine/engine.cpp                              |  16 +-
 src/engine/engine_config.cpp                       |  14 +-
 src/engine/guidance/lane_processing.cpp            | 179 +++++--
 src/engine/guidance/post_processing.cpp            | 457 ++++++++++------
 src/engine/plugins/nearest.cpp                     |  15 +-
 src/engine/plugins/tile.cpp                        |   2 +-
 src/engine/polyline_compressor.cpp                 |  10 +-
 src/extractor/edge_based_graph_factory.cpp         |  72 +--
 src/extractor/extraction_containers.cpp            | 130 ++---
 src/extractor/extractor.cpp                        | 223 ++++----
 src/extractor/extractor_callbacks.cpp              |  59 +--
 src/extractor/graph_compressor.cpp                 |  20 +-
 src/extractor/guidance/classification_data.cpp     |  53 --
 src/extractor/guidance/intersection_generator.cpp  | 388 ++++++++++++--
 src/extractor/guidance/intersection_handler.cpp    | 386 +++++++++++++-
 .../guidance/intersection_scenario_three_way.cpp   |   8 -
 src/extractor/guidance/motorway_handler.cpp        | 103 ++--
 src/extractor/guidance/roundabout_handler.cpp      | 183 ++++---
 src/extractor/guidance/sliproad_handler.cpp        | 282 ++++++++++
 src/extractor/guidance/turn_analysis.cpp           | 163 +-----
 src/extractor/guidance/turn_discovery.cpp          |  23 +-
 src/extractor/guidance/turn_handler.cpp            | 393 +++++---------
 src/extractor/guidance/turn_lane_augmentation.cpp  |  24 +-
 src/extractor/guidance/turn_lane_data.cpp          |  16 +-
 src/extractor/guidance/turn_lane_handler.cpp       | 585 +++++++++++++++------
 src/extractor/guidance/turn_lane_matcher.cpp       |  67 ++-
 src/extractor/raster_source.cpp                    |   5 +-
 src/extractor/restriction_map.cpp                  |   8 +-
 src/extractor/restriction_parser.cpp               |  65 +--
 src/extractor/scripting_environment.cpp            | 207 --------
 src/extractor/scripting_environment_lua.cpp        | 407 ++++++++++++++
 src/extractor/suffix_table.cpp                     |  27 +-
 src/osrm/osrm.cpp                                  |  12 +-
 src/storage/storage.cpp                            |   9 +-
 src/tools/components.cpp                           |   7 +-
 src/tools/contract.cpp                             |  23 +-
 src/tools/extract.cpp                              |  57 +-
 src/tools/io-benchmark.cpp                         |  13 +-
 src/tools/routed.cpp                               |  34 +-
 src/tools/springclean.cpp                          |   7 +-
 src/tools/store.cpp                                |  24 +-
 src/tools/unlock_all_mutexes.cpp                   |   7 +-
 src/util/coordinate_calculation.cpp                |   6 +-
 src/util/name_table.cpp                            |  21 +
 taginfo.json                                       |  59 ++-
 unit_tests/engine/json_factory.cpp                 |  16 +
 unit_tests/library/limits.cpp                      |  30 ++
 unit_tests/mocks/mock_datafacade.hpp               |   5 +-
 unit_tests/util/bearing.cpp                        |   3 +
 unit_tests/util/packed_vector.cpp                  |   2 +-
 unit_tests/util/static_rtree.cpp                   |   3 +
 241 files changed, 8851 insertions(+), 4335 deletions(-)

diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..c51d02e
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,12 @@
+# Issue
+
+What issue is this PR targeting? Is there no issue that covers the problem addressed here? Please open a corresponding issue and link it from here.
+
+## Tasklist
+ - [ ] ADD OWN TASKS HERE
+ - [ ] add regression / cucumber cases (see docs/testing.md)
+ - [ ] review
+ - [ ] adjust for for comments
+
+## Requirements / Relations
+ Link any requirements here. Other pull requests this PR is based on?
diff --git a/.travis.yml b/.travis.yml
index 9ec92df..74b96e6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,7 +13,7 @@ notifications:
 branches:
   only:
     - master
-    - "5.3"
+    - 5.4
 
 cache:
   ccache: true
@@ -25,7 +25,7 @@ env:
   global:
    - CCACHE_TEMPDIR=/tmp/.ccache-temp
    - CCACHE_COMPRESS=1
-   - CASHER_TIME_OUT=1000
+   - CASHER_TIME_OUT=599 # one second less than 10m to avoid 10m timeout error: https://github.com/Project-OSRM/osrm-backend/issues/2742
    - JOBS=4
 
 matrix:
@@ -44,14 +44,6 @@ matrix:
       env: CCOMPILER='gcc-5' CXXCOMPILER='g++-5' BUILD_TYPE='Debug' COVERAGE=ON
 
     - os: linux
-      compiler: "gcc-4.8-debug"
-      addons: &gcc48
-        apt:
-          sources: ['ubuntu-toolchain-r-test']
-          packages: ['g++-4.8', 'libbz2-dev', 'libstxxl-dev', 'libstxxl1', 'libxml2-dev', 'libzip-dev', 'lua5.1', 'liblua5.1-0-dev', 'libtbb-dev', 'libgdal-dev', 'libluabind-dev', 'libboost-all-dev', 'ccache']
-      env: CCOMPILER='gcc-4.8' CXXCOMPILER='g++-4.8' BUILD_TYPE='Debug'
-
-    - os: linux
       compiler: "clang-3.8-debug"
       addons: &clang38
         apt:
@@ -77,19 +69,6 @@ matrix:
       compiler: "gcc-5-release-i686"
       env: TARGET_ARCH='i686' CCOMPILER='gcc-5' CXXCOMPILER='g++-5' BUILD_TYPE='Release'
 
-    - os: linux
-      compiler: "gcc-4.8-release-armhf"
-      env: TARGET_ARCH='armhf' CCOMPILER='arm-linux-gnueabihf-gcc-4.8' CXXCOMPILER='arm-linux-gnueabihf-g++-4.8' BUILD_TYPE='Release'
-
-      # Disabled because of CI slowness
-      #- os: linux
-      #- compiler: gcc
-      #- addons: &gcc48
-      #-   apt:
-      #-     sources: ['ubuntu-toolchain-r-test']
-      #-     packages: ['g++-4.8', 'libbz2-dev', 'libstxxl-dev', 'libstxxl1', 'libxml2-dev', 'libzip-dev', 'lua5.1', 'liblua5.1-0-dev', 'libtbb-dev', 'libgdal-dev', 'libluabind-dev', 'libboost-all-dev']
-      #- env: CCOMPILER='gcc-4.8' CXXCOMPILER='g++-4.8' BUILD_TYPE='Release'
-
       # Disabled because of CI slowness
       #- os: linux
       #- compiler: clang
@@ -152,7 +131,7 @@ install:
     fi
   - mkdir build && pushd build
   - export CC=${CCOMPILER} CXX=${CXXCOMPILER}
-  - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS:-OFF} -DCOVERAGE=${COVERAGE:-OFF} -DBUILD_TOOLS=1 -DENABLE_CCACHE=ON
+  - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS:-OFF} -DCOVERAGE=${COVERAGE:-OFF} -DBUILD_TOOLS=ON -DBUILD_COMPONENTS=ON -DENABLE_CCACHE=ON
   - echo "travis_fold:start:MAKE"
   - make osrm-extract --jobs=3
   - make --jobs=${JOBS}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 49523cf..45d8688 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,24 +1,68 @@
-# 5.3.3
-  Changes from 5.3.2
+# 5.4.0-rc.7
+  - Chages from 5.4.0-rc.6
+    - Bugfixes re-introduce space between two entries in summaries
+
+# 5.4.0-rc.6
+  - Changes from 5.4.0-rc.5
     - Bugfixes
-      - Fixed an issue that would result in segfaults for viaroutes with an invalid intermediate segment when u-turns were allowed at the via-location
-      - Fixed an issue that could result in segfaults when querying roads that could require looping back to the start of a way while using a core factor
-      - Fixed an issue that could break some testcases when using a core factor
-      - Fixed an issue with parallel edges that could result in weird routes
+      - fixed a bug where polyline decoding on a defective polyline could end up in out-of-bound access on a vector
+    - Guidance
+      - Summaries have been improved to consider references as well
 
-# 5.3.2
-  Changes from 5.3.1
+# 5.4.0-rc.5
+  - Changes from 5.4.0-rc.4
+    - Guidance
+      - Improved detection of obvious name changes
+    - Profiles
+      - The default profile for car now excludes HOV-only routes in navigation by default
     - Bugfixes
-      - fixed a bug that occurred when trimming very short segments at the begin/end of a route (less than 1 meter)
+      - Fixed a bug that could result in endless loops in combination with sliproads
 
-# 5.3.1
-  Changes from 5.3.1
-    - Bugfixes:
-      - Disabled broken lane handling for complex uturn/oneway combinations for now (190 intersections affected on the planet)
-      - Fixed a bug with overlaping geometries, which broke OSRM on recent Egypt extracts with data-modelling issues
+# 5.4.0-rc.4
+  - Changes from 5.4.0-rc.3
+    - Bugfixes
+      - Fixed a bug where roundabout intersections could result in breaking assertions when immediately exited
+
+# 5.4.0-rc.3
+  - Changes from 5.4.0-rc.2
+    - Bugfixes
+      - BREAKING: Fixed a bug where some roads could be falsly identified as sliproadsi This change requires reprocessing datasets with osrm-extract and osrm-contract
+      - BREAKING: Fixed a bug that resulted in false names/ref/destination/pronunciation This change requires reprocessing datasets with osrm-extract and osrm-contract
+      - `restrictions` is now used for namespaced restrictions and restriction exceptions (e.g. `restriction:motorcar=` as well as `except=motorcar`)
+      - replaced lhs/rhs profiles by using test defined profiles
+    - Trip Plugin
+      - changed internal behaviour to prefer the smallest lexicographic result over the largest one
+
+# 5.4.0
+  - Changes from 5.3.0
+    - Profiles
+      - includes library guidance.lua that offers preliminary configuration on guidance.
+      - added left_hand_driving flag in global profile properties
+      - modified turn penalty function for car profile - better fit to real data
+      - return `ref` and `name` as separate fields. Do no use ref or destination as fallback for name value
+    - Guidance
+      - Handle Access tags for lanes, only considering valid lanes in lane-guidance (think car | car | bike | car)
+    - API:
+      - `annotations=true` now returns the data source id for each segment as `datasources`
+      - Reduced semantic of merge to refer only to merges from a lane onto a motorway-like road
+      - new `ref` field in the `RouteStep` object. It contains the reference code or name of a way. Previously merged into the `name` property like `name (ref)` and are now separate fields.
+    - Bugfixes
+      - Fixed an issue that would result in segfaults for viaroutes with an invalid intermediate segment when u-turns were allowed at the via-location
+      - Invalid only_* restrictions could result in loss of connectivity. As a fallback, we assume all turns allowed when the restriction is not valid
+      - Fixed a bug that could result in an infinite loop when finding information about an upcoming intersection
+      - Fixed a bug that led to not discovering if a road simply looses a considered prefix
+      - BREAKING: Fixed a bug that could crash postprocessing of instructions on invalid roundabout taggings. This change requires reprocessing datasets with osrm-extract and osrm-contract
+      - Fixed an issue that could emit `invalid` as instruction when ending on a sliproad after a traffic-light
+      - Fixed an issue that would detect turning circles as sliproads
+      - Fixed a bug where post-processing instructions (e.g. left + left -> uturn) could result in false pronunciations
+      - Fixes a bug where a bearing range of zero would cause exhaustive graph traversals
+      - Fixes a bug where certain looped geometries could cause an infinite loop during extraction
+
+    - Infrastructure:
+      - Adds a feature to limit results in nearest service with a default of 100 in `osrm-routed`
 
 # 5.3.0
-  Changes from 5.3.0-rc.3
+  - Changes from 5.3.0-rc.3
     - Guidance
       - Only announce `use lane` on required turns (not using all lanes to go straight)
       - Moved `lanes` to the intersection objects. This is BREAKING in relation to other Release Candidates but not with respect to other releases.
@@ -26,7 +70,7 @@
       - Fix BREAKING: bug that could result in failure to load 'osrm.icd' files. This breaks the dataformat
       - Fix: bug that results in segfaults when `use lane` instructions are suppressed
 
-  Changes form 5.2.7
+  - Changes form 5.2.7
     - API
       - Introduces new `TurnType` in the form of `use lane`. The type indicates that you have to stick to a lane without turning
       - Introduces `lanes` to the `Intersection` object. The lane data contains both the markings at the intersection and a flag indicating if they can be chosen for the next turn
@@ -50,7 +94,7 @@
       - Fix devide by zero on updating speed data using osrm-contract
 
 # 5.3.0 RC3
-  Changes from 5.3.0-rc.2
+  - Changes from 5.3.0-rc.2
     - Guidance
       - Improved detection of obvious turns
       - Improved turn lane detection
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c6518c9..92ae330 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,8 +9,8 @@ endif()
 
 project(OSRM C CXX)
 set(OSRM_VERSION_MAJOR 5)
-set(OSRM_VERSION_MINOR 3)
-set(OSRM_VERSION_PATCH 2)
+set(OSRM_VERSION_MINOR 4)
+set(OSRM_VERSION_PATCH 0)
 
 # these two functions build up custom variables:
 #   OSRM_INCLUDE_PATHS and OSRM_DEFINES
@@ -54,6 +54,8 @@ option(ENABLE_ASSERTIONS OFF)
 option(COVERAGE OFF)
 option(SANITIZER OFF)
 option(ENABLE_LTO "Use LTO if available" ON)
+option(ENABLE_FUZZING "Fuzz testing using LLVM's libFuzzer" OFF)
+option(ENABLE_GOLD_LINKER "Use GNU gold linker if available" ON)
 
 include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/include/)
 include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/include/)
@@ -115,6 +117,26 @@ if(CMAKE_BUILD_TYPE MATCHES Debug)
 
   endif()
 endif()
+
+if(ENABLE_GOLD_LINKER)
+    execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION)
+    if("${LD_VERSION}" MATCHES "GNU gold")
+        set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold -Wl,--disable-new-dtags")
+        set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold -Wl,--disable-new-dtags")
+        message(STATUS "Using GNU gold as linker.")
+
+        # Issue 2785: check gold binutils version and don't use gc-sections for versions prior 2.25
+        string(REGEX REPLACE ".*\\(GNU Binutils[^\\)0-9]+([0-9]+\\.[0-9]+)[^\\)]*\\).*" "\\1" GOLD_BINUTILS_VERSION "${LD_VERSION}")
+        if ("${GOLD_BINUTILS_VERSION}" VERSION_LESS "2.26")
+          message(STATUS "Disabling gc-sections on gold binutils < 2.26, see: https://sourceware.org/bugzilla/show_bug.cgi?id=17639")
+          set(LD_AVOID_GC_SECTIONS TRUE)
+        endif()
+    else()
+        message(WARNING "GNU gold linker isn't available.")
+        set(ENABLE_GOLD_LINKER OFF)
+    endif()
+endif()
+
 if(CMAKE_BUILD_TYPE MATCHES Release)
   message(STATUS "Configuring OSRM in release mode")
   # Check if LTO is available
@@ -205,7 +227,7 @@ execute_process(COMMAND ${CMAKE_CXX_COMPILER} "-Wl,--version" ERROR_QUIET OUTPUT
 # For ld.gold and ld.bfs (the GNU linkers) we optimize hard
 if("${LINKER_VERSION}" MATCHES "GNU gold" OR "${LINKER_VERSION}" MATCHES "GNU ld")
   message(STATUS "Setting linker optimizations")
-  if(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
+  if(NOT (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC" OR "${LD_AVOID_GC_SECTIONS}"))
     # Tell compiler to put every function in separate section, linker can then match sections and functions
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
     # Tell linker to do dead code and data eminination during link time discarding sections
@@ -220,9 +242,9 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}")
 set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}")
 set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}")
 
-# Activate C++11
+# Activate C++1y
 if(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y")
 endif()
 
 # Configuring other platform dependencies
@@ -244,6 +266,7 @@ if(UNIX AND NOT APPLE)
   set(MAYBE_RT_LIBRARY rt)
 endif()
 
+
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third_party/libosmium/cmake")
 set(OSMIUM_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/libosmium/include")
 find_package(Osmium REQUIRED COMPONENTS io)
@@ -416,7 +439,7 @@ file(GLOB VariantGlob third_party/variant/*.hpp)
 file(GLOB LibraryGlob include/osrm/*.hpp)
 file(GLOB ParametersGlob include/engine/api/*_parameters.hpp)
 set(EngineHeader include/engine/status.hpp include/engine/engine_config.hpp include/engine/hint.hpp include/engine/bearing.hpp include/engine/phantom_node.hpp)
-set(UtilHeader include/util/coordinate.hpp include/util/json_container.hpp include/util/typedefs.hpp include/util/strong_typedef.hpp)
+set(UtilHeader include/util/coordinate.hpp include/util/json_container.hpp include/util/typedefs.hpp include/util/strong_typedef.hpp include/util/exception.hpp)
 set(ExtractorHeader include/extractor/extractor.hpp include/extractor/extractor_config.hpp include/extractor/travel_mode.hpp)
 set(ContractorHeader include/contractor/contractor.hpp include/contractor/contractor_config.hpp)
 set(StorageHeader include/storage/storage.hpp include/storage/storage_config.hpp)
@@ -498,3 +521,16 @@ add_custom_target(uninstall
 # Modular build system: each directory registered here provides its own CMakeLists.txt
 add_subdirectory(unit_tests)
 add_subdirectory(src/benchmarks)
+
+if (ENABLE_FUZZING)
+  # Requires libosrm being built with sanitizers; make configurable and default to ubsan
+  set(FUZZ_SANITIZER "undefined" CACHE STRING "Sanitizer to be used for Fuzz testing")
+  set_property(CACHE FUZZ_SANITIZER PROPERTY STRINGS "undefined" "integer" "address" "memory" "thread" "leak")
+
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize-coverage=edge,indirect-calls,8bit-counters -fsanitize=address")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
+
+  message(STATUS "Using -fsanitize=${FUZZ_SANITIZER} for Fuzz testing")
+
+  add_subdirectory(fuzz)
+endif ()
diff --git a/README.md b/README.md
index f1a28a3..13d0c1d 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,11 @@ The Open Source Routing Machine is a high performance routing engine written in
 | Windows      | [![Build status](https://ci.appveyor.com/api/projects/status/4iuo3s9gxprmcjjh)](https://ci.appveyor.com/project/DennisOSRM/osrm-backend) |
 | Coverage     | [![codecov](https://codecov.io/gh/Project-OSRM/osrm-backend/branch/master/graph/badge.svg)](https://codecov.io/gh/Project-OSRM/osrm-backend) |
 
+## Contact
+
+- IRC: server `irc.oftc.net`, channel: `#osrm` (see: `https://www.oftc.net`, and for a webchat: `https://webchat.oftc.net`)
+- Mailinglist: `https://lists.openstreetmap.org/listinfo/osrm-talk`
+
 ## Building
 
 For instructions on how to [build](https://github.com/Project-OSRM/osrm-backend/wiki/Building-OSRM) and [run OSRM](https://github.com/Project-OSRM/osrm-backend/wiki/Running-OSRM), please consult [the Wiki](https://github.com/Project-OSRM/osrm-backend/wiki).
diff --git a/cucumber.js b/cucumber.js
index 5a87dd7..0bf6f57 100644
--- a/cucumber.js
+++ b/cucumber.js
@@ -1,10 +1,8 @@
 module.exports = {
-    default: '--require features --tags ~@stress --tags ~@todo',
-    verify: '--require features --tags ~@todo --tags ~@bug --tags ~@stress -f progress',
-    jenkins: '--require features --tags ~@todo --tags ~@bug --tags ~@stress --tags ~@options -f progress',
-    bugs: '--require features --tags @bug',
-    todo: '--require features --tags @todo',
-    all: '--require features'
+    default: '--strict --tags ~@stress --tags ~@todo --require features/support --require features/step_definitions',
+    verify: '--strict --tags ~@stress --tags ~@todo -f progress --require features/support --require features/step_definitions',
+    todo: '--strict --tags @todo --require features/support --require features/step_definitions',
+    all: '--strict --require features/support --require features/step_definitions'
 }
 
 
diff --git a/docs/http.md b/docs/http.md
index fca0680..f870c86 100644
--- a/docs/http.md
+++ b/docs/http.md
@@ -30,17 +30,17 @@ http://{server}/{service}/{version}/{profile}/{coordinates}[.{format}]?option=va
   
     | Service     |           Description                                     |
     |-------------|-----------------------------------------------------------|
-    | [`route`](#service-route)     | shortest path between given coordinates                   |
+    | [`route`](#service-route)     | fastest path between given coordinates                   |
     | [`nearest`](#service-nearest)   | returns the nearest street segment for a given coordinate |
     | [`table`](#service-table)     | computes distance tables for given coordinates            |
     | [`match`](#service-match)     | matches given coordinates to the road network             |
-    | [`trip`](#service-trip)      | Compute the shortest round trip between given coordinates |
+    | [`trip`](#service-trip)      | Compute the fastest round trip between given coordinates |
     | [`tile`](#service-tile)      | Return vector tiles containing debugging info             |
   
 - `version`: Version of the protocol implemented by the service.
 - `profile`: Mode of transportation, is determined by the profile that is used to prepare the data
 - `coordinates`: String of format `{longitude},{latitude};{longitude},{latitude}[;{longitude},{latitude} ...]` or `polyline({polyline})`.
-- `format`: Only `json` is supportest at the moment. This parameter is optional and defaults to `json`.
+- `format`: Only `json` is supported at the moment. This parameter is optional and defaults to `json`.
 
 Passing any `option=value` is optional. `polyline` follows Google's polyline format with precision 5 and can be generated using [this package](https://www.npmjs.com/package/polyline).
 To pass parameters to each location some options support an array like encoding:
@@ -181,6 +181,14 @@ In case of error the following `code`s are supported in addition to the general
 
 All other fields might be undefined.
 
+### Example
+
+Query on Berlin with three coordinates and no overview geometry returned:
+
+```
+http://router.project-osrm.org/route/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?overview=false
+```
+
 ## Service `table`
 ### Request
 ```
@@ -294,7 +302,7 @@ All other fields might be undefined.
 ## Service `trip`
 
 The trip plugin solves the Traveling Salesman Problem using a greedy heuristic (farthest-insertion algorithm).
-The returned path does not have to be the shortest path, as TSP is NP-hard it is only an approximation.
+The returned path does not have to be the fastest path, as TSP is NP-hard it is only an approximation.
 Note that if the input coordinates can not be joined by a single trip (e.g. the coordinates are on several disconnected islands)
 multiple trips for each connected component are returned.
 
@@ -399,8 +407,8 @@ Represents a route between two waypoints.
 
    | annotations  |                                                                       |
    |--------------|-----------------------------------------------------------------------|
-   | true         | returns distance and durations of each coordinate along the route     |
-   | false        | will not exist                                                        |
+   | true         | An `Annotation` object containing node ids, durations and distances   |
+   | false        | `undefined`                                                           |
 
 #### Example
 
@@ -414,11 +422,35 @@ With `steps=false` and `annotations=true`:
   "annotation": {
     "distance": [5,5,10,5,5],
     "duration": [15,15,40,15,15],
+    "datasources": [1,0,0,0,1],
     "nodes": [49772551,49772552,49786799,49786800,49786801,49786802]
   }
 }
 ```
 
+### Annotation
+
+Annotation of the whole route leg with fine-grained information about each segment or node id.
+
+#### Properties
+
+- `distance`: The distance, in metres, between each pair of coordinates
+- `duration`: The duration between each pair of coordinates, in seconds
+- `datasources`: The index of the datasource for the speed between each pair of coordinates. `0` is the default profile, other values are supplied via `--segment-speed-file` to `osrm-contract`
+- `nodes`: The OSM node ID for each coordinate along the route, excluding the first/last user-supplied coordinates
+
+#### Example
+
+```json
+{
+  "distance": [5,5,10,5,5],
+  "duration": [15,15,40,15,15],
+  "datasources": [1,0,0,0,1],
+  "nodes": [49772551,49772552,49786799,49786800,49786801,49786802]
+}
+```
+
+
 ### RouteStep
 
 A step consists of a maneuver such as a turn or merge, followed
@@ -437,6 +469,7 @@ step.
   | geojson    | [GeoJSON `LineString`](http://geojson.org/geojson-spec.html#linestring) or [GeoJSON `Point`](http://geojson.org/geojson-spec.html#point) if it is only one coordinate (not wrapped by a GeoJSON feature)|
   
 - `name`: The name of the way along which travel proceeds.
+- `ref`: A reference number or code for the way. Optionally included, if ref data is available for the given way.
 - `pronunciation`: The pronunciation hint of the way name. Will be `undefined` if there is no pronunciation hit.
 - `destinations`: The destinations of the way. Will be `undefined` if there are no destinations.
 - `mode`: A string signifying the mode of transportation.
@@ -505,7 +538,7 @@ step.
   | `use lane`       | going straight on a specific lane                            |
   | `continue`       | Turn in direction of `modifier` to stay on the same road     |
   | `roundabout`     | traverse roundabout, has additional field `exit` with NR if the roundabout is left. `the modifier specifies the direction of entering the roundabout` |
-  | `rotary`         | a larger version of a roundabout, can offer `rotary_name` in addition to the `exit` parameter.  |
+  | `rotary`         | a larger version of a roundabout, can offer `rotary_name/rotary_pronunciation` in addition to the `exit` parameter.  |
   | `roundabout turn`| Describes a turn at a small roundabout that should be treated as normal turn. The `modifier` indicates the turn direciton. Example instruction: `At the roundabout turn left`. |
   | `notification`   | not an actual turn but a change in the driving conditions. For example the travel mode.  If the road takes a turn itself, the `modifier` describes the direction |
 
@@ -591,7 +624,7 @@ location of the StepManeuver. Further intersections are listed for every cross-w
   in the direction of driving, the bearing has to be rotated by a value of 180. The value is not supplied for `depart` maneuvers.
 - `out`: index into the bearings/entry array. Used to extract the bearing just after the turn. Namely, The clockwise angle from true north to the
   direction of travel immediately after the maneuver/passing the intersection. The value is not supplied for `arrive` maneuvers.
-- `lanes`: Array of `Lane` objects that denote the available turn lanes at the turn location
+- `lanes`: Array of `Lane` objects that denote the available turn lanes at the intersection. If no lane information is available for an intersection, the `lanes` property will not be present.
 
 #### Example
 ```
diff --git a/docs/profiles.md b/docs/profiles.md
index c36267e..dc8546a 100644
--- a/docs/profiles.md
+++ b/docs/profiles.md
@@ -32,3 +32,16 @@ Given an OpenStreetMap way, the way_function will either return nothing (meaning
 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)
 
 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.
+
+## 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.
+
+### Road Classification
+
+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.
diff --git a/docs/releasing.md b/docs/releasing.md
index bae0809..9001454 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -1,50 +1,50 @@
 # Releasing a new OSRM version
 
-Do decide if this is a major or minor version bump use: http://semver.org/
+We are using http://semver.org/ for versioning with major, minor and patch versions.
 
-What we guarantee on major version changes:
+## Guarantees
 
-- Breaking changes will be in the changelog
-- If we break an HTTP API we bump the version
+We are giving the following guarantees between versions:
 
-What we guarantee on minor version changes:
+### Major version change
 
-- HTTP API does not include breaking changes
-- C++ library API does not include breaking changes
-- node-osrm API does not include breaking changes
+- There are no guarantees about compatiblity of APIs or datasets
+- Breaking changes will be noted as `BREAKING` in the changelog
 
-What we DO NOT guarantee on minor version changes:
+### Minor version change
 
-- file format comp ability. Breakage will be listed in the changelog.
-- new turn types and fields may be introduced. How to handle this see [the HTTP API docs](http.md).
+We may introduce forward-compatible changes: query parameters and response properties may be added in responses, but existing properties may not be changed or removed. One exception to this is the addition of new turn types, which we see as forward-compatible changes.
 
-What we guarantee on patch version changes:
+- Forward-compatible HTTP API
+- Forward-compatible C++ library API
+- Forward-compatible node-osrm API
+- No compatiblity between OSRM datasets (needs new processing)
 
-- HTTP API does not include breaking changes
-- C++ library API does not include breaking changes
-- node-osrm API does not include breaking changes
-- full file format compatibility
+### Patch version change
 
-## Major or Minor release x.y
+- No change of query parameters or response formats
+- Compatible HTTP API
+- Compatible C++ library API
+- Compatible node-osrm API
+- Compatible OSRM datasets
 
-1. Make sure all tests are passing (e.g. Travis CI gives you a :thumbs_up:)
-2. Make sure `CHANGELOG.md` is up to date.
-3. Make sure the OSRM version in `CMakeLists.txt` is up to date
-4. Use an annotated tag to mark the release: `git tag vx.y.0 -a` Body of the tag description should be the changelog entries.
-5. Push tags and commits: `git push; git push --tags`
-6. Branch of the `vx.y.0` tag to create a release branch `x.y`:
-   `git branch x.y. vx.y.0; git push -u x.y:origin/x.y`
-7. Modify `.travis.yml` to allow builds for the `x.y` branch.
-8. Write a mailing-list post to osrm-talk at openstreetmap.org to announce the release
+## Release and branch management
 
-## Patch release x.y.z
+- The `master` branch is for the bleeding edge development
+- We create and maintain release branches `x.y` to control the release flow
+- No minor or major version will be released without a code-equal release candidates
+- For quality assurance, release candidates will be run on the demo server for 24 hours before releaseing the version proper
+- Patch versions may be released without a release candidate
+- We may backport fixes to older versions and release them as patch versions
 
-1. Check out the appropriate release branch x.y
-2. Make sure all fixes are listed in the changelog and included in the branch
-3. Make sure all tests are passing (e.g. Travis CI gives you a :thumbs_up:)
+## Releasing a version
+
+1. Check out the appropriate release branch `x.y`
+2. Make sure all tests are passing (e.g. Travis CI gives you a :thumbs_up:)
+3. Make sure `CHANGELOG.md` is up to date.
 4. Make sure the OSRM version in `CMakeLists.txt` is up to date
 5. Use an annotated tag to mark the release: `git tag vx.y.z -a` Body of the tag description should be the changelog entries.
 6. Push tags and commits: `git push; git push --tags`
-7. Proceede with the `node-osrm` release as outlined in the repository.
-8. Write a mailing-list post to osrm-talk at openstreetmap.org to announce the release
+8. Proceede with the `node-osrm` release as [outlined in the repository](https://github.com/Project-OSRM/node-osrm/blob/master/docs/releasing.md).
+9. If not a release-candidate: Write a mailing-list post to osrm-talk at openstreetmap.org to announce the release
 
diff --git a/example/example.cpp b/example/example.cpp
index c3008f4..3ad13e6 100644
--- a/example/example.cpp
+++ b/example/example.cpp
@@ -18,7 +18,7 @@
 
 #include <cstdlib>
 
-int main(int argc, const char *argv[]) try
+int main(int argc, const char *argv[])
 {
     if (argc < 2)
     {
@@ -34,7 +34,7 @@ int main(int argc, const char *argv[]) try
     config.use_shared_memory = false;
 
     // Routing machine with several services (such as Route, Table, Nearest, Trip, Match)
-    OSRM osrm{config};
+    const OSRM osrm{config};
 
     // The following shows how to use the Route service; configure this service
     RouteParameters params;
@@ -67,6 +67,7 @@ int main(int argc, const char *argv[]) try
 
         std::cout << "Distance: " << distance << " meter\n";
         std::cout << "Duration: " << duration << " seconds\n";
+        return EXIT_SUCCESS;
     }
     else if (status == Status::Error)
     {
@@ -78,8 +79,3 @@ int main(int argc, const char *argv[]) try
         return EXIT_FAILURE;
     }
 }
-catch (const std::exception &e)
-{
-    std::cerr << "Error: " << e.what() << std::endl;
-    return EXIT_FAILURE;
-}
diff --git a/features/bicycle/names.feature b/features/bicycle/names.feature
index b196857..38b1d2e 100644
--- a/features/bicycle/names.feature
+++ b/features/bicycle/names.feature
@@ -15,8 +15,8 @@ Feature: Bike - Street names in instructions
             | bc    | Your Way | A7  |
 
         When I route I should get
-            | from | to | route                                   |
-            | a    | c  | My Way (A6),Your Way (A7),Your Way (A7) |
+            | from | to | route                                   | ref      |
+            | a    | c  | My Way,Your Way,Your Way                | A6,A7,A7 |
 
     @unnamed
     Scenario: Bike - Use way type to describe unnamed ways
diff --git a/features/bicycle/ref.feature b/features/bicycle/ref.feature
index a9c9084..8c64232 100644
--- a/features/bicycle/ref.feature
+++ b/features/bicycle/ref.feature
@@ -13,8 +13,8 @@ Feature: Bike - Way ref
             | ab    | Utopia Drive | E7  |
 
         When I route I should get
-            | from | to | route                               |
-            | a    | b  | Utopia Drive (E7),Utopia Drive (E7) |
+            | from | to | route                               | ref   |
+            | a    | b  | Utopia Drive,Utopia Drive           | E7,E7 |
 
     Scenario: Bike - Way with only ref
         Given the node map
@@ -25,8 +25,8 @@ Feature: Bike - Way ref
             | ab    |      | E7  |
 
         When I route I should get
-            | from | to | route |
-            | a    | b  | E7,E7 |
+            | from | to | route                               | ref   |
+            | a    | b  | {highway:primary},{highway:primary} | E7,E7 |
 
     Scenario: Bike - Way with only name
         Given the node map
diff --git a/features/car/access.feature b/features/car/access.feature
index 93691d4..bcbdae8 100644
--- a/features/car/access.feature
+++ b/features/car/access.feature
@@ -5,7 +5,7 @@ Feature: Car - Restricted access
     Background:
         Given the profile "car"
 
-    Scenario: Car - Access tag hierarchy    on ways
+    Scenario: Car - Access tag hierarchy on ways
         Then routability should be
             | access | vehicle | motor_vehicle | motorcar | bothw |
             |        |         |               |          | x     |
@@ -148,3 +148,42 @@ Feature: Car - Restricted access
             | primary |      |         | no  |           | x     |
             | runway  |      |         |     | yes       |       |
             | primary |      |         |     | no        | x     |
+
+    Scenario: Car - only designated HOV ways are ignored by default
+        Then routability should be
+            | highway | hov        | bothw |
+            | primary | designated |       |
+            | primary | yes        | x     |
+            | primary | no         | x     |
+
+    Scenario: Car - a way with all lanes HOV-designated is inaccessible by default (similar to hov=designated)
+        Then routability should be
+            | highway | hov:lanes:forward      | hov:lanes:backward     | hov:lanes              | oneway | forw | backw |
+            | primary | designated             | designated             |                        |        |      |       |
+            | primary |                        | designated             |                        |        | x    |       |
+            | primary | designated             |                        |                        |        |      | x     |
+            | primary | designated\|designated | designated\|designated |                        |        |      |       |
+            | primary | designated\|no         | designated\|no         |                        |        | x    | x     |
+            | primary | yes\|no                | yes\|no                |                        |        | x    | x     |
+            | primary |                        |                        |                        |        | x    | x     |
+            | primary | designated             |                        |                        | -1     |      |       |
+            | primary |                        | designated             |                        | -1     |      | x     |
+            | primary |                        |                        | designated             | yes    |      |       |
+            | primary |                        |                        | designated             | -1     |      |       |
+            | primary |                        |                        | designated\|designated | yes    |      |       |
+            | primary |                        |                        | designated\|designated | -1     |      |       |
+            | primary |                        |                        | designated\|yes        | yes    | x    |       |
+            | primary |                        |                        | designated\|no         | -1     |      | x     |
+
+     Scenario: Car - these toll roads always work
+        Then routability should be
+            | highway | toll        | bothw |
+            | primary | no          | x     |
+            | primary | snowmobile  | x     |
+
+     # To test this we need issue #2781
+     @todo
+     Scenario: Car - only toll=yes ways are ignored by default
+        Then routability should be
+            | highway | toll        | bothw |
+            | primary | yes         |       |
diff --git a/features/car/ferry.feature b/features/car/ferry.feature
index c6adc9f..60e1b09 100644
--- a/features/car/ferry.feature
+++ b/features/car/ferry.feature
@@ -41,10 +41,10 @@ Feature: Car - Handle ferry routes
 
         When I route I should get
             | from | to | route           | modes                         | speed   |
-            | a    | g  | abc,cde,efg,efg | driving,ferry,driving,driving | 25 km/h |
-            | b    | f  | abc,cde,efg,efg | driving,ferry,driving,driving | 20 km/h |
-            | c    | e  | cde,cde         | ferry,ferry                   | 12 km/h |
-            | e    | c  | cde,cde         | ferry,ferry                   | 12 km/h |
+            | a    | g  | abc,cde,efg,efg | driving,ferry,driving,driving | 24 km/h |
+            | b    | f  | abc,cde,efg,efg | driving,ferry,driving,driving | 18 km/h |
+            | c    | e  | cde,cde         | ferry,ferry                   | 11 km/h |
+            | e    | c  | cde,cde         | ferry,ferry                   | 11 km/h |
 
     Scenario: Car - Properly handle ISO 8601 durations
         Given the node map
@@ -60,7 +60,7 @@ Feature: Car - Handle ferry routes
 
         When I route I should get
             | from | to | route           | modes                         | speed   |
-            | a    | g  | abc,cde,efg,efg | driving,ferry,driving,driving | 25 km/h |
-            | b    | f  | abc,cde,efg,efg | driving,ferry,driving,driving | 20 km/h |
-            | c    | e  | cde,cde         | ferry,ferry                   | 12 km/h |
-            | e    | c  | cde,cde         | ferry,ferry                   | 12 km/h |
+            | a    | g  | abc,cde,efg,efg | driving,ferry,driving,driving | 24 km/h |
+            | b    | f  | abc,cde,efg,efg | driving,ferry,driving,driving | 18 km/h |
+            | c    | e  | cde,cde         | ferry,ferry                   | 11 km/h |
+            | e    | c  | cde,cde         | ferry,ferry                   | 11 km/h |
diff --git a/features/car/names.feature b/features/car/names.feature
index 1dfe944..de76b25 100644
--- a/features/car/names.feature
+++ b/features/car/names.feature
@@ -15,8 +15,8 @@ Feature: Car - Street names in instructions
             | bc    | Your Way | A1  |
 
         When I route I should get
-            | from | to | route                              |
-            | a    | c  | My Way,Your Way (A1),Your Way (A1) |
+            | from | to | route                              | ref  |
+            | a    | c  | My Way,Your Way,Your Way           | ,A1,A1|
 
     Scenario: Car - A named street with pronunciation
         Given the node map
@@ -25,15 +25,32 @@ Feature: Car - Street names in instructions
             |   | c |   |
 
         And the ways
-            | nodes | name     |name:pronunciation | ref |
+            | nodes | name     |name:pronunciation  | ref |
             | ab    | My Way   |                    |     |
             | bd    | My Way   | meyeway            | A1  |
             | cd    | Your Way | yourewaye          |     |
 
         When I route I should get
-            | from | to | route              | pronunciations      |
-            | a    | d  | My Way,My Way (A1) | ,meyeway             |
-            | 1    | c  | Your Way,Your Way  | yourewaye,yourewaye  |
+            | from | to | route              | pronunciations       | ref   |
+            | a    | d  | My Way,My Way      | ,meyeway             | ,A1   |
+            | 1    | c  | Your Way,Your Way  | yourewaye,yourewaye  | ,     |
+
+    # See #2860
+    Scenario: Car - same street name but different pronunciation
+        Given the node map
+            | a | b | c |
+            |   | d |   |
+            |   | e |   |
+
+        And the ways
+            | nodes | name       | name:pronunciation |
+            | abc   | Houston St | hew-stun           |
+            | bde   | Houston St | how-stun           |
+
+        When I route I should get
+            | from | to | route                            | pronunciations             |
+            | a    | c  | Houston St,Houston St            | hew-stun,hew-stun          |
+            | a    | e  | Houston St,Houston St,Houston St | hew-stun,how-stun,how-stun |
 
     @todo
     Scenario: Car - Use way type to describe unnamed ways
@@ -48,3 +65,31 @@ Feature: Car - Street names in instructions
         When I route I should get
             | from | to | route                            |
             | a    | c  | tertiary,residential,residential |
+
+    Scenario: Inner city expressway with on road
+        Given the node map
+            | a | b |   |   |   | c | g |
+            |   |   |   |   | f |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   | d |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   | e |   |
+
+        And the ways
+            | nodes | highway      | name  | name:pronunciation |
+            | abc   | primary      | road  | roooaad            |
+            | cg    | primary      | road  | roooaad            |
+            | bfd   | trunk_link   |       |                    |
+            | cde   | trunk        | trunk | truank             |
+
+        And the relations
+            | type        | way:from | way:to | node:via | restriction   |
+            | restriction | abc      | cde    | c        | no_right_turn |
+
+       When I route I should get
+            | waypoints | route                | turns                    | pronunciations        |
+            | a,e       | road,trunk,trunk     | depart,turn right,arrive | roooaad,truank,truank |
diff --git a/features/car/restrictions.feature b/features/car/restrictions.feature
index 719ab5f..294e587 100644
--- a/features/car/restrictions.feature
+++ b/features/car/restrictions.feature
@@ -149,11 +149,27 @@ Feature: Car - Turn restrictions
             | type        | way:from | way:to | node:via | restriction    |
             | restriction | sj       | wj     | j        | only_left_turn |
 
+    Scenario: Car - Only right turn, invalid
+        Given the node map
+            |   | n |   |   |
+            | w | j | e | r |
+            |   | s |   |   |
+
+        And the ways
+            | nodes | oneway |
+            | sj    | yes    |
+            | nj    | -1     |
+            | wj    | -1     |
+            | ej    | -1     |
+            | re    | -1     |
+
+        And the relations
+            | type        | way:from | way:to | node:via | restriction   |
+            | restriction | sj       | er     | j        | only_right_on |
+
         When I route I should get
-            | from | to | route    |
-            | s    | w  | sj,wj,wj |
-            | s    | n  |          |
-            | s    | e  |          |
+            | from | to | route       |
+            | s    | r  | sj,ej,re,re |
 
     @only_turning
     Scenario: Car - Only right turn
@@ -430,3 +446,27 @@ Feature: Car - Turn restrictions
             | a    | b  | ax,xy,yb,yb |
             | b    | a  | yb,xy,ax,ax |
 
+    @specific
+    Scenario: Car - Ignore unrecognized restriction
+        Given the node map
+            |   | n |   |
+            | w | j | e |
+            |   | s |   |
+
+        And the ways
+            | nodes | oneway |
+            | sj    | yes    |
+            | nj    | -1     |
+            | wj    | -1     |
+            | ej    | -1     |
+
+        And the relations
+            | type        | way:from | way:to | node:via | restriction  |
+            | restriction | sj       | wj     | j        | yield        |
+
+        When I route I should get
+            | from | to | route    |
+            | s    | w  | sj,wj,wj |
+            | s    | n  | sj,nj,nj |
+            | s    | e  | sj,ej,ej |
+
diff --git a/features/car/service.feature b/features/car/service.feature
new file mode 100644
index 0000000..22e62d6
--- /dev/null
+++ b/features/car/service.feature
@@ -0,0 +1,13 @@
+ at routing @car @surface
+Feature: Car - Surfaces
+
+    Background:
+        Given the profile "car"
+
+    Scenario: Car - Surface should reduce speed
+        Then routability should be
+            | highway  | service           | forw       | backw       |
+            | service  | alley             | 5 km/h +-1 | 5 km/h +-1  |
+            | service  | emergency_access  |            |             |
+            | service  | driveway          | 15 km/h +-1| 15 km/h +-1 |
+
diff --git a/features/car/summaries.feature b/features/car/summaries.feature
new file mode 100644
index 0000000..38670ea
--- /dev/null
+++ b/features/car/summaries.feature
@@ -0,0 +1,94 @@
+ at routing @basic @car
+Feature: Basic Routing
+
+    Background:
+        Given the profile "car"
+        Given a grid size of 500 meters
+
+    @smallest
+    Scenario: Summaries when routing on a simple network
+        Given the node map
+            | b |   |   | f |
+            |   |   |   |   |
+            | c | d |   | g |
+            |   |   |   |   |
+            | a |   | e |   |
+
+        And the ways
+            | nodes | name   |
+            | acb   | road   |
+            | de    | 1 st   |
+            | cd    |        |
+            | dg    | blvd   |
+            | df    | street |
+
+        When I route I should get
+            | waypoints | route                               | summary                 |
+            | a,e       | road,,1 st,1 st                     | road, 1 st              |
+            | a,d,f     | road,,,street,street                | road;street             |
+            | a,e,f     | road,,1 st,1 st,1 st,street,street  | road, 1 st;1 st, street |
+
+     Scenario: Name Empty
+        Given the node map
+            | a |   | b |   |   | c |
+
+        And the ways
+            | nodes | name |
+            | ab    | road |
+            | bc    |      |
+
+        When I route I should get
+            | waypoints | route  | summary |
+            | a,c       | road,  | road    |
+
+     Scenario: Name Empty But Ref
+        Given the node map
+            | a |   | b |   |   | c |
+
+        And the ways
+            | nodes | name | ref |
+            | ab    | road |     |
+            | bc    |      | 101 |
+
+        When I route I should get
+            | waypoints | route | summary   |
+            | a,c       | road, | road, 101 |
+
+     Scenario: Only Refs
+        Given the node map
+            | a |   | b |   |   | c |
+
+        And the ways
+            | nodes | name | ref |
+            | ab    |      | 100 |
+            | bc    |      | 101 |
+
+        When I route I should get
+            | waypoints | route  | summary  |
+            | a,c       | ,      | 100, 101 |
+
+     Scenario: Single Ref
+        Given the node map
+            | a |   | b |   |   | c |
+
+        And the ways
+            | nodes | name | ref |
+            | ab    |      |     |
+            | bc    |      | 101 |
+
+        When I route I should get
+            | waypoints | route | summary |
+            | a,c       | ,,    | 101     |
+
+     Scenario: Nothing
+        Given the node map
+            | a |   | b |   |   | c |
+
+        And the ways
+            | nodes | name |
+            | ab    |      |
+            | bc    |      |
+
+        When I route I should get
+            | waypoints | route | summary |
+            | a,c       | ,     |         |
diff --git a/features/car/traffic_speeds.feature b/features/car/traffic_speeds.feature
index 84335d0..c26c695 100644
--- a/features/car/traffic_speeds.feature
+++ b/features/car/traffic_speeds.feature
@@ -2,6 +2,8 @@
 Feature: Traffic - speeds
 
     Background: Use specific speeds
+
+    Scenario: Weighting based on speed file
         Given the node locations
             | node | lat        | lon      |
             | a    | 0.1        | 0.1      |
@@ -21,27 +23,105 @@ Feature: Traffic - speeds
             | eb    | primary |
             | df    | primary |
             | fb    | primary |
-        And the speed file
+        Given the profile "testbot"
+        Given the extract extra arguments "--generate-edge-lookup"
+        Given the contract extra arguments "--segment-speed-file {speeds_file}"
+        Given the speed file
         """
-        1,2,27
-        2,1,27
+        1,2,0
+        2,1,0
         2,3,27
         3,2,27
         1,4,27
         4,1,27
         """
+        And I route I should get
+            | from | to | route          | speed   |
+            | a    | b  | ad,de,eb,eb    | 30 km/h |
+            | a    | c  | ad,dc,dc       | 31 km/h |
+            | b    | c  | bc,bc          | 27 km/h |
+            | a    | d  | ad,ad          | 27 km/h |
+            | d    | c  | dc,dc          | 36 km/h |
+            | g    | b  | fb,fb          | 36 km/h |
+            | a    | g  | ad,df,fb,fb    | 30 km/h |
 
-    Scenario: Weighting based on speed file
+
+    Scenario: Speeds that isolate a single node (a)
+        Given the node locations
+            | node | lat        | lon      |
+            | a    | 0.1        | 0.1      |
+            | b    | .05        | 0.1      |
+            | c    | 0.0        | 0.1      |
+            | d    | .05        | .03      |
+            | e    | .05        | .066     |
+            | f    | .075       | .066     |
+            | g    | .075       | 0.1      |
+            | h    | 2.075      | 19.1     |
+        And the ways
+            | nodes | highway |
+            | ab    | primary |
+            | ad    | primary |
+            | bc    | primary |
+            | dc    | primary |
+            | de    | primary |
+            | eb    | primary |
+            | df    | primary |
+            | fb    | primary |
         Given the profile "testbot"
         Given the extract extra arguments "--generate-edge-lookup"
-        Given the contract extra arguments "--segment-speed-file speeds.csv"
+        Given the contract extra arguments "--segment-speed-file {speeds_file}"
+        Given the speed file
+        """
+        1,2,0
+        2,1,0
+        2,3,27
+        3,2,27
+        1,4,0
+        4,1,0
+        """
         And I route I should get
-            | from | to | route    | speed   |
-            | a    | b  | ab,ab    | 27 km/h |
-            | a    | c  | ab,bc,bc | 27 km/h |
-            | b    | c  | bc,bc    | 27 km/h |
-            | a    | d  | ad,ad    | 27 km/h |
-            | d    | c  | dc,dc    | 36 km/h |
-            | g    | b  | ab,ab    | 27 km/h |
-            | a    | g  | ab,ab    | 27 km/h |
+            | from | to | route          | speed   |
+            | a    | b  | fb,fb          | 36 km/h |
+            | a    | c  | fb,bc,bc       | 30 km/h |
+            | b    | c  | bc,bc          | 27 km/h |
+            | a    | d  | fb,df,df       | 36 km/h |
+            | d    | c  | dc,dc          | 36 km/h |
+            | g    | b  | fb,fb          | 36 km/h |
+            | a    | g  | fb,fb          | 36 km/h |
 
+    Scenario: Verify that negative values cause an error, they're not valid at all
+        Given the node locations
+            | node | lat        | lon      |
+            | a    | 0.1        | 0.1      |
+            | b    | .05        | 0.1      |
+            | c    | 0.0        | 0.1      |
+            | d    | .05        | .03      |
+            | e    | .05        | .066     |
+            | f    | .075       | .066     |
+            | g    | .075       | 0.1      |
+            | h    | 1.075      | 10.1     |
+        And the ways
+            | nodes | highway |
+            | ab    | primary |
+            | ad    | primary |
+            | bc    | primary |
+            | dc    | primary |
+            | de    | primary |
+            | eb    | primary |
+            | df    | primary |
+            | fb    | primary |
+        Given the profile "testbot"
+        Given the extract extra arguments "--generate-edge-lookup"
+        Given the speed file
+        """
+        1,2,-10
+        2,1,-20
+        2,3,27
+        3,2,27
+        1,4,-3
+        4,1,-5
+        """
+        And the data has been extracted
+        When I try to run "osrm-contract --segment-speed-file {speeds_file} {processed_file}"
+        And stderr should contain "malformed"
+        And it should exit with an error
diff --git a/features/car/traffic_turn_penalties.feature b/features/car/traffic_turn_penalties.feature
index 658f780..cb3907a 100644
--- a/features/car/traffic_turn_penalties.feature
+++ b/features/car/traffic_turn_penalties.feature
@@ -33,19 +33,19 @@ Feature: Traffic - turn penalties
             | from | to | route           | speed   | time      |
             | a    | h  | ad,dhk,dhk      | 63 km/h | 11.5s +-1 |
                                                                   # straight
-            | i    | g  | fim,fg,fg       | 59 km/h | 12s  +-1  |
+            | i    | g  | fim,fg,fg       | 53 km/h | 13.5s +-1 |
                                                                   # right
-            | a    | e  | ad,def,def      | 57 km/h | 12.5s +-1 |
+            | a    | e  | ad,def,def      | 43 km/h | 16.7s +-1 |
                                                                   # left
             | c    | g  | cd,def,fg,fg    | 63 km/h | 23s +-1   |
                                                                   # double straight
-            | p    | g  | mp,fim,fg,fg    | 61 km/h | 23.5s +-1 |
+            | p    | g  | mp,fim,fg,fg    | 58 km/h | 24.9s +-1 |
                                                                   # straight-right
-            | a    | l  | ad,dhk,klm,klm  | 60 km/h | 24s +-1   |
+            | a    | l  | ad,dhk,klm,klm  | 51 km/h | 28.1s +-1 |
                                                                   # straight-left
-            | l    | e  | klm,dhk,def,def | 59 km/h | 24.5s +-1 |
+            | l    | e  | klm,dhk,def,def | 53 km/h | 27s +-1   |
                                                                   # double right
-            | g    | n  | fg,fim,mn,mn    | 57 km/h | 25s +-1   |
+            | g    | n  | fg,fim,mn,mn    | 43 km/h | 33.4s +-1 |
                                                                   # double left
 
     Scenario: Weighting based on turn penalty file
@@ -53,12 +53,12 @@ Feature: Traffic - turn penalties
             """
             9,6,7,1.8
             9,13,14,24.5
-            8,4,3,26
+            8,4,3,30
             12,11,8,9
-            8,11,12,13
+            8,11,12,23
             1,4,5,-0.2
             """
-        And the contract extra arguments "--turn-penalty-file penalties.csv"
+        And the contract extra arguments "--turn-penalty-file {penalties_file}"
         When I route I should get
             | from | to | route                 | speed   | time      |
             | a    | h  | ad,dhk,dhk            | 63 km/h | 11.5s +-1 |
@@ -71,17 +71,17 @@ Feature: Traffic - turn penalties
                                                                               # double straight
             | p    | g  | mp,fim,fg,fg          | 59 km/h | 24.5s +-1 |
                                                                               # straight-right - ifg penalty
-            | a    | l  | ad,def,fim,klm,klm    | 61 km/h | 35.5s +-1 |
+            | a    | l  | ad,def,fim,klm,klm    | 57 km/h | 38.2s +-1 |
                                                                               # was straight-left - forced around by hkl penalty
-            | l    | e  | klm,fim,def,def       | 57 km/h | 25s +-1   |
+            | l    | e  | klm,fim,def,def       | 43 km/h | 33.4s +-1 |
                                                                               # double right - forced left by lkh penalty
-            | g    | n  | fg,fim,mn,mn          | 30 km/h | 47.5s +-1 |
+            | g    | n  | fg,fim,mn,mn          | 27 km/h | 52.6s +-1 |
                                                                               # double left - imn penalty
-            | j    | c  | jk,klm,fim,def,cd,cd  | 60 km/h | 48s +-1   |
+            | j    | c  | jk,klm,fim,def,cd,cd  | 51 km/h | 56.2s +-1 |
                                                                               # double left - hdc penalty ever so slightly higher than imn; forces all the way around
 
     Scenario: Too-negative penalty clamps, but does not fail
-        Given the contract extra arguments "--turn-penalty-file penalties.csv"
+        Given the contract extra arguments "--turn-penalty-file {penalties_file}"
         And the profile "testbot"
         And the turn penalty file
             """
diff --git a/features/foot/names.feature b/features/foot/names.feature
index 6a28aaf..2c3d76b 100644
--- a/features/foot/names.feature
+++ b/features/foot/names.feature
@@ -16,7 +16,7 @@ Feature: Foot - Street names in instructions
 
         When I route I should get
             | from | to | route                                   |
-            | a    | c  | My Way (A6),Your Way (B7),Your Way (B7) |
+            | a    | c  | My Way,Your Way,Your Way                |
 
     @unnamed
     Scenario: Foot - Use way type to describe unnamed ways
diff --git a/features/foot/ref.feature b/features/foot/ref.feature
index 39bc3e8..a99c24b 100644
--- a/features/foot/ref.feature
+++ b/features/foot/ref.feature
@@ -14,7 +14,7 @@ Feature: Foot - Way ref
 
         When I route I should get
             | from | to | route                               |
-            | a    | b  | Utopia Drive (E7),Utopia Drive (E7) |
+            | a    | b  | Utopia Drive,Utopia Drive           |
 
     Scenario: Foot - Way with only ref
         Given the node map
@@ -25,8 +25,8 @@ Feature: Foot - Way ref
             | ab    |      | E7  |
 
         When I route I should get
-            | from | to | route |
-            | a    | b  | E7,E7 |
+            | from | to | route                               |
+            | a    | b  | {highway:primary},{highway:primary} |
 
     Scenario: Foot - Way with only name
         Given the node map
diff --git a/features/guidance/advanced-lanes.feature b/features/guidance/advanced-lanes.feature
new file mode 100644
index 0000000..17bc97f
--- /dev/null
+++ b/features/guidance/advanced-lanes.feature
@@ -0,0 +1,236 @@
+ at routing @guidance @turn-lanes
+Feature: Turn Lane Guidance
+
+    Background:
+        Given the profile "car"
+        Given a grid size of 3 meters
+
+    @sliproads
+    Scenario: Separate Turn Lanes
+        Given the node map
+            |   |   |   |   |   |   |   | e |   |
+            | a |   |   | b |   |   |   | c | g |
+            |   |   |   |   |   |   |   | d |   |
+            |   |   |   |   |   |   |   | f |   |
+
+        And the ways
+            | nodes | turn:lanes:forward | name     | oneway |
+            | ab    |                    | in       | yes    |
+            | bc    | left\|through      | in       | yes    |
+            | bd    | right              | in       | yes    |
+            | ec    |                    | cross    | no     |
+            | cd    |                    | cross    | no     |
+            | df    |                    | cross    | no     |
+            | cg    |                    | straight | no     |
+
+        And the relations
+            | type        | way:from | way:to | node:via | restriction   |
+            | restriction | bd       | cd     | d        | no_left_turn  |
+            | restriction | bc       | cd     | c        | no_right_turn |
+
+       When I route I should get
+            | waypoints | route                | turns                           | lanes                                  |
+            | a,e       | in,cross,cross       | depart,turn left,arrive         | ,left:true straight:false right:false, |
+            | a,g       | in,straight,straight | depart,new name straight,arrive | ,left:false straight:true right:false, |
+            | a,f       | in,cross,cross       | depart,turn right,arrive        | ,left:false straight:false right:true, |
+
+    @sliproads
+    Scenario: Separate Turn Lanes
+        Given the node map
+            |   |   |   |   |   |   |   | e |   |
+            | a |   |   | b |   |   |   | c | g |
+            |   |   |   |   |   |   |   | d |   |
+            |   |   |   |   |   |   |   | f |   |
+
+        And the ways
+            | nodes | turn:lanes:forward | name     | oneway |
+            | ab    |                    | in       | yes    |
+            | bc    | left\|through      | in       | yes    |
+            | bd    | right              | in       | yes    |
+            | ec    |                    | cross    | no     |
+            | cd    |                    | cross    | no     |
+            | df    |                    | cross    | no     |
+            | cg    |                    | straight | no     |
+
+        And the relations
+            | type        | way:from | way:to | node:via | restriction   |
+            | restriction | bd       | cd     | d        | no_left_turn  |
+            | restriction | bc       | cd     | c        | no_right_turn |
+
+       When I route I should get
+            | waypoints | route                | turns                           | lanes                                  |
+            | a,e       | in,cross,cross       | depart,turn left,arrive         | ,left:true straight:false right:false, |
+            | a,g       | in,straight,straight | depart,new name straight,arrive | ,left:false straight:true right:false, |
+            | a,f       | in,cross,cross       | depart,turn right,arrive        | ,left:false straight:false right:true, |
+
+    @sliproads
+    Scenario: Separate Turn Lanes Next to other turns
+        Given the node map
+            |   |   |   |   |   |   |   | e |   |
+            | a |   |   | b |   |   |   | c | g |
+            |   |   |   |   |   |   |   | d |   |
+            |   |   |   |   |   |   |   | f |   |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |
+            | i |   |   | h |   |   |   | j |   |
+
+        And the ways
+            | nodes | turn:lanes:forward | name     | oneway |
+            | ab    |                    | in       | yes    |
+            | bc    | left\|through      | in       | yes    |
+            | bd    | right              | in       | yes    |
+            | ec    |                    | cross    | no     |
+            | cd    |                    | cross    | no     |
+            | df    |                    | cross    | no     |
+            | cg    |                    | straight | no     |
+            | bh    | left\|right        | turn     | yes    |
+            | ihj   |                    | other    | no     |
+
+        And the relations
+            | type        | way:from | way:to | node:via | restriction   |
+            | restriction | bd       | cd     | d        | no_left_turn  |
+            | restriction | bc       | cd     | c        | no_right_turn |
+
+       When I route I should get
+            | waypoints | route                | turns                               | lanes                                  |
+            | a,e       | in,cross,cross       | depart,turn left,arrive             | ,left:true straight:false right:false, |
+            | a,g       | in,straight,straight | depart,new name straight,arrive     | ,left:false straight:true right:false, |
+            | a,f       | in,cross,cross       | depart,turn right,arrive            | ,left:false straight:false right:true, |
+            | a,j       | in,turn,other,other  | depart,turn right,turn left,arrive  | ,,left:true right:false,               |
+            | a,i       | in,turn,other,other  | depart,turn right,turn right,arrive | ,,left:false right:true,               |
+
+    @todo @2654 @none
+    #https://github.com/Project-OSRM/osrm-backend/issues/2645
+    #http://www.openstreetmap.org/export#map=19/52.56054/13.32152
+    Scenario: Kurt-Schuhmacher-Damm
+        Given the node map
+            |   |   |   | g |   | f |
+            |   |   |   |   |   |   |
+            | j |   |   | h |   | e |
+            |   |   |   |   |   |   |
+            | a |   |   | b |   | c |
+            |   |   |   | i |   | d |
+
+        And the ways
+            | nodes | name | highway        | oneway | turn:lanes        |
+            | ab    |      | motorway_link  | yes    | left\|none\|right |
+            | bc    |      | primary_link   | yes    |                   |
+            | cd    | ksd  | secondary      | yes    |                   |
+            | cef   | ksd  | primary        | yes    |                   |
+            | hj    |      | motorway_link  | yes    |                   |
+            | eh    |      | secondary_link | yes    |                   |
+            | gh    | ksd  | primary        | yes    |                   |
+            | hbi   | ksd  | secondary      | yes    |                   |
+
+        When I route I should get
+            | waypoints | route    | turns                    | lanes                             |
+            | a,f       | ,ksd,ksd | depart,turn left,arrive  | ,left:true none:true right:false, |
+            | a,i       | ,ksd,ksd | depart,turn right,arrive | ,left:false none:true right:true, |
+
+    @todo @2650 @sliproads
+    #market and haight in SF, restricted turn
+    #http://www.openstreetmap.org/#map=19/37.77308/-122.42238
+    Scenario: Market/Haight without Through Street
+        Given the node map
+            |   |   |   |   |   |   |   | g | j |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   | f |
+            |   |   |   |   |   |   |   |   | e |   |
+            |   |   |   |   |   |   |   | d |   |   |
+            | a |   |   |   |   |   | b | c |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   | l |   |   | h | i |   |
+
+        And the ways
+            | nodes | name   | highway     | oneway | turn:lanes:forward |
+            | ab    | ghough | secondary   | yes    |                    |
+            | bc    | ghough | secondary   | yes    | through\|through   |
+            | bd    | ghough | secondary   | yes    | none\|through      |
+            | def   | ghough | secondary   | yes    |                    |
+            | gd    | market | primary     | yes    |                    |
+            | dc    | market | primary     | yes    |                    |
+            | ch    | market | primary     | yes    |                    |
+            | iej   | market | primary     | yes    |                    |
+            | bl    | haight | residential | yes    | left\|none         |
+
+        And the relations
+            | type        | way:from | way:to | node:via | restriction   |
+            | relation    | bd       | dc     | d        | no_right_turn |
+
+        When I route I should get
+            | waypoints | route                | turns                              | lanes                                                    |
+            | a,l       | ghough,haight,haight | depart,turn right,arrive           | ,none:false straight:false straight:false straight:true, |
+            | a,h       | ghough,market,market | depart,turn slight right,arrive    | ,none:false straight:false straight:true straight:true,  |
+            | a,j       | ghough,market,market | depart,turn left,arrive            | ,none:true straight:false straight:false straight:false, |
+            | a,f       | ghough,ghough,ghough | depart,continue slight left,arrive | ,none:true straight:true straight:false straight:false,  |
+
+    @todo @2650 @sliproads
+    #market and haight in SF, unrestricted
+    #http://www.openstreetmap.org/#map=19/37.77308/-122.42238
+    Scenario: Market/Haight without Through Street
+        Given the node map
+            |   |   |   |   |   |   |   | g | j |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   | f |
+            |   |   |   |   |   |   |   |   | e |   |
+            |   |   |   |   |   |   |   | d |   |   |
+            | a |   |   |   |   |   | b | c |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   | l |   |   | h | i |   |
+
+        And the ways
+            | nodes | name   | highway     | oneway | turn:lanes:forward |
+            | ab    | ghough | secondary   | yes    |                    |
+            | bc    | ghough | secondary   | yes    | through\|through   |
+            | bd    | ghough | secondary   | yes    | none\|through      |
+            | def   | ghough | secondary   | yes    |                    |
+            | gd    | market | primary     | yes    |                    |
+            | dc    | market | primary     | yes    |                    |
+            | ch    | market | primary     | yes    |                    |
+            | iej   | market | primary     | yes    |                    |
+            | bl    | haight | residential | yes    | left\|none         |
+
+        When I route I should get
+            | waypoints | route                | turns                              | lanes                                                    |
+            | a,l       | ghough,haight,haight | depart,turn right,arrive           | ,none:false straight:false straight:false straight:true, |
+            | a,h       | ghough,market,market | depart,turn slight right,arrive    | ,none:false straight:false straight:true straight:true,  |
+            | a,j       | ghough,market,market | depart,turn left,arrive            | ,none:true straight:false straight:false straight:false, |
+            | a,f       | ghough,ghough,ghough | depart,continue slight left,arrive | ,none:true straight:true straight:false straight:false,  |
+
+    Scenario: Check sliproad handler loop's exit condition, Issue #2896
+      # http://www.openstreetmap.org/way/198481519
+        Given the node locations
+            | node | lat        | lon         |
+            | a    | 7.6125350  | 126.5708309 |
+            | b    | 7.6125156  | 126.5707219 |
+            | c    | 7.6125363  | 126.5708337 |
+
+        And the ways
+            | nodes | name |
+            | cbac  |      |
+
+        When I route I should get
+            | from | to | route | turns         |
+            | a    | c  | ,     | depart,arrive |
diff --git a/features/guidance/anticipate-lanes.feature b/features/guidance/anticipate-lanes.feature
index cb77895..e235f2f 100644
--- a/features/guidance/anticipate-lanes.feature
+++ b/features/guidance/anticipate-lanes.feature
@@ -135,9 +135,9 @@ Feature: Turn Lane Guidance
             | cj    |                    | 1     | motorway_link | yes    | xbcj |
 
        When I route I should get
-            | waypoints | route         | turns                                             | lanes                           |
-            | a,i       | ab,xbcj,ci,ci | depart,merge slight left,turn slight right,arrive | ,,none:false slight right:true, |
-            | a,j       | ab,xbcj,xbcj  | depart,merge slight left,arrive                   | ,,                              |
+            | waypoints | route        | turns                           | lanes                          |
+            | a,i       | ab,ci,ci     | depart,turn slight right,arrive | ,none:false slight right:true, |
+            | a,j       | ab,xbcj      | depart,arrive                   | ,                              |
 
 
     @anticipate
@@ -257,9 +257,172 @@ Feature: Turn Lane Guidance
 
        When I route I should get
             | waypoints | route                 | turns                                                   | lanes                                                                                                                                                  |
-            | a,f       | abx,bcy,cdz,dew,ef,ef | depart,turn right,turn left,turn right,turn left,arrive | ,straight:false right:false right:true right:false,left:false left:true straight:false,straight:false right:true right:false,left:true straight:false, |
-
-    @anticipate @todo @bug @2661
+            | a,f       | abx,bcy,cdz,dew,ef,ef | depart,turn right,turn left,turn right,turn left,arrive | ,straight:false right:true right:false right:false,left:true left:false straight:false,straight:false right:true right:false,left:true straight:false, |
+
+       @anticipate
+       Scenario: Anticipate Lanes for through, through with lanes
+           Given the node map
+               |   |   |   | f | g |   |
+               |   |   |   |   |   |   |
+               | a | b | c | d |   | e |
+               |   |   |   |   |   |   |
+               |   |   |   | h | i |   |
+
+           And the ways
+               | nodes | turn:lanes:forward                     | name |
+               | ab    |                                        | main |
+               | bc    | left\|through\|through\|through\|right | main |
+               | cd    | left\|through\|right                   | main |
+               | de    |                                        | main |
+               | cf    |                                        | off  |
+               | ch    |                                        | off  |
+               | dg    |                                        | off  |
+               | di    |                                        | off  |
+
+          When I route I should get
+               | waypoints | route          | turns                           | lanes                                                                |
+               | a,e       | main,main,main | depart,use lane straight,arrive | ,left:false straight:false straight:true straight:false right:false, |
+
+       @anticipate
+       Scenario: Anticipate Lanes for through and collapse multiple use lanes
+           Given the node map
+               |   |   | e | f | g |
+               |   |   |   |   |   |
+               | a | b | c | d |   |
+               |   |   |   |   |   |
+               |   |   | h | i | j |
+
+           And the ways
+               | nodes | turn:lanes:forward                     | name |
+               | ab    | left\|through\|through\|right          | main |
+               | bc    | left\|through\|through\|right          | main |
+               | cd    | left\|through\|through\|through\|right | main |
+               | be    |                                        | off  |
+               | bh    |                                        | off  |
+               | cf    |                                        | off  |
+               | ci    |                                        | off  |
+               | dg    |                                        | off  |
+               | dj    |                                        | off  |
+
+          When I route I should get
+               | waypoints | route     | turns         | lanes |
+               | a,c       | main,main | depart,arrive | ,     |
+               | a,d       | main,main | depart,arrive | ,     |
+
+       @anticipate
+       Scenario: Anticipate Lanes for through followed by left/right
+           Given the node map
+               |   |   | f | g |   |
+               |   |   |   |   | d |
+               | a | b | c | x |   |
+               |   |   |   |   | e |
+               |   |   | h | i |   |
+
+           And the ways
+               | nodes | turn:lanes:forward                              | name  |
+               | ab    | left\|through\|through\|through\|through\|right | main  |
+               | bc    | left\|through\|through\|right                   | main  |
+               | cx    | left\|right                                     | main  |
+               | xd    |                                                 | left  |
+               | xe    |                                                 | right |
+               | bf    |                                                 | off   |
+               | bh    |                                                 | off   |
+               | cg    |                                                 | off   |
+               | ci    |                                                 | off   |
+
+          When I route I should get
+               | waypoints | route                      | turns                                                        | lanes                                                                                                                                                         |
+               | a,d       | main,main,main,left,left   | depart,use lane straight,use lane straight,turn left,arrive  | ,left:false straight:false straight:true straight:false straight:false right:false,left:false straight:true straight:false right:false,left:true right:false, |
+               | a,e       | main,main,main,right,right | depart,use lane straight,use lane straight,turn right,arrive | ,left:false straight:false straight:false straight:true straight:false right:false,left:false straight:false straight:true right:false,left:false right:true, |
+
+       @anticipate
+       Scenario: Anticipate Lanes for through with turn before / after
+           Given the node map
+               | a | b | c |
+               |   | d |   |
+               | f | e | g |
+               |   | h |   |
+               | j | i | l |
+
+           And the ways
+               | nodes | turn:lanes:forward                                           | name  | oneway |
+               | ab    | right\|right\|right\|right                                   | ab    | yes    |
+               | cb    | left\|left\|left\|left                                       | cb    | yes    |
+               | bd    |                                                              | bdehi |        |
+               | de    | left\|left\|through\|through\|through\|through\|right\|right | bdehi |        |
+               | ef    |                                                              | ef    |        |
+               | eg    |                                                              | eg    |        |
+               | eh    |                                                              | bdehi |        |
+               | hi    | left\|left\|right\|right                                     | bdehi |        |
+               | ij    |                                                              | ij    |        |
+               | il    |                                                              | il    |        |
+
+          When I route I should get
+               | waypoints | route                | turns                                                 | lanes                                                                                                                                                                                               | #           |
+               | a,f       | ab,bdehi,ef,ef       | depart,turn right,turn right,arrive                   | ,right:false right:false right:true right:true,left:false left:false straight:false straight:false straight:false straight:false right:true right:true,                                             |             |
+               | a,g       | ab,bdehi,eg,eg       | depart,turn right,turn left,arrive                    | ,right:true right:true right:false right:false,left:true left:true straight:false straight:false straight:false straight:false right:false right:false,                                             |             |
+               | a,j       | ab,bdehi,bdehi,ij,ij | depart,turn right,use lane straight,turn right,arrive | ,right:true right:true right:false right:false,left:false left:false straight:false straight:false straight:true straight:true right:false right:false,left:false left:false right:true right:true, |             |
+               | a,l       | ab,bdehi,bdehi,il,il | depart,turn right,use lane straight,turn left,arrive  | ,right:false right:false right:true right:true,left:false left:false straight:true straight:true straight:false straight:false right:false right:false,left:true left:true right:false right:false, | not perfect |
+               | c,g       | cb,bdehi,eg,eg       | depart,turn left,turn left,arrive                     | ,left:true left:true left:false left:false,left:true left:true straight:false straight:false straight:false straight:false right:false right:false,                                                 |             |
+               | c,f       | cb,bdehi,ef,ef       | depart,turn left,turn right,arrive                    | ,left:false left:false left:true left:true,left:false left:false straight:false straight:false straight:false straight:false right:true right:true,                                                 |             |
+               | c,l       | cb,bdehi,bdehi,il,il | depart,turn left,use lane straight,turn left,arrive   | ,left:false left:false left:true left:true,left:false left:false straight:true straight:true straight:false straight:false right:false right:false,left:true left:true right:false right:false,     |             |
+               | c,j       | cb,bdehi,bdehi,ij,ij | depart,turn left,use lane straight,turn right,arrive  | ,left:true left:true left:false left:false,left:false left:false straight:false straight:false straight:true straight:true right:false right:false,left:false left:false right:true right:true,     | not perfect |
+
+       @anticipate
+       Scenario: Anticipate Lanes for turns with through before and after
+           Given the node map
+               | a | b | q |   | s | h | i |
+               |   |   | e | f | g |   |   |
+               | c | d | r |   | t | j | k |
+
+           And the ways
+               | nodes | turn:lanes:forward                              | name |
+               | ab    | through\|right\|right\|right                    | top  |
+               | be    |                                                 | top  |
+               | bq    |                                                 | off  |
+               | ef    | left\|through\|through\|through\|through\|right | main |
+               | fg    | left\|left\|right\|right                        | main |
+               | fs    |                                                 | off  |
+               | ft    |                                                 | off  |
+               | gh    |                                                 | top  |
+               | hi    |                                                 | top  |
+               | cd    | left\|left\|left\|through                       | bot  |
+               | de    |                                                 | bot  |
+               | dr    |                                                 | off  |
+               | gj    |                                                 | bot  |
+               | jk    |                                                 | bot  |
+
+          When I route I should get
+               | waypoints | route                 | turns                                                  | lanes                                                                                                                                                                           |
+               | a,i       | top,main,main,top,top | depart,turn right,use lane straight,turn left,arrive   | ,straight:false right:false right:true right:true,left:false straight:true straight:true straight:false straight:false right:false,left:true left:true right:false right:false, |
+               | a,k       | top,main,main,bot,bot | depart,turn right,use lane straight,turn right,arrive  | ,straight:false right:true right:true right:false,left:false straight:false straight:false straight:true straight:true right:false,left:false left:false right:true right:true, |
+               | c,i       | bot,main,main,top,top | depart,turn left,use lane straight,turn left,arrive    | ,left:false left:true left:true straight:false,left:false straight:true straight:true straight:false straight:false right:false,left:true left:true right:false right:false,    |
+               | c,k       | bot,main,main,bot,bot | depart,turn left,use lane straight,turn right,arrive   | ,left:true left:true left:false straight:false,left:false straight:false straight:false straight:true straight:true right:false,left:false left:false right:true right:true,    |
+
+       @anticipate
+       Scenario: Anticipate Lanes for turn between throughs
+           Given the node map
+               |   | q |   |   |
+               | a | b | c | s |
+               |   | r | d | t |
+               |   |   | e |   |
+
+           And the ways
+               | nodes | turn:lanes:forward                                       | name |
+               | ab    | left\|through\|through\|through\|through\|through\|right | main |
+               | bq    |                                                          | off  |
+               | br    |                                                          | off  |
+               | bc    | through\|through\|right\|right\|right                    | main |
+               | cs    |                                                          | off  |
+               | cd    | left\|through\|through                                   | main |
+               | de    |                                                          | main |
+               | dt    |                                                          | off  |
+
+          When I route I should get
+               | waypoints | route               | turns                                          | lanes                                                                                                                                                             |
+               | a,e       | main,main,main,main | depart,use lane straight,continue right,arrive | ,left:false straight:false straight:false straight:false straight:true straight:true right:false,straight:false straight:false right:false right:true right:true, |
+
+    @anticipate @todo @2661
     Scenario: Anticipate with lanes in roundabout: roundabouts as the unit of anticipation
         Given the node map
             |   |   | e |   |   |
@@ -271,20 +434,20 @@ Feature: Turn Lane Guidance
             |   |   | i |   |   |
 
         And the ways
-            | nodes | turn:lanes:forward                      | highway | junction   | #   |
+            | nodes | turn:lanes:forward                       | highway | junction   | #   |
             | ab    | slight_right\|slight_right\|slight_right | primary |            |     |
             | bc    | slight_left\|slight_right\|slight_right  | primary | roundabout | top |
-            | cd    |                                         | primary | roundabout | top |
-            | de    |                                         | primary | roundabout | top |
-            | eb    |                                         | primary | roundabout | top |
-            | df    |                                         | primary |            |     |
-            | cg    | slight_right\|slight_right              | primary |            |     |
-            | gh    | slight_left\|slight_right               | primary | roundabout | bot |
-            | hi    |                                         | primary | roundabout | bot |
-            | ij    | slight_left\|slight_right               | primary | roundabout | bot |
-            | jg    |                                         | primary | roundabout | bot |
-            | hk    |                                         | primary |            |     |
-            | jl    |                                         | primary |            |     |
+            | cd    |                                          | primary | roundabout | top |
+            | de    |                                          | primary | roundabout | top |
+            | eb    |                                          | primary | roundabout | top |
+            | df    |                                          | primary |            |     |
+            | cg    | slight_right\|slight_right               | primary |            |     |
+            | gh    | slight_left\|slight_right                | primary | roundabout | bot |
+            | hi    |                                          | primary | roundabout | bot |
+            | ij    | slight_left\|slight_right                | primary | roundabout | bot |
+            | jg    |                                          | primary | roundabout | bot |
+            | hk    |                                          | primary |            |     |
+            | jl    |                                          | primary |            |     |
 
         When I route I should get
             | #           | waypoints | route       | turns                                             | lanes                                                                                          |
@@ -301,20 +464,20 @@ Feature: Turn Lane Guidance
             |   |   | c |   |   |
 
         And the ways
-            | nodes | turn:lanes:forward                    | highway | junction   |
-            | ab    |                                       | primary |            |
-            | bc    |                                       | primary | roundabout |
+            | nodes | turn:lanes:forward                     | highway | junction   |
+            | ab    |                                        | primary |            |
+            | bc    |                                        | primary | roundabout |
             | cd    | slight_left\|slight_left\|slight_right | primary | roundabout |
-            | de    |                                       | primary | roundabout |
-            | eb    |                                       | primary | roundabout |
-            | df    |                                       | primary |            |
+            | de    |                                        | primary | roundabout |
+            | eb    |                                        | primary | roundabout |
+            | df    |                                        | primary |            |
 
         When I route I should get
             | waypoints | route    | turns                                                 | lanes                                                    |
             | a,f       | ab,df,df | depart,roundabout-exit-1,use lane slight right,arrive | ,,slight left:false slight left:false slight right:true, |
 
     @anticipate
-    Scenario: Anticipate with lanes in roundabout where we stay on the roundabout for multiple exits
+    Scenario: No Lanes for Roundabouts, see #2626
         Given the node map
             |   |   | a |   |   |
             |   |   | b |   |   |
@@ -331,7 +494,7 @@ Feature: Turn Lane Guidance
             | cd    |                            | primary | roundabout |
             | de    |                            | primary | roundabout |
             | ef    |                            | primary | roundabout |
-            | fg    | slight_right               | primary | roundabout |
+            | fg    | through\|slight_right      | primary | roundabout |
             | gb    |                            | primary | roundabout |
             | gh    |                            | primary |            |
             | cx    |                            | primary |            |
@@ -340,11 +503,11 @@ Feature: Turn Lane Guidance
             | fy    |                            | primary |            |
 
         When I route I should get
-            | waypoints | route    | turns                           | lanes                                  |
-            | a,h       | ab,gh,gh | depart,roundabout-exit-5,arrive | ,slight right:false slight right:true, |
+            | waypoints | route    | turns                           | lanes |
+            | a,h       | ab,gh,gh | depart,roundabout-exit-5,arrive | ,,    |
 
     @anticipate
-    Scenario: Departing or arriving inside a roundabout does not yet anticipate lanes
+    Scenario: No Lanes for Roundabouts, see #2626
         Given the node map
             |   |   | a |   |   |
             | x | b |   | d | y |
@@ -360,13 +523,44 @@ Feature: Turn Lane Guidance
             | da    |                            | primary | roundabout | roundabout |
 
         When I route I should get
-            | waypoints | route                    | turns                                   | lanes                                  |
-            | x,y       | xb,dy,dy                 | depart,roundabout-exit-1,arrive         | ,slight right:false slight right:true, |
-            | x,c       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,slight right:true slight right:true,  |
-            | x,a       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,slight right:true slight right:true,  |
+            | waypoints | route                    | turns                                   | lanes |
+            | x,y       | xb,dy,dy                 | depart,roundabout-exit-1,arrive         | ,,    |
+            | x,c       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,,    |
+            | x,a       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,,    |
+
+    @anticipate
+    Scenario: No Lanes for Roundabouts, see #2626
+        Given the profile "lhs"
+        And the node map
+            |   |   | a |   |   |
+            |   |   | b |   |   |
+            | h | c |   | g |   |
+            |   |   |   |   |   |
+            |   | d |   | f |   |
+            |   |   | e |   |   |
+            | x |   |   |   | y |
+
+        And the ways
+            | nodes | turn:lanes:forward         | highway | junction   |
+            | ab    | slight_left\|slight_left   | primary |            |
+            | bg    |                            | primary | roundabout |
+            | gf    |                            | primary | roundabout |
+            | fe    |                            | primary | roundabout |
+            | ed    |                            | primary | roundabout |
+            | dc    | slight_left                | primary | roundabout |
+            | cb    |                            | primary | roundabout |
+            | ch    |                            | primary |            |
+            | ex    |                            | primary |            |
+            | dx    |                            | primary |            |
+            | gy    |                            | primary |            |
+            | fy    |                            | primary |            |
+
+        When I route I should get
+            | waypoints | route      | turns                           | lanes |
+            | a,h       | ab,ch,ch   | depart,roundabout-exit-5,arrive | ,,    |
 
     @anticipate
-    Scenario: Departing or arriving inside a roundabout does not yet anticipate lanes (BIG version)
+    Scenario: No Lanes for Roundabouts, see #2626
         Given the node map
             |   |   | a |   |   |
             | x | b |   | d | y |
@@ -416,13 +610,13 @@ Feature: Turn Lane Guidance
             | da    |                            | primary | roundabout | roundabout |
 
         When I route I should get
-            | waypoints | route                    | turns                                   | lanes                                  |
-            | x,y       | xb,dy,dy                 | depart,roundabout-exit-1,arrive         | ,slight right:false slight right:true, |
-            | x,c       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,slight right:true slight right:true,  |
-            | x,a       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,slight right:true slight right:true,  |
+            | waypoints | route                    | turns                                   | lanes |
+            | x,y       | xb,dy,dy                 | depart,roundabout-exit-1,arrive         | ,,    |
+            | x,c       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,,    |
+            | x,a       | xb,roundabout,roundabout | depart,roundabout-exit-undefined,arrive | ,,    |
 
-    @anticipate
-    Scenario: Anticipate Lanes for turns before and / or after roundabout
+    @anticipate @todo @2032
+    Scenario: No Lanes for Roundabouts, see #2626
         Given the node map
             | a | b |   |   | x |
             |   | c |   |   |   |
@@ -432,25 +626,48 @@ Feature: Turn Lane Guidance
             |   | y |   |   |   |
 
         And the ways
-            | nodes | turn:lanes:forward                                 | highway | junction   | name  |
-            | ab    | through\|right\|right\|right\|right                | primary |            | abx   |
-            | bx    |                                                    | primary |            | abx   |
-            | bc    | right\|right\|right\|right                         | primary |            | bc    |
-            | cd    |                                                    | primary | roundabout | cdefc |
-            | de    | slight_left\|slight_left&slight_left\|slight_right | primary | roundabout | cdefc |
-            | ef    | left\|slight_right\|slight_right                   | primary | roundabout | cdefc |
-            | fc    |                                                    | primary | roundabout | cdefc |
-            | ey    |                                                    | primary |            | ey    |
-            | fg    | through\|right                                     | primary |            | fg    |
-            | gz    |                                                    | primary |            | gz    |
-            | gh    |                                                    | primary |            | gh    |
+            | nodes | turn:lanes:forward                                  | highway | junction   | name  |
+            | ab    | through\|right\|right\|right\|right                 | primary |            | abx   |
+            | bx    |                                                     | primary |            | abx   |
+            | bc    | right\|right\|right\|right                          | primary |            | bc    |
+            | cd    |                                                     | primary | roundabout | cdefc |
+            | de    | slight_left\|slight_left\|slight_left\|slight_right | primary | roundabout | cdefc |
+            | ef    | left\|slight_right\|slight_right                    | primary | roundabout | cdefc |
+            | fc    |                                                     | primary | roundabout | cdefc |
+            | ey    |                                                     | primary |            | ey    |
+            | fg    | through\|right                                      | primary |            | fg    |
+            | gz    |                                                     | primary |            | gz    |
+            | gh    |                                                     | primary |            | gh    |
 
         When I route I should get
-            | waypoints | route           | turns                                            | lanes                                                                                                                                    |
-            | a,h       | abx,bc,fg,gh,gh | depart,turn right,cdefc-exit-2,turn right,arrive | ,straight:false right:false right:false right:false right:true,right:false right:false right:false right:true,straight:false right:true, |
+            | waypoints | route           | turns                                            | lanes                                                                                      |
+            | a,h       | abx,bc,fg,gh,gh | depart,turn right,cdefc-exit-2,turn right,arrive | ,straight:false right:false right:false right:false right:true,,straight:false right:true, |
 
-    @anticipate @bug @todo
-    Scenario: Tripple Right keeping Left
+    @anticipate
+    Scenario: Anticipate none tags
+        Given the node map
+            | a | b | c |
+            |   | d |   |
+            | f | e | g |
+            |   | h |   |
+
+        And the ways
+            | nodes | turn:lanes:forward       | highway   | name |
+            | ab    | none\|none\|right\|right | primary   | abc  |
+            | bc    |                          | primary   | abc  |
+            | bd    |                          | primary   | bdeh |
+            | de    | left\|none\|none\|right  | primary   | bdeh |
+            | eh    |                          | primary   | bdeh |
+            | ef    |                          | primary   | feg  |
+            | eg    |                          | primary   | feg  |
+
+        When I route I should get
+            | waypoints | route            | turns                               | lanes                                                                                      |
+            | a,g       | abc,bdeh,feg,feg | depart,turn right,turn left,arrive  | ,none:false none:false right:true right:false,left:true none:false none:false right:false, |
+            | a,f       | abc,bdeh,feg,feg | depart,turn right,turn right,arrive | ,none:false none:false right:false right:true,left:false none:false none:false right:true, |
+
+    @anticipate
+    Scenario: Triple Right keeping Left
         Given the node map
             | a |   |   |   | b |   | i |
             |   |   |   |   |   |   |   |
@@ -470,11 +687,11 @@ Feature: Turn Lane Guidance
             | feg   |                    | tertiary  | fourth |
 
         When I route I should get
-            | waypoints | route                                  | turns                                                            | lanes                                                                                                                                                                      |
-            | a,f       | start,first,second,third,fourth,fourth | depart,turn right,turn right,turn right,end of road left,arrive  | ,none:false none:true right:false right:false,none:false none:true right:false right:false,none:false none:true right:false right:false,left:true right:false right:false, |
-            | a,g       | start,first,second,third,fourth,fourth | depart,turn right,turn right,turn right,end of road right,arrive | ,none:false none:false right:true right:true,none:false none:false right:true right:true,none:false none:false right:true right:true,left:false right:true right:true,     |
+            | waypoints | route                                  | turns                                                     | lanes                                                                                                                                                                      |
+            | a,f       | start,first,second,third,fourth,fourth | depart,turn right,turn right,turn right,turn left,arrive  | ,none:false none:false right:true right:false,none:false none:false right:true right:false,none:false none:false right:true right:false,left:true right:false right:false, |
+            | a,g       | start,first,second,third,fourth,fourth | depart,turn right,turn right,turn right,turn right,arrive | ,none:false none:false right:true right:true,none:false none:false right:true right:true,none:false none:false right:true right:true,left:false right:true right:true,     |
 
-    @anticipate @bug @todo
+    @anticipate
     Scenario: Tripple Left keeping Right
         Given the node map
             | i |   | b |   |   |   | a |
@@ -495,6 +712,6 @@ Feature: Turn Lane Guidance
             | feg   |                    | tertiary  | fourth |
 
         When I route I should get
-            | waypoints | route                                  | turns                                                         | lanes                                                                                                                                                               |
-            | a,f       | start,first,second,third,fourth,fourth | depart,turn left,turn left,turn left,end of road right,arrive | ,left:false left:false none:true none:false,left:false left:false none:true none:false,left:false left:false none:true none:false,left:false left:false right:true, |
-            | a,g       | start,first,second,third,fourth,fourth | depart,turn left,turn left,turn left,end of road left,arrive  | ,left:true left:true none:false none:false,left:true left:true none:false none:false,left:true left:true none:false none:false,left:true left:true right:false,     |
+            | waypoints | route                                  | turns                                                  | lanes                                                                                                                                                               |
+            | a,f       | start,first,second,third,fourth,fourth | depart,turn left,turn left,turn left,turn right,arrive | ,left:false left:true none:false none:false,left:false left:true none:false none:false,left:false left:true none:false none:false,left:false left:false right:true, |
+            | a,g       | start,first,second,third,fourth,fourth | depart,turn left,turn left,turn left,turn left,arrive  | ,left:true left:true none:false none:false,left:true left:true none:false none:false,left:true left:true none:false none:false,left:true left:true right:false,     |
diff --git a/features/guidance/bugs.feature b/features/guidance/bugs.feature
new file mode 100644
index 0000000..489262c
--- /dev/null
+++ b/features/guidance/bugs.feature
@@ -0,0 +1,35 @@
+ at routing @guidance
+Feature: Features related to bugs
+
+    Background:
+        Given the profile "car"
+        Given a grid size of 5 meters
+
+    @2852
+    Scenario: Loop
+        Given the node map
+            | a | 1 |   | g |   |   | b |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            | e |   |   |   |   |   | f |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   | 2 |
+            | d |   |   | h |   |   | c |
+
+        And the ways
+            | nodes | name   | oneway |
+            | agb   | top    | yes    |
+            | bfc   | right  | yes    |
+            | chd   | bottom | yes    |
+            | dea   | left   | yes    |
+
+        And the nodes
+            | node | highway         |
+            | g    | traffic_signals |
+            | f    | traffic_signals |
+            | h    | traffic_signals |
+            | e    | traffic_signals |
+
+        When I route I should get
+            | waypoints | route           | turns                        |
+            | 1,2       | top,right,right | depart,new name right,arrive |
diff --git a/features/guidance/collapse-detail.feature b/features/guidance/collapse-detail.feature
new file mode 100644
index 0000000..9653bc9
--- /dev/null
+++ b/features/guidance/collapse-detail.feature
@@ -0,0 +1,53 @@
+ at routing  @guidance @collapsing
+Feature: Collapse
+
+    Background:
+        Given the profile "car"
+        Given a grid size of 5 meters
+
+    @reverse
+    Scenario: Collapse U-Turn Triangle Intersection
+        Given the node map
+            | g |   | f |   | e |   | d |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            | a |   |   | b |   |   | c |
+
+        And the ways
+            | nodes | highway      | name | oneway |
+            | abc   | primary      | road | yes    |
+            | defg  | primary      | road | yes    |
+            | fb    | primary_link |      |        |
+            | be    | primary_link |      |        |
+
+       When I route I should get
+            | waypoints | route          | turns                        |
+            | a,g       | road,road,road | depart,continue uturn,arrive |
+            | d,c       | road,road,road | depart,continue uturn,arrive |
+
+    @reverse @traffic-signals
+    Scenario: Collapse U-Turn Triangle Intersection
+        Given the node map
+            | g |   | f |   | j |   | e |   | d |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   | h |   | i |   |   |   |
+            |   |   |   |   |   |   |   |   |   |
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | highway      | name | oneway |
+            | abc   | primary      | road | yes    |
+            | dejfg | primary      | road | yes    |
+            | fhb   | primary_link |      |        |
+            | bie   | primary_link |      |        |
+
+       And the nodes
+            | node | highway         |
+            | j    | traffic_signals |
+            | h    | traffic_signals |
+            | i    | traffic_signals |
+
+       When I route I should get
+            | waypoints | route          | turns                        |
+            | a,g       | road,road,road | depart,continue uturn,arrive |
+            | d,c       | road,road,road | depart,continue uturn,arrive |
diff --git a/features/guidance/collapse.feature b/features/guidance/collapse.feature
index 709cf7c..f8e6666 100644
--- a/features/guidance/collapse.feature
+++ b/features/guidance/collapse.feature
@@ -136,6 +136,12 @@ Feature: Collapse
 
     Scenario: Partly Segregated Intersection, Two Segregated Roads
         Given the node map
+            |   | n |   | m |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
             |   | g |   | h |   |
             |   |   |   |   |   |
             |   |   |   |   |   |
@@ -144,6 +150,12 @@ Feature: Collapse
             |   |   |   |   |   |
             |   |   |   |   |   |
             |   | j |   | i |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   | k |   | l |   |
 
         And the ways
             | nodes | highway | name   | oneway |
@@ -152,8 +164,8 @@ Feature: Collapse
             | de    | primary | first  | yes    |
             | ef    | primary | first  | yes    |
             | be    | primary | first  | no     |
-            | gbh   | primary | second | yes    |
-            | iej   | primary | second | yes    |
+            | ngbhm | primary | second | yes    |
+            | liejk | primary | second | yes    |
 
        When I route I should get
             | waypoints | route                | turns                        |
@@ -176,6 +188,10 @@ Feature: Collapse
 
     Scenario: Partly Segregated Intersection, Two Segregated Roads, Intersection belongs to Second
         Given the node map
+            |   | n |   | m |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
             |   | g |   | h |   |
             |   |   |   |   |   |
             |   |   |   |   |   |
@@ -184,6 +200,10 @@ Feature: Collapse
             |   |   |   |   |   |
             |   |   |   |   |   |
             |   | j |   | i |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   | k |   | l |   |
 
         And the ways
             | nodes | highway | name   | oneway |
@@ -192,8 +212,8 @@ Feature: Collapse
             | de    | primary | first  | yes    |
             | ef    | primary | first  | yes    |
             | be    | primary | second | no     |
-            | gbh   | primary | second | yes    |
-            | iej   | primary | second | yes    |
+            | ngbhm | primary | second | yes    |
+            | liejk | primary | second | yes    |
 
        When I route I should get
             | waypoints | route                | turns                        |
@@ -302,12 +322,12 @@ Feature: Collapse
 
     Scenario: Entering a segregated road
         Given the node map
-            |   | a | f |   |   |
-            |   |   |   |   | g |
-            |   | b | e |   |   |
-            |   |   |   |   |   |
-            |   |   |   |   |   |
-            | c | d |   |   |   |
+            |   | a | f |   |   |   | g |
+            |   |   |   |   |   |   |   |
+            |   | b | e |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            | c | d |   |   |   |   |   |
 
         And the ways
             | nodes | highway | name   | oneway |
@@ -325,7 +345,6 @@ Feature: Collapse
             | g,f       | second,first,first  | depart,turn right,arrive       |
             | g,c       | second,first,first  | depart,end of road left,arrive |
 
-
     Scenario: Do not collapse turning roads
         Given the node map
             |   |   | e |   |   |
@@ -333,12 +352,12 @@ Feature: Collapse
             | a |   | b | f |   |
 
         And the ways
-            | nodes | highway | name   |
-            | ab    | primary | first  |
-            | bc    | primary | first  |
-            | cd    | primary | first  |
-            | ce    | primary | second |
-            | bf    | primary | third  |
+            | nodes | highway | name   | oneway |
+            | ab    | primary | first  | yes    |
+            | bc    | primary | first  | yes    |
+            | cd    | primary | first  | yes    |
+            | ce    | primary | second | yes    |
+            | bf    | primary | third  | yes    |
 
         When I route I should get
             | waypoints | route                   | turns                                      |
@@ -383,13 +402,13 @@ Feature: Collapse
 
     Scenario: Pankenbruecke
         Given the node map
-            | h |   |   |   |   |   | i |   |   |   |   |   |   |
-            |   |   | b | c | d | e | f |   |   |   |   |   | g |
-            | a |   |   |   |   |   |   |   |   |   |   |   |   |
+            | j |   |   |   | h |   |   |   |   |   | i |   |   |   |   |   |   |
+            |   |   |   |   |   |   | b | c | d | e | f |   |   |   |   |   | g |
+            | k |   |   |   | a |   |   |   |   |   |   |   |   |   |   |   |   |
 
         And the ways
             | nodes | highway | name    | oneway |
-            | abh   | primary | inroad  | yes    |
+            | kabhj | primary | inroad  | yes    |
             | bc    | primary | inroad  | no     |
             | cd    | primary | bridge  | no     |
             | defg  | primary | outroad | no     |
@@ -508,6 +527,36 @@ Feature: Collapse
 
         When I route I should get
             | waypoints | route        | turns                           |
+            | i,h       | in,road,road | depart,turn left,arrive         |
+            | a,d       | road,road    | depart,arrive                   |
+            | a,j       | road,out,out | depart,turn slight right,arrive |
+
+    Scenario: Segregated Intersection into Very Slight Turn
+        Given the node map
+            | h |   |   |   |   |   |   |
+            | a |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   | g |   |   |   |   |
+            |   |   | b |   |   |   |   |
+            |   |   |   | f |   |   |   |
+            |   |   |   | c |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   | e |
+            |   |   |   |   |   |   | d |
+            |   |   | j | i |   |   |   |
+
+        And the ways
+            | nodes | highway   | name | oneway |
+            | abcd  | primary   | road | yes    |
+            | efgh  | primary   | road | yes    |
+            | icf   | secondary | in   | yes    |
+            | gbj   | secondary | out  | yes    |
+
+        When I route I should get
+            | waypoints | route        | turns                           |
             | i,h       | in,road,road | depart,turn slight left,arrive  |
             | a,d       | road,road    | depart,arrive                   |
             | a,j       | road,out,out | depart,turn slight right,arrive |
@@ -571,26 +620,28 @@ Feature: Collapse
             | bd    | road  | yes    | primary   |
             | bc    | road  | yes    | primary   |
             | de    | road  | yes    | primary   |
-            | fdcg  | cross | no     | secondary |
+            | fd    | cross | no     | secondary |
+            | dc    | cross | no     | secondary |
+            | cg    | cross | no     | secondary |
 
         And the relations
             | type        | way:from | way:to | node:via | restriction   |
-            | restriction | bd       | fdcg   | d        | no_left_turn  |
-            | restriction | bc       | fdcg   | c        | no_right_turn |
+            | restriction | bd       | dc     | d        | no_left_turn  |
+            | restriction | bc       | dc     | c        | no_right_turn |
 
         When I route I should get
-          | waypoints | route            | turns                   |
-          | a,g       | road,cross,cross | depart,turn left,arrive |
-          | a,e       | road,road        | depart,arrive           |
+            | waypoints | route            | turns                   |
+            | a,g       | road,cross,cross | depart,turn left,arrive |
+            | a,e       | road,road        | depart,arrive           |
 
     Scenario: Forking before a turn (forky)
         Given the node map
-            |   |   |   | g |   |   |
-            |   |   |   |   |   |   |
-            |   |   |   | c |   |   |
-            | a | b |   |   |   |   |
-            |   |   |   |   | d |   |
-            |   |   |   |   | f | e |
+            |   |   |   |   |   | g |   |   |
+            |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   | c |   |   |
+            | a |   |   | b |   |   |   |   |
+            |   |   |   |   |   |   | d |   |
+            |   |   |   |   |   |   | f | e |
 
         And the ways
             | nodes | name  | oneway | highway   |
@@ -598,12 +649,14 @@ Feature: Collapse
             | bd    | road  | yes    | primary   |
             | bc    | road  | yes    | primary   |
             | de    | road  | yes    | primary   |
-            | fdcg  | cross | no     | secondary |
+            | fd    | cross | no     | secondary |
+            | dc    | cross | no     | secondary |
+            | cg    | cross | no     | secondary |
 
         And the relations
             | type        | way:from | way:to | node:via | restriction   |
-            | restriction | bd       | fdcg   | d        | no_left_turn  |
-            | restriction | bc       | fdcg   | c        | no_right_turn |
+            | restriction | bd       | dc     | d        | no_left_turn  |
+            | restriction | bc       | dc     | c        | no_right_turn |
 
         When I route I should get
             | waypoints | route                 | turns                                          |
@@ -630,3 +683,158 @@ Feature: Collapse
             | f,d       | on,Hwy,Hwy     | depart,merge slight right,arrive                |
             | f,e       | on,Hwy,off,off | depart,merge slight right,off ramp right,arrive |
             | a,e       | Hwy,off,off    | depart,off ramp right,arrive                    |
+
+    @negative @straight
+    Scenario: Don't collapse going straight if actual turn
+        Given the node map
+            |   | c | e |   |   |
+            |   |   | d |   | f |
+            |   |   |   |   |   |
+            |   |   | b |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   | a |   |   |
+
+        And the ways
+            | nodes | name     | highway     |
+            | abc   | main     | primary     |
+            | bde   | straight | residential |
+            | df    | right    | residential |
+
+        When I route I should get
+            | waypoints | route                     | turns                                  |
+            | a,c       | main,main                 | depart,arrive                          |
+            | a,e       | main,straight,straight    | depart,turn straight,arrive            |
+            | a,f       | main,straight,right,right | depart,turn straight,turn right,arrive |
+
+    Scenario: Entering a segregated road
+        Given the node map
+            |   | a | f |   |   |
+            |   |   |   |   | g |
+            |   | b | e |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            | c | d |   |   |   |
+
+        And the ways
+            | nodes | highway | name   | oneway |
+            | abc   | primary | first  | yes    |
+            | def   | primary | first  | yes    |
+            | be    | primary | first  | no     |
+            | ge    | primary | second | no     |
+
+        When I route I should get
+            | waypoints | route               | turns                          |
+            | d,c       | first,first,first   | depart,continue uturn,arrive   |
+
+    Scenario: Entering a segregated road slight turn
+        Given the node map
+            |   |   | a | f |   |
+            |   |   |   |   | g |
+            |   | b | e |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            | c | d |   |   |   |
+
+        And the ways
+            | nodes | highway | name   | oneway |
+            | abc   | primary | first  | yes    |
+            | def   | primary | first  | yes    |
+            | be    | primary | first  | no     |
+            | ge    | primary | second | no     |
+
+        When I route I should get
+            | waypoints | route               | turns                          |
+            | d,c       | first,first,first   | depart,continue uturn,arrive   |
+
+    Scenario: Do not collapse UseLane step when lanes change
+        Given the node map
+            |   |   |   | f | g |   |
+            |   |   |   |   |   |   |
+            | a | b | c | d |   | e |
+            |   |   |   |   |   |   |
+            |   |   |   | h | i |   |
+
+        And the ways
+            | nodes | turn:lanes:forward                     | name |
+            | ab    |                                        | main |
+            | bc    | left\|through\|through\|through\|right | main |
+            | cd    | left\|through\|right                   | main |
+            | de    |                                        | main |
+            | cf    |                                        | off  |
+            | ch    |                                        | off  |
+            | dg    |                                        | off  |
+            | di    |                                        | off  |
+
+       When I route I should get
+            | waypoints | route          | turns                           |
+            | a,e       | main,main,main | depart,use lane straight,arrive |
+
+    Scenario: But _do_ collapse UseLane step when lanes stay the same
+        Given the node map
+            |   |   |   | f | g |   |
+            |   |   |   |   |   |   |
+            | a | b | c | d |   | e |
+            |   |   |   |   |   |   |
+            |   |   |   | h | i |   |
+
+        And the ways
+            | nodes | turn:lanes:forward                     | name |
+            | ab    |                                        | main |
+            | bc    | left\|through\|through\|through\|right | main |
+            | cd    | left\|through\|through\|through\|right | main |
+            | de    |                                        | main |
+            | cf    |                                        | off  |
+            | ch    |                                        | off  |
+            | dg    |                                        | off  |
+            | di    |                                        | off  |
+
+       When I route I should get
+            | waypoints | route     | turns         |
+            | a,e       | main,main | depart,arrive |
+
+    Scenario: Don't collapse different travel modes
+        Given the node map
+            | g |   |   |   |   |   |   | h |   |
+            | a | b |   | c |   |   |   | e | f |
+            |   |   |   |   |   | d |   |   |   |
+            |   |   |   | i | j |   |   |   |   |
+
+        And the ways
+            | nodes | highway | route | name |
+            | ab    | primary |       | road |
+            | bc    | primary | ferry |      |
+            | cd    | primary |       | road |
+            | de    |         | ferry |      |
+            | ef    | primary |       | road |
+            | bg    | service |       | turn |
+            | ci    | service |       | turn |
+            | dj    | service |       | turn |
+            | eh    | service |       | turn |
+
+        When I route I should get
+            | waypoints | route                 |
+            | a,f       | road,,road,,road,road |
+
+    Scenario: U-Turn onto a Ferry
+        Given the node map
+            |   |   |   |   |   |   | i |   |   |
+            | j | e |   |   |   |   | d | c | h |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |
+            | k | g |   |   |   |   | a | b | f |
+
+        And the ways
+            | nodes | highway | route | name  | oneway |
+            | bf    | primary |       | road  | yes    |
+            | hcd   | primary |       | road  | yes    |
+            | bc    | primary |       |       | yes    |
+            | di    | service |       | serv  | yes    |
+            | ed    |         | ferry | ferry |        |
+            | gab   |         | ferry | ferry |        |
+            | kg    | primary |       | on    | yes    |
+            | ej    | primary |       | off   | yes    |
+
+        When I route I should get
+            | waypoints | route                   | turns                                                                          |
+            | k,j       | on,ferry,,ferry,off,off | depart,new name straight,continue uturn,turn straight,new name straight,arrive |
diff --git a/features/guidance/dedicated-turn-roads.feature b/features/guidance/dedicated-turn-roads.feature
index 30cb87e..d6fe959 100644
--- a/features/guidance/dedicated-turn-roads.feature
+++ b/features/guidance/dedicated-turn-roads.feature
@@ -52,8 +52,8 @@ Feature: Slipways and Dedicated Turn Lanes
             | efg   | primary       | second |
 
        When I route I should get
-            | waypoints | route                | turns                                                 |
-            | a,g       | first,,second,second | depart,off ramp slight right,merge slight left,arrive |
+            | waypoints | route                | turns                                             |
+            | a,g       | first,,second,second | depart,off ramp slight right,turn straight,arrive |
 
     Scenario: Inner city expressway with on road
         Given the node map
@@ -165,5 +165,187 @@ Feature: Slipways and Dedicated Turn Lanes
             | qe    | secondary_link | Ettlinger Allee    |      | yes    |
 
         When I route I should get
-            | waypoints | route                                                     | turns                    |
-            | a,o       | Schwarzwaldstrasse (L561),Ettlinger Allee,Ettlinger Allee | depart,turn right,arrive |
+            | waypoints | route                                                    | turns                    | ref        |
+            | a,o       | Schwarzwaldstrasse,Ettlinger Allee,Ettlinger Allee       | depart,turn right,arrive | L561,,     |
+
+    Scenario: Traffic Lights everywhere
+        #http://map.project-osrm.org/?z=18&center=48.995336%2C8.383813&loc=48.995467%2C8.384548&loc=48.995115%2C8.382761&hl=en&alt=0
+        Given the node map
+            | a |   |   | k | l |   |   | j |   |
+            |   |   |   |   |   | d | b | c | i |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   | e | g |   |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   | 1 |   |   |
+            |   |   |   |   |   |   |   | h |   |
+            |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   | f |   |
+
+        And the nodes
+            | node | highway         |
+            | b    | traffic_signals |
+            | e    | traffic_signals |
+            | g    | traffic_signals |
+
+        And the ways
+            | nodes  | highway        | name          | oneway |
+            | aklbci | secondary      | Ebertstrasse  | yes    |
+            | kdeh   | secondary_link |               | yes    |
+            | jcghf  | primary        | Brauerstrasse | yes    |
+
+        When I route I should get
+            | waypoints | route                                    | turns                           |
+            | a,i       | Ebertstrasse,Ebertstrasse                | depart,arrive                   |
+            | a,l       | Ebertstrasse,Ebertstrasse                | depart,arrive                   |
+            | a,f       | Ebertstrasse,Brauerstrasse,Brauerstrasse | depart,turn right,arrive        |
+            | a,1       | Ebertstrasse,,                           | depart,turn slight right,arrive |
+
+    #2839
+    Scenario: Self-Loop
+        Given the node map
+            # 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | l |   |   | k |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | j |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | m |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | i |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | h |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | n |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | g |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | o |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | f |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | p |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | e |   |   |   |
+            | a |   |   |   |   | b |   |   |   |   |   |   |   |   | c |   |   |   |   |   |   |   |   |   | d |   |   |   |   |   |   |
+
+     And the ways
+            | nodes           | name    | oneway | highway     | lanes |
+            | abc             | circled | no     | residential | 1     |
+            | cdefghijklmnopc | circled | yes    | residential | 1     |
+
+     When I route I should get
+            | waypoints | bearings     | route           | turns         |
+            | b,a       | 90,10 270,10 | circled,circled | depart,arrive |
+
+    @todo
+    #due to the current turn function, the left turn bcp is exactly the same cost as pcb, a right turn. The right turn should be way faster,though
+    #for that reason we cannot distinguish between driving clockwise through the circle or counter-clockwise. Until this is improved, this case here
+    #has to remain as todo (see #https://github.com/Project-OSRM/osrm-backend/pull/2849)
+    Scenario: Self-Loop - Bidirectional
+        Given the node map
+            # 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | l |   |   | k |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | j |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | m |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | i |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | h |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | n |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | g |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | o |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | f |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | p |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | e |   |   |   |
+            | a |   |   |   |   | b |   |   |   |   |   |   |   |   | c |   |   |   |   |   |   |   |   |   | d |   |   |   |   |   |   |
+
+     And the ways
+            | nodes           | name    | oneway | highway     | lanes |
+            | abc             | circled | no     | residential | 1     |
+            | cdefghijklmnopc | circled | no     | residential | 1     |
+
+     When I route I should get
+            | waypoints | bearings     | route           | turns         |
+            | b,a       | 90,10 270,10 | circled,circled | depart,arrive |
+
+    #http://www.openstreetmap.org/#map=19/38.90597/-77.01276
+    Scenario: Don't falsly classify as sliproads
+        Given the node map
+            # 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | j |   |   |   |   |   |   |   |
+            | a | b |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | c |   |   |   |   |   |   | d |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   | e |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | 1 |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | f |   |   |   |   | g |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | i |   |   |   |   |   |   | h |
+
+        And the ways
+            | nodes | name       | highway   | oneway | maxspeed |
+            | abcd  | new york   | primary   | yes    | 35       |
+            | befgh | m street   | secondary | yes    | 35       |
+            | igcj  | 1st street | tertiary  | no     | 20       |
+
+        And the nodes
+            | node | highway         |
+            | c    | traffic_signals |
+            | g    | traffic_signals |
+
+        When I route I should get
+            | waypoints | route                                   | turns                              | #                                    |
+            | a,d       | new york,new york                       | depart,arrive                      | this is the sinatra route            |
+            | a,j       | new york,1st street,1st street          | depart,turn left,arrive            |                                      |
+            | a,1       | new york,m street,1st street,1st street | depart,turn right,turn left,arrive | this can false be seen as a sliproad |
+
+    # Merging into degree two loop on dedicated turn detection / 2927
+    Scenario: Turn Instead of Ramp
+        Given the node map
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   | f |
+            |   |   |   |   | g |   |   |   |   |   | h |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   | d |   |   | e |
+            | i |   |   |   | c |   |   |   |   |   | j |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   | b |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   | a |   |   |   |   |   |   |   |   |   |   |   |   |   |
+
+        And the ways
+            | nodes | highway | name | oneway |
+            | abi   | primary | road | yes    |
+            | bcjd  | primary | loop | yes    |
+            | dhgf  | primary | loop | yes    |
+            | fed   | primary | loop | yes    |
+
+        And the nodes
+            | node | highway         |
+            | g    | traffic_signals |
+            | c    | traffic_signals |
+
+        # We don't actually care about routes here, this is all about endless loops in turn discovery
+        When I route I should get
+            | waypoints | route          | turns                          |
+            | a,i       | road,road,road | depart,fork slight left,arrive |
diff --git a/features/guidance/destination-signs.feature b/features/guidance/destination-signs.feature
index 3c5aa4e..73f90d9 100644
--- a/features/guidance/destination-signs.feature
+++ b/features/guidance/destination-signs.feature
@@ -29,13 +29,13 @@ Feature: Destination Signs
           | qr    | QR   |     |                | A1;A2           | yes    |                                      |
 
         When I route I should get
-          | from | to | route                                                     | destinations                                    | #                         |
-          | a    | b  | AB (E1),AB (E1)                                           | ,                                               |                           |
-          | c    | d  | CD (Berlin),CD (Berlin)                                   | Berlin,Berlin                                   |                           |
-          | e    | f  | EF (A1: Berlin),EF (A1: Berlin)                           | A1: Berlin,A1: Berlin                           |                           |
-          | g    | h  | ,                                                         | A1: Berlin,A1: Berlin                           |                           |
-          | i    | j  | ,                                                         | Berlin,Berlin                                   |                           |
-          | k    | l  | KL (E1),KL (E1)                                           | A1: Berlin,A1: Berlin                           |                           |
-          | m    | n  | MN (A1, A2: Berlin, Hamburg),MN (A1, A2: Berlin, Hamburg) | A1, A2: Berlin, Hamburg,A1, A2: Berlin, Hamburg |                           |
-          | o    | p  | OP,OP                                                     | ,                                               | guard against mis-tagging |
-          | q    | r  | QR (A1, A2),QR (A1, A2)                                   | A1, A2,A1, A2                                   |                           |
+          | from | to | route                                                     | destinations                                    | ref   | #                         |
+          | a    | b  | AB,AB                                                     | ,                                               | E1,E1 |                           |
+          | c    | d  | CD,CD                                                     | Berlin,Berlin                                   | ,     |                           |
+          | e    | f  | EF,EF                                                     | A1: Berlin,A1: Berlin                           | ,     |                           |
+          | g    | h  | ,                                                         | A1: Berlin,A1: Berlin                           | ,     |                           |
+          | i    | j  | ,                                                         | Berlin,Berlin                                   | ,     |                           |
+          | k    | l  | KL,KL                                                     | A1: Berlin,A1: Berlin                           | E1,E1 |                           |
+          | m    | n  | MN,MN                                                     | A1, A2: Berlin, Hamburg,A1, A2: Berlin, Hamburg | ,     |                           |
+          | o    | p  | OP,OP                                                     | ,                                               | ,     | guard against mis-tagging |
+          | q    | r  | QR,QR                                                     | A1, A2,A1, A2                                   | ,     |                           |
diff --git a/features/guidance/fork.feature b/features/guidance/fork.feature
index 2c8d592..047d897 100644
--- a/features/guidance/fork.feature
+++ b/features/guidance/fork.feature
@@ -311,6 +311,6 @@ Feature: Fork Instructions
             | ab    | on   | motorway_link |
 
         When I route I should get
-            | waypoints | route           | turns                                             |
-            | a,j       | on,xbcj,xbcj    | depart,merge slight left,arrive                   |
-            | a,i       | on,xbcj,off,off | depart,merge slight left,turn slight right,arrive |
+            | waypoints | route      | turns                           |
+            | a,j       | on,xbcj    | depart,arrive                   |
+            | a,i       | on,off,off | depart,turn slight right,arrive |
diff --git a/features/guidance/merge.feature b/features/guidance/merge.feature
index 0190c8b..2b52ba7 100644
--- a/features/guidance/merge.feature
+++ b/features/guidance/merge.feature
@@ -3,13 +3,14 @@ Feature: Merging
 
     Background:
         Given the profile "car"
-        Given a grid size of 10 meters
+        And a grid size of 10 meters
 
+    @merge
     Scenario: Merge on Four Way Intersection
         Given the node map
-            | d |   |   |
-            | a | b | c |
-            | e |   |   |
+            | d |   |   |   |   |   |   |   |   |   |
+            | a |   | b |   |   |   |   |   |   | c |
+            | e |   |   |   |   |   |   |   |   |   |
 
         And the ways
             | nodes | highway |
@@ -18,14 +19,15 @@ Feature: Merging
             | eb    | primary |
 
        When I route I should get
-            | waypoints | route      | turns                            |
-            | d,c       | db,abc,abc | depart,merge slight right,arrive |
-            | e,c       | eb,abc,abc | depart,merge slight left,arrive  |
+            | waypoints | route      | turns                       |
+            | d,c       | db,abc,abc | depart,turn straight,arrive |
+            | e,c       | eb,abc,abc | depart,turn straight,arrive |
 
+    @merge
     Scenario: Merge on Three Way Intersection Right
         Given the node map
-            | d |   |   |
-            | a | b | c |
+            | d |   |   |   |   |   |   |   |   |   |
+            | a |   | b |   |   |   |   |   |   | c |
 
         And the ways
             | nodes | highway |
@@ -33,13 +35,30 @@ Feature: Merging
             | db    | primary |
 
        When I route I should get
-            | waypoints | route      | turns                            |
-            | d,c       | db,abc,abc | depart,merge slight right,arrive |
+            | waypoints | route      | turns                       |
+            | d,c       | db,abc,abc | depart,turn straight,arrive |
+
+    @merge @negative
+    Scenario: Don't Merge on Short-Three Way Intersection Right
+        Given the node map
+            | d |   |   |   |   |   |   |   |
+            | a |   | b |   |   |   |   | c |
+
+        And the ways
+            | nodes | highway |
+            | abc   | primary |
+            | db    | primary |
+
+       When I route I should get
+            | waypoints | route      | turns                          |
+            | d,c       | db,abc,abc | depart,turn slight left,arrive |
+
 
+    @merge
     Scenario: Merge on Three Way Intersection Right
         Given the node map
-            | a | b | c |
-            | d |   |   |
+            | a |   | b |   |   |   |   |   |   | c |
+            | d |   |   |   |   |   |   |   |   |   |
 
         And the ways
             | nodes | highway |
@@ -47,15 +66,21 @@ Feature: Merging
             | db    | primary |
 
        When I route I should get
-            | waypoints | route      | turns                           |
-            | d,c       | db,abc,abc | depart,merge slight left,arrive |
+            | waypoints | route      | turns                       |
+            | d,c       | db,abc,abc | depart,turn straight,arrive |
 
+    @merge
     Scenario: Merge onto a turning road
         Given the node map
             |   |   |   |   |   |   | e |
             |   |   |   |   |   |   |   |
             |   |   |   |   |   |   |   |
             |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
             |   |   |   |   |   | d |   |
             |   |   |   |   |   |   |   |
             |   |   |   |   |   |   |   |
@@ -71,6 +96,21 @@ Feature: Merging
             | fd    | residential | in   |
 
         When I route I should get
-            | waypoints | turns                           | route        |
-            | f,e       | depart,merge slight left,arrive | in,road,road |
-            | f,a       | depart,turn sharp left,arrive  | in,road,road |
+            | waypoints | turns                         | route        |
+            | f,e       | depart,turn straight,arrive   | in,road,road |
+            | f,a       | depart,turn sharp left,arrive | in,road,road |
+
+    @merge
+    Scenario: Merge onto a motorway
+        Given the node map
+            | d |   |   |   |   |   |   |   |   |   |
+            | a |   |   | b |   |   |   |   |   | c |
+
+        And the ways
+            | nodes | name | highway       | oneway |
+            | abc   | A100 | motorway      | yes    |
+            | db    |      | motorway_link | yes    |
+
+        When I route I should get
+            | waypoints | route      | turns                            |
+            | d,c       | ,A100,A100 | depart,merge slight right,arrive |
diff --git a/features/guidance/new-name.feature b/features/guidance/new-name.feature
index 8c823dd..9ff9756 100644
--- a/features/guidance/new-name.feature
+++ b/features/guidance/new-name.feature
@@ -125,10 +125,10 @@ Feature: New-Name Instructions
             |   |   |   |   | c |
 
         And the ways
-            | nodes  | highway     |
-            | ab     | residential |
-            | bc     | residential |
-            | bd     | service     |
+            | nodes  | highway     | oneway |
+            | ab     | residential | yes    |
+            | bc     | residential | yes    |
+            | bd     | service     | yes    |
 
        When I route I should get
             | waypoints | route    | turns                               |
@@ -164,3 +164,172 @@ Feature: New-Name Instructions
             | waypoints | route                    | turns                           |
             | a,e       | name,with-name,with-name | depart,new name straight,arrive |
             | b,e       | with-name,with-name      | depart,arrive                   |
+
+    Scenario: Both Name and Ref Empty
+        Given the node map
+            | a |  | b |  | c |
+
+        And the ways
+            | nodes | name | ref |
+            | ab    |      |     |
+            | bc    |      |     |
+
+        When I route I should get
+            | waypoints | route | turns         |
+            | a,c       | ,     | depart,arrive |
+
+    Scenario: Same Name, Ref Extended
+        Given the node map
+            | a |  | b |  | c |
+
+        And the ways
+            | nodes | name | ref   |
+            | ab    | A    | B1    |
+            | bc    | C    | B1;B2 |
+
+        When I route I should get
+            | waypoints | route | turns                           |
+            | a,c       | A,C,C | depart,new name straight,arrive |
+
+    Scenario: Same Name, Ref Removed
+        Given the node map
+            | a |  | b |  | c |
+
+        And the ways
+            | nodes | name | ref   |
+            | ab    | A    | B1;B2 |
+            | bc    | C    | B1    |
+
+        When I route I should get
+            | waypoints | route | turns                           |
+            | a,c       | A,C,C | depart,new name straight,arrive |
+
+    Scenario: Name Removed, Ref Extended
+        Given the node map
+            | a |  | b |  | c |
+
+        And the ways
+            | nodes | name | ref   |
+            | ab    | A    | B1    |
+            | bc    |      | B1;B2 |
+
+        When I route I should get
+            | waypoints | route | turns         |
+            | a,c       | A,    | depart,arrive |
+
+    Scenario: Name Added, Ref Removed
+        Given the node map
+            | a |  | b |  | c |
+
+        And the ways
+            | nodes | name | ref   |
+            | ab    |      | B1    |
+            | bc    | A    |       |
+
+        When I route I should get
+            | waypoints | route | turns                           |
+            | a,c       | ,A,A  | depart,new name straight,arrive |
+
+    Scenario: Prefix Change
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name                     | ref   | highway  |
+            | ab    | North Central Expressway | US 75 | motorway |
+            | bc    | Central Expressway       | US 75 | motorway |
+
+        When I route I should get
+            | waypoints | route                                                          | turns                           |
+            | a,c       | North Central Expressway,Central Expressway,Central Expressway | depart,new name straight,arrive |
+
+    Scenario: Prefix Change
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name                     | ref   | highway  |
+            | ba    | North Central Expressway | US 75 | motorway |
+            | cb    | Central Expressway       | US 75 | motorway |
+
+        When I route I should get
+            | waypoints | route                                                                | turns                           |
+            | c,a       | Central Expressway,North Central Expressway,North Central Expressway | depart,new name straight,arrive |
+
+    Scenario: No Name, Same Reference
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name               | ref   | highway  |
+            | ab    | Central Expressway | US 75 | motorway |
+            | bc    |                    | US 75 | motorway |
+
+        When I route I should get
+            | waypoints | route               | turns         |
+            | a,c       | Central Expressway, | depart,arrive |
+
+    Scenario: No Name, Same Reference
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name               | ref   | highway  |
+            | ab    |                    | US 75 | motorway |
+            | bc    | Central Expressway | US 75 | motorway |
+
+        When I route I should get
+            | waypoints | route               | turns         |
+            | a,c       | ,Central Expressway | depart,arrive |
+
+    Scenario: No Name, Same Reference
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name | ref         | highway  |
+            | ab    |      | US 75;US 69 | motorway |
+            | bc    |      | US 75       | motorway |
+
+        When I route I should get
+            | waypoints | route | turns         |
+            | a,c       | ,     | depart,arrive |
+
+    Scenario: No Name, Same Reference
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name | ref         | highway  |
+            | ab    |      | US 69;US 75 | motorway |
+            | bc    |      | US 75       | motorway |
+
+        When I route I should get
+            | waypoints | route | turns         |
+            | a,c       | ,     | depart,arrive |
+
+    Scenario: No Name, Same Reference
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name | ref         | highway  |
+            | ab    |      | US 75       | motorway |
+            | bc    |      | US 75;US 69 | motorway |
+
+        When I route I should get
+            | waypoints | route | turns         |
+            | a,c       | ,     | depart,arrive |
+
+    Scenario: No Name, Same Reference
+        Given the node map
+            | a |   |   |   | b |   |   |   | c |
+
+        And the ways
+            | nodes | name | ref         | highway  |
+            | ab    |      | US 75       | motorway |
+            | bc    |      | US 69;US 75 | motorway |
+
+        When I route I should get
+            | waypoints | route | turns         |
+            | a,c       | ,     | depart,arrive |
diff --git a/features/guidance/perception.feature b/features/guidance/perception.feature
new file mode 100644
index 0000000..3a6f2cf
--- /dev/null
+++ b/features/guidance/perception.feature
@@ -0,0 +1,126 @@
+ at routing  @guidance @perceived-angles
+Feature: Simple Turns
+
+    Background:
+        Given the profile "car"
+        Given a grid size of 5 meters
+
+    Scenario: Turning into splitting road
+        Given the node map
+            |   | a |   |   |
+            |   | b |   |   |
+            |   |   |   |   |
+            |   |   |   |   |
+            | c |   | d |   |
+            |   |   |   |   |
+            |   |   |   | e |
+            |   |   |   |   |
+            |   |   | f |   |
+
+        And the ways
+            | nodes | name | highway | oneway |
+            | ab    | road | primary | no     |
+            | bc    | road | primary | yes    |
+            | fdb   | road | primary | yes    |
+            | de    | turn | primary | no     |
+
+        When I route I should get
+            | waypoints | turns                           | route          |
+            | f,a       | depart,arrive                   | road,road      |
+            | e,a       | depart,turn slight right,arrive | turn,road,road |
+
+    Scenario: Middle Island
+        Given the node map
+            |   | a |   |
+            |   |   |   |
+            |   | b |   |
+            | c |   | h |
+            |   |   |   |
+            |   |   |   |
+            |   |   |   |
+            |   |   |   |
+            | d |   | g |
+            |   | e |   |
+            |   |   |   |
+            |   | f |   |
+
+        And the ways
+            | nodes | name | oneway |
+            | ab    | road | no     |
+            | ef    | road | no     |
+            | bcde  | road | yes    |
+            | eghb  | road | yes    |
+
+        When I route I should get
+            | waypoints | turns         | route     |
+            | a,f       | depart,arrive | road,road |
+            | c,f       | depart,arrive | road,road |
+            | f,a       | depart,arrive | road,road |
+            | g,a       | depart,arrive | road,road |
+
+    Scenario: Middle Island Over Bridge
+        Given the node map
+            |   | a |   |
+            |   |   |   |
+            |   | b |   |
+            | c |   | h |
+            |   |   |   |
+            |   |   |   |
+            | 1 |   | 2 |
+            |   |   |   |
+            | d |   | g |
+            |   | e |   |
+            |   |   |   |
+            |   | f |   |
+
+        And the ways
+            | nodes | name   | oneway |
+            | ab    | road   | no     |
+            | ef    | road   | no     |
+            | bc    | road   | yes    |
+            | cd    | bridge | yes    |
+            | de    | road   | yes    |
+            | eg    | road   | yes    |
+            | gh    | bridge | yes    |
+            | hb    | road   | yes    |
+
+        When I route I should get
+            | waypoints | turns                           | route            |
+            | a,f       | depart,arrive                   | road,road        |
+            | c,f       | depart,new name straight,arrive | bridge,road,road |
+            | 1,f       | depart,new name straight,arrive | bridge,road,road |
+            | f,a       | depart,arrive                   | road,road        |
+            | g,a       | depart,new name straight,arrive | bridge,road,road |
+            | 2,a       | depart,new name straight,arrive | bridge,road,road |
+
+    @negative
+    Scenario: Don't Collapse Places:
+        Given the node map
+            |   |   |   |   |   |   | h |   |   |   |   |   |   |
+            |   |   |   |   |   |   | g |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            | a | b |   |   |   |   |   |   |   |   |   | e | f |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   | c |   |   |   |   |   |   |
+            |   |   |   |   |   |   | d |   |   |   |   |   |   |
+
+        And the ways
+            | nodes | name   | oneway |
+            | ab    | place  | no     |
+            | cd    | bottom | no     |
+            | ef    | place  | no     |
+            | gh    | top    | no     |
+            | bcegb | place  | yes    |
+
+        When I route I should get
+            | waypoints | turns                                             | route                      |
+            | a,d       | depart,turn right,arrive                          | place,bottom,bottom        |
+            | a,f       | depart,continue left,continue right,arrive        | place,place,place,place    |
+            | d,f       | depart,turn right,continue right,arrive           | bottom,place,place,place   |
+            | d,h       | depart,turn right,continue left,turn right,arrive | bottom,place,place,top,top |
diff --git a/features/guidance/ramp.feature b/features/guidance/ramp.feature
index 2c40d24..0d7acb9 100644
--- a/features/guidance/ramp.feature
+++ b/features/guidance/ramp.feature
@@ -83,14 +83,14 @@ Feature: Ramp Guidance
 
     Scenario: Ramp Off Though Street
         Given the node map
-            |   |   | c |
-            | a | b |   |
-            |   | d |   |
+            |   |   |   |   | c |
+            | a |   |   | b |   |
+            |   |   |   | d |   |
 
         And the ways
-            | nodes | highway       |
-            | abc   | tertiary      |
-            | bd    | motorway_link |
+            | nodes | highway       | oneway |
+            | abc   | tertiary      | yes    |
+            | bd    | motorway_link | yes    |
 
        When I route I should get
             | waypoints | route     | turns                          |
@@ -108,9 +108,9 @@ Feature: Ramp Guidance
             | bd    | motorway_link |
 
        When I route I should get
-            | waypoints | route       | turns                             |
-            | a,d       | abc,bd,bd   | depart,on ramp straight,arrive |
-            | a,c       | abc,abc,abc | depart,continue left,arrive       |
+            | waypoints | route     | turns                          |
+            | a,d       | abc,bd,bd | depart,on ramp straight,arrive |
+            | a,c       | abc,abc   | depart,arrive                  |
 
     Scenario: Fork Ramp Off Turning Though Street
         Given the node map
@@ -124,9 +124,9 @@ Feature: Ramp Guidance
             | bd    | motorway_link |
 
        When I route I should get
-            | waypoints | route       | turns                             |
-            | a,d       | abc,bd,bd   | depart,on ramp right,arrive    |
-            | a,c       | abc,abc,abc | depart,continue left,arrive       |
+            | waypoints | route     | turns                       |
+            | a,d       | abc,bd,bd | depart,on ramp right,arrive |
+            | a,c       | abc,abc   | depart,arrive               |
 
     Scenario: Fork Ramp
         Given the node map
@@ -174,9 +174,9 @@ Feature: Ramp Guidance
             | bd    | motorway_link |
 
        When I route I should get
-            | waypoints | route       | turns                                    |
-            | a,d       | abc,bd,bd   | depart,on ramp slight right,arrive    |
-            | a,c       | abc,abc,abc | depart,continue slight left,arrive       |
+            | waypoints | route     | turns                              |
+            | a,d       | abc,bd,bd | depart,on ramp slight right,arrive |
+            | a,c       | abc,abc   | depart,arrive                      |
 
     Scenario: Fork Slight Ramp on Obvious Through Street
         Given the node map
diff --git a/features/guidance/roundabout-bike.feature b/features/guidance/roundabout-bike.feature
index 3652c15..b50b7a5 100644
--- a/features/guidance/roundabout-bike.feature
+++ b/features/guidance/roundabout-bike.feature
@@ -5,37 +5,6 @@ Feature: Basic Roundabout
         Given the profile "bicycle"
         Given a grid size of 10 meters
 
-    Scenario: Enter and Exit
-        Given the node map
-            |   |   | a |   |   |
-            |   |   | b |   |   |
-            | h | g |   | c | d |
-            |   |   | e |   |   |
-            |   |   | f |   |   |
-
-       And the ways
-            | nodes  | junction   |
-            | ab     |            |
-            | cd     |            |
-            | ef     |            |
-            | gh     |            |
-            | bgecb  | roundabout |
-
-       When I route I should get
-           | waypoints | route    | turns                           |
-           | a,d       | ab,cd,cd | depart,roundabout-exit-3,arrive |
-           | a,f       | ab,ef,ef | depart,roundabout-exit-2,arrive |
-           | a,h       | ab,gh,gh | depart,roundabout-exit-1,arrive |
-           | d,f       | cd,ef,ef | depart,roundabout-exit-3,arrive |
-           | d,h       | cd,gh,gh | depart,roundabout-exit-2,arrive |
-           | d,a       | cd,ab,ab | depart,roundabout-exit-1,arrive |
-           | f,h       | ef,gh,gh | depart,roundabout-exit-3,arrive |
-           | f,a       | ef,ab,ab | depart,roundabout-exit-2,arrive |
-           | f,d       | ef,cd,cd | depart,roundabout-exit-1,arrive |
-           | h,a       | gh,ab,ab | depart,roundabout-exit-3,arrive |
-           | h,d       | gh,cd,cd | depart,roundabout-exit-2,arrive |
-           | h,f       | gh,ef,ef | depart,roundabout-exit-1,arrive |
-
     Scenario: Only Enter
         Given the node map
             |   |   | a |   |   |
diff --git a/features/guidance/roundabout-turn-bike.feature b/features/guidance/roundabout-turn-bike.feature
new file mode 100644
index 0000000..77aa20c
--- /dev/null
+++ b/features/guidance/roundabout-turn-bike.feature
@@ -0,0 +1,37 @@
+ at routing  @guidance
+Feature: Basic Roundabout
+
+    Background:
+        Given the profile "bicycle"
+        Given a grid size of 10 meters
+
+    Scenario: Enter and Exit
+        Given the node map
+            |   |   | a |   |   |
+            |   |   | b |   |   |
+            | h | g |   | c | d |
+            |   |   | e |   |   |
+            |   |   | f |   |   |
+
+       And the ways
+            | nodes  | junction   |
+            | ab     |            |
+            | cd     |            |
+            | ef     |            |
+            | gh     |            |
+            | bgecb  | roundabout |
+
+       When I route I should get
+           | waypoints | route    | turns                                         |
+           | a,d       | ab,cd,cd | depart,roundabout turn left exit-3,arrive     |
+           | a,f       | ab,ef,ef | depart,roundabout turn straight exit-2,arrive |
+           | a,h       | ab,gh,gh | depart,roundabout turn right exit-1,arrive    |
+           | d,f       | cd,ef,ef | depart,roundabout turn left exit-3,arrive     |
+           | d,h       | cd,gh,gh | depart,roundabout turn straight exit-2,arrive |
+           | d,a       | cd,ab,ab | depart,roundabout turn right exit-1,arrive    |
+           | f,h       | ef,gh,gh | depart,roundabout turn left exit-3,arrive     |
+           | f,a       | ef,ab,ab | depart,roundabout turn straight exit-2,arrive |
+           | f,d       | ef,cd,cd | depart,roundabout turn right exit-1,arrive    |
+           | h,a       | gh,ab,ab | depart,roundabout turn left exit-3,arrive     |
+           | h,d       | gh,cd,cd | depart,roundabout turn straight exit-2,arrive |
+           | h,f       | gh,ef,ef | depart,roundabout turn right exit-1,arrive    |
diff --git a/features/guidance/roundabout-turn.feature b/features/guidance/roundabout-turn.feature
index 66609f8..d62c5fe 100644
--- a/features/guidance/roundabout-turn.feature
+++ b/features/guidance/roundabout-turn.feature
@@ -389,3 +389,125 @@ Feature: Basic Roundabout
            | a,f       | ab,ef,ef | depart,roundabout turn straight exit-2,arrive | 0->180,180->224,180->0 |
            | a,h       | ab,gh,gh | depart,roundabout turn right exit-1,arrive    | 0->180,180->224,270->0 |
 
+    Scenario: Enter and Exit - Bearings
+        Given the node map
+            |   |   |   | a |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   | i | b | l |   |   |
+            | h |   | g |   | c |   | d |
+            |   |   | j | e | k |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   | f |   |   |   |
+
+       And the ways
+            | nodes      | junction   |
+            | ab         |            |
+            | cd         |            |
+            | ef         |            |
+            | gh         |            |
+            | bigjekclb  | roundabout |
+
+       When I route I should get
+           | waypoints | route    | turns                                         | bearing                |
+           | a,d       | ab,cd,cd | depart,roundabout turn left exit-3,arrive     | 0->180,180->270,90->0  |
+           | a,f       | ab,ef,ef | depart,roundabout turn straight exit-2,arrive | 0->180,180->270,180->0 |
+           | a,h       | ab,gh,gh | depart,roundabout turn right exit-1,arrive    | 0->180,180->270,270->0 |
+
+    Scenario: Large radius Roundabout Intersection and ways modelled out: East Mission St, North 7th St
+    # http://www.openstreetmap.org/way/348812150
+    # Note: grid size is 3 meter, this roundabout is more like 5-10 meters in radius
+        Given the node map
+           |   |   |   | a |   |   |   |   |   |
+           |   |   |   |   |   |   |   |   |   |
+           |   |   |   | b |   | n |   |   |   |
+           |   |   |   |   |   |   |   |   |   |
+           |   |   | c |   |   |   | m |   |   |
+           |   |   |   |   |   |   |   |   |   |
+           | e |   | d |   |   |   | k |   | l |
+           |   |   |   |   |   |   |   |   |   |
+           |   |   | f |   |   |   | j |   |   |
+           |   |   |   |   |   |   |   |   |   |
+           |   |   |   | g |   | i |   |   |   |
+           |   |   |   |   |   |   |   |   |   |
+           |   |   |   | h |   |   |   |   |   |
+
+       And the ways
+            | nodes       | junction   | highway  | name            |
+            | ab          |            | tertiary | North 7th St    |
+            | ed          |            | tertiary | East Mission St |
+            | hg          |            | tertiary | North 7th St    |
+            | lk          |            | tertiary | East Mission St |
+            | bcdfgijkmnb | roundabout | tertiary | Roundabout      |
+
+       When I route I should get
+           | waypoints | route                                           | turns                                         |
+           | a,e       | North 7th St,East Mission St,East Mission St    | depart,roundabout turn right exit-1,arrive    |
+           | a,h       | North 7th St,North 7th St,North 7th St          | depart,roundabout turn straight exit-2,arrive |
+           | a,l       | North 7th St,East Mission St,East Mission St    | depart,roundabout turn left exit-3,arrive     |
+           | h,l       | North 7th St,East Mission St,East Mission St    | depart,roundabout turn right exit-1,arrive    |
+           | h,a       | North 7th St,North 7th St,North 7th St          | depart,roundabout turn straight exit-2,arrive |
+           | h,e       | North 7th St,East Mission St,East Mission St    | depart,roundabout turn left exit-3,arrive     |
+           | e,h       | East Mission St,North 7th St,North 7th St       | depart,roundabout turn right exit-1,arrive    |
+           | e,l       | East Mission St,East Mission St,East Mission St | depart,roundabout turn straight exit-2,arrive |
+           | e,a       | East Mission St,North 7th St,North 7th St       | depart,roundabout turn left exit-3,arrive     |
+           | l,a       | East Mission St,North 7th St,North 7th St       | depart,roundabout turn right exit-1,arrive    |
+           | l,e       | East Mission St,East Mission St,East Mission St | depart,roundabout turn straight exit-2,arrive |
+           | l,h       | East Mission St,North 7th St,North 7th St       | depart,roundabout turn left exit-3,arrive     |
+
+    Scenario: Enter and Exit - Traffic Signals
+        Given the node map
+            |   |   | a |   |   |
+            |   | i | b | l |   |
+            | h | g |   | c | d |
+            |   | j | e | k |   |
+            |   |   | f |   |   |
+
+       And the nodes
+            | node | highway         |
+            | i    | traffic_signals |
+            | j    | traffic_signals |
+            | k    | traffic_signals |
+            | l    | traffic_signals |
+
+       And the ways
+            | nodes     | junction   |
+            | ab        |            |
+            | cd        |            |
+            | ef        |            |
+            | gh        |            |
+            | bigjekclb | roundabout |
+
+       When I route I should get
+           | waypoints | route    | turns                                         |
+           | a,d       | ab,cd,cd | depart,roundabout turn left exit-3,arrive     |
+           | a,f       | ab,ef,ef | depart,roundabout turn straight exit-2,arrive |
+           | a,h       | ab,gh,gh | depart,roundabout turn right exit-1,arrive    |
+           | d,f       | cd,ef,ef | depart,roundabout turn left exit-3,arrive     |
+           | d,h       | cd,gh,gh | depart,roundabout turn straight exit-2,arrive |
+           | d,a       | cd,ab,ab | depart,roundabout turn right exit-1,arrive    |
+           | f,h       | ef,gh,gh | depart,roundabout turn left exit-3,arrive     |
+           | f,a       | ef,ab,ab | depart,roundabout turn straight exit-2,arrive |
+           | f,d       | ef,cd,cd | depart,roundabout turn right exit-1,arrive    |
+           | h,a       | gh,ab,ab | depart,roundabout turn left exit-3,arrive     |
+           | h,d       | gh,cd,cd | depart,roundabout turn straight exit-2,arrive |
+           | h,f       | gh,ef,ef | depart,roundabout turn right exit-1,arrive    |
+
+    #http://www.openstreetmap.org/#map=19/41.03275/-2.18990
+    #at some point we probably want to recognise these situations and don't mention the roundabout at all here
+    Scenario: Enter And Exit Throughabout
+        Given the node map
+            |   |   |   |   |   | h |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |
+            | c | b |   | d |   |   |   | e |   | f |
+            |   |   |   |   |   |   |   |   |   |   |
+            |   | a |   |   |   | g |   |   |   |   |
+
+        And the ways
+            | nodes | highway       | name    | junction   | oneway |
+            | dghd  | tertiary_link |         | roundabout |        |
+            | cbdef | trunk         | through |            | no     |
+            | ab    | residential   | in      |            |        |
+
+        When I route I should get
+            | waypoints | turns                                                    | route                      |
+            | a,f       | depart,turn right,roundabout turn straight exit-1,arrive | in,through,through,through |
diff --git a/features/guidance/roundabout.feature b/features/guidance/roundabout.feature
index fe6605b..a02c762 100644
--- a/features/guidance/roundabout.feature
+++ b/features/guidance/roundabout.feature
@@ -5,37 +5,6 @@ Feature: Basic Roundabout
         Given the profile "car"
         Given a grid size of 10 meters
 
-    Scenario: Enter and Exit
-        Given the node map
-            |   |   | a |   |   |
-            |   |   | b |   |   |
-            | h | g |   | c | d |
-            |   |   | e |   |   |
-            |   |   | f |   |   |
-
-       And the ways
-            | nodes  | junction   |
-            | ab     |            |
-            | cd     |            |
-            | ef     |            |
-            | gh     |            |
-            | bgecb  | roundabout |
-
-       When I route I should get
-           | waypoints | route    | turns                           |
-           | a,d       | ab,cd,cd | depart,roundabout-exit-3,arrive |
-           | a,f       | ab,ef,ef | depart,roundabout-exit-2,arrive |
-           | a,h       | ab,gh,gh | depart,roundabout-exit-1,arrive |
-           | d,f       | cd,ef,ef | depart,roundabout-exit-3,arrive |
-           | d,h       | cd,gh,gh | depart,roundabout-exit-2,arrive |
-           | d,a       | cd,ab,ab | depart,roundabout-exit-1,arrive |
-           | f,h       | ef,gh,gh | depart,roundabout-exit-3,arrive |
-           | f,a       | ef,ab,ab | depart,roundabout-exit-2,arrive |
-           | f,d       | ef,cd,cd | depart,roundabout-exit-1,arrive |
-           | h,a       | gh,ab,ab | depart,roundabout-exit-3,arrive |
-           | h,d       | gh,cd,cd | depart,roundabout-exit-2,arrive |
-           | h,f       | gh,ef,ef | depart,roundabout-exit-1,arrive |
-
     Scenario: Only Enter
         Given the node map
             |   |   | a |   |   |
@@ -67,6 +36,25 @@ Feature: Basic Roundabout
            | h,c       | gh,bcegb,bcegb | depart,roundabout-exit-undefined,arrive |
            | h,e       | gh,bcegb,bcegb | depart,roundabout-exit-undefined,arrive |
 
+    #2927
+    Scenario: Only Roundabout
+        Given the node map
+            |   |   | a |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            | b |   |   |   | d |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   | c |   |   |
+
+       And the ways
+            | nodes  | junction   |
+            | abcda  | roundabout |
+
+       When I route I should get
+           | waypoints | route       | turns         |
+           | a,c       | abcda,abcda | depart,arrive |
+
     Scenario: Only Exit
         Given the node map
             |   |   | a |   |   |
@@ -260,7 +248,7 @@ Feature: Basic Roundabout
             | a,e       | ab,ce,ce | depart,roundabout-exit-1,arrive |
             | a,f       | ab,df,df | depart,roundabout-exit-2,arrive |
 
-       Scenario: Collinear in Y
+    Scenario: Collinear in Y
         Given the node map
             |   | a |
             |   | b |
@@ -280,87 +268,6 @@ Feature: Basic Roundabout
             | a,e       | ab,ce,ce | depart,roundabout-exit-1,arrive |
             | a,f       | ab,df,df | depart,roundabout-exit-2,arrive |
 
-       Scenario: Collinear in X,Y
-        Given the node map
-            | a |   |   |
-            | b |   |   |
-            | c | d | f |
-            | e |   |   |
-
-        And the ways
-            | nodes | junction   |
-            | ab    |            |
-            | bcdb  | roundabout |
-            | ce    |            |
-            | df    |            |
-
-        When I route I should get
-            | waypoints | route    | turns                           |
-            | a,e       | ab,ce,ce | depart,roundabout-exit-1,arrive |
-            | a,f       | ab,df,df | depart,roundabout-exit-2,arrive |
-
-       Scenario: Collinear in X,Y
-        Given the node map
-            | a |   |   |
-            | d |   |   |
-            | b | c | f |
-            | e |   |   |
-
-        And the ways
-            | nodes | junction   |
-            | ad    |            |
-            | bcdb  | roundabout |
-            | be    |            |
-            | cf    |            |
-
-        When I route I should get
-            | waypoints | route    | turns                           |
-            | a,e       | ad,be,be | depart,roundabout-exit-1,arrive |
-            | a,f       | ad,cf,cf | depart,roundabout-exit-2,arrive |
-
-       Scenario: Collinear in X,Y
-        Given the node map
-            | a |   |   |
-            | c |   |   |
-            | d | b | f |
-            | e |   |   |
-
-        And the ways
-            | nodes | junction   |
-            | ac    |            |
-            | bcdb  | roundabout |
-            | de    |            |
-            | bf    |            |
-
-        When I route I should get
-            | waypoints | route    | turns                           |
-            | a,e       | ac,de,de | depart,roundabout-exit-1,arrive |
-            | a,f       | ac,bf,bf | depart,roundabout-exit-2,arrive |
-
-    Scenario: Enter and Exit - Bearings
-        Given the node map
-            |   |   |   | a |   |   |   |
-            |   |   |   |   |   |   |   |
-            |   |   | i | b | l |   |   |
-            | h |   | g |   | c |   | d |
-            |   |   | j | e | k |   |   |
-            |   |   |   |   |   |   |   |
-            |   |   |   | f |   |   |   |
-
-       And the ways
-            | nodes      | junction   |
-            | ab         |            |
-            | cd         |            |
-            | ef         |            |
-            | gh         |            |
-            | bigjekclb  | roundabout |
-
-       When I route I should get
-           | waypoints | route    | turns                           | bearing                |
-           | a,d       | ab,cd,cd | depart,roundabout-exit-3,arrive | 0->180,180->270,90->0  |
-           | a,f       | ab,ef,ef | depart,roundabout-exit-2,arrive | 0->180,180->270,180->0 |
-           | a,h       | ab,gh,gh | depart,roundabout-exit-1,arrive | 0->180,180->270,270->0 |
-
     Scenario: Motorway Roundabout
     #See 39.933742 -75.082345
         Given the node map
@@ -395,9 +302,9 @@ Feature: Basic Roundabout
             | dmg   | roundabout |          | trunk_link | yes    |        |
 
         When I route I should get
-            | waypoints | route                                                 | turns                           |
-            | a,e       | crescent (US 130),crescent (US 130),crescent (US 130) | depart,roundabout-exit-3,arrive |
-            | j,l       | NJ 38,NJ 38,NJ 38                                     | depart,roundabout-exit-2,arrive |
+            | waypoints | route                                                 | turns                           | ref                     |
+            | a,e       | crescent,crescent,crescent                            | depart,roundabout-exit-3,arrive | US 130,US 130,US 130    |
+            | j,l       | ,,                                                    | depart,roundabout-exit-2,arrive | NJ 38,NJ 38,NJ 38       |
 
     Scenario: Double Roundabout with through-lane
     #http://map.project-osrm.org/?z=18&center=38.911752%2C-77.048667&loc=38.912003%2C-77.050831&loc=38.909277%2C-77.042516&hl=en&alt=0
@@ -438,41 +345,61 @@ Feature: Basic Roundabout
             | waypoints | route                                                   | turns                                                     |
             | a,k       | massachusetts,massachusetts,massachusetts,massachusetts | depart,sheridan circle-exit-2,dupont circle-exit-1,arrive |
 
-    Scenario: Enter and Exit - Traffic Signals
+    #2856 - http://www.openstreetmap.org/#map=19/47.23318/-1.56563
+    Scenario: Linked Roundabouts
         Given the node map
-            |   |   | a |   |   |
-            |   | i | b | l |   |
-            | h | g |   | c | d |
-            |   | j | e | k |   |
-            |   |   | f |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   | x |
+            |   | u |   |   |   |   |   |   |   |   |   |   | r |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   | t |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   | s |   |   |   |
+            |   |   | v |   |   | i |   | h |   | g |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   | q |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   | j |   |   |   |   |   |   |   | f |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   | a |   |   |   |   |   |   |   | e |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   | b |   | c |   | d |   | p |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   | m |   |   |   |   |   |   |   | n |   |   |   |
+            |   |   |   |   | l |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   | k |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
+            |   | w |   |   |   |   |   |   |   |   |   | o |   |   |
 
-       And the nodes
-            | node | highway         |
-            | i    | traffic_signals |
-            | j    | traffic_signals |
-            | k    | traffic_signals |
-            | l    | traffic_signals |
+        And the ways
+            | nodes | junction   | name | highway   | oneway |
+            | abija | roundabout | egg  | primary   | yes    |
+            | defgd | roundabout | egg  | primary   | yes    |
+            | bcd   | roundabout | egg  | primary   | yes    |
+            | ghi   |            | egg  | primary   | yes    |
+            | amklb |            | ll   | primary   | yes    |
+            | wk    |            | ll   | primary   | no     |
+            | dnope |            | lr   | secondary | yes    |
+            | fqrsg |            | tr   | primary   | yes    |
+            | rx    |            | tr   | primary   | no     |
+            | ituvj |            | tl   | primary   | yes    |
 
-       And the ways
-            | nodes     | junction   |
-            | ab        |            |
-            | cd        |            |
-            | ef        |            |
-            | gh        |            |
-            | bigjekclb | roundabout |
+        And the nodes
+            | node | highway  |
+            | c    | give_way |
+            | h    | give_way |
 
-       When I route I should get
-           | waypoints | route    | turns                           |
-           | a,d       | ab,cd,cd | depart,roundabout-exit-3,arrive |
-           | a,f       | ab,ef,ef | depart,roundabout-exit-2,arrive |
-           | a,h       | ab,gh,gh | depart,roundabout-exit-1,arrive |
-           | d,f       | cd,ef,ef | depart,roundabout-exit-3,arrive |
-           | d,h       | cd,gh,gh | depart,roundabout-exit-2,arrive |
-           | d,a       | cd,ab,ab | depart,roundabout-exit-1,arrive |
-           | f,h       | ef,gh,gh | depart,roundabout-exit-3,arrive |
-           | f,a       | ef,ab,ab | depart,roundabout-exit-2,arrive |
-           | f,d       | ef,cd,cd | depart,roundabout-exit-1,arrive |
-           | h,a       | gh,ab,ab | depart,roundabout-exit-3,arrive |
-           | h,d       | gh,cd,cd | depart,roundabout-exit-2,arrive |
-           | h,f       | gh,ef,ef | depart,roundabout-exit-1,arrive |
+        When I route I should get
+            | waypoints | route            | turns                                                   |
+            # since we cannot handle these invalid roundabout tags yet, we cannout output roundabout taggings. This will hopefully change some day
+            #| w,x       | ll,egg,egg,tr,tr | depart,roundabout-exit-1,roundabout-exit-2,arrive       |
+            | w,x       | ll,egg,egg,tr,tr | depart,turn right,continue left,turn slight left,arrive |
 
diff --git a/features/guidance/staggered-intersections.feature b/features/guidance/staggered-intersections.feature
new file mode 100644
index 0000000..63e0dc5
--- /dev/null
+++ b/features/guidance/staggered-intersections.feature
@@ -0,0 +1,92 @@
+ at routing  @guidance @staggered-intersections
+Feature: Staggered Intersections
+
+    Background:
+        Given the profile "car"
+        Given a grid size of 1 meters
+        # Note the one meter grid size: staggered intersections make zig-zags of a couple of meters only
+
+    # https://www.openstreetmap.org/#map=19/39.26022/-84.25144
+    Scenario: Staggered Intersection: Oak St, Cedar Dr
+        Given the node map
+            |   |   | j |   |   |
+            | a | b | c |   |   |
+            |   |   | d |   |   |
+            |   |   | e | f | g |
+            |   |   | h |   |   |
+            |   |   | i |   |   |
+
+        And the ways
+            | nodes  | highway     | name     |
+            | abc    | residential | Oak St   |
+            | efg    | residential | Oak St   |
+            | jcdehi | residential | Cedar Dr |
+
+        When I route I should get
+            | waypoints | route         | turns |
+            | a,g       | Oak St,Oak St | depart,arrive |
+            | g,a       | Oak St,Oak St | depart,arrive |
+
+    Scenario: Staggered Intersection: do not collapse if long segment in between
+        Given the node map
+            |   |   | j |   |   |
+            | a | b | c |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   | d |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   | e | f | g |
+            |   |   | h |   |   |
+            |   |   | i |   |   |
+
+        And the ways
+            | nodes  | highway     | name     |
+            | abc    | residential | Oak St   |
+            | efg    | residential | Oak St   |
+            | jcdehi | residential | Cedar Dr |
+
+        When I route I should get
+            | waypoints | route                         | turns                              |
+            | a,g       | Oak St,Cedar Dr,Oak St,Oak St | depart,turn right,turn left,arrive |
+            | g,a       | Oak St,Cedar Dr,Oak St,Oak St | depart,turn right,turn left,arrive |
+
+    Scenario: Staggered Intersection: do not collapse if not left-right or right-left
+        Given the node map
+            |   |   | j |   |   |
+            | a | b | c |   |   |
+            |   |   | d |   |   |
+            | g | f | e |   |   |
+            |   |   | h |   |   |
+            |   |   | i |   |   |
+
+        And the ways
+            | nodes  | highway     | name     |
+            | abc    | residential | Oak St   |
+            | efg    | residential | Oak St   |
+            | jcdehi | residential | Cedar Dr |
+
+        When I route I should get
+            | waypoints | route                | turns                        |
+            | a,g       | Oak St,Oak St,Oak St | depart,continue uturn,arrive |
+            | g,a       | Oak St,Oak St,Oak St | depart,continue uturn,arrive |
+
+    Scenario: Staggered Intersection: do not collapse if the names are not the same
+        Given the node map
+            |   |   | j |   |   |
+            | a | b | c |   |   |
+            |   |   | d |   |   |
+            |   |   | e | f | g |
+            |   |   | h |   |   |
+            |   |   | i |   |   |
+
+        And the ways
+            | nodes  | highway     | name     |
+            | abc    | residential | Oak St   |
+            | efg    | residential | Elm St   |
+            | jcdehi | residential | Cedar Dr |
+
+        When I route I should get
+            | waypoints | route                         | turns                              |
+            | a,g       | Oak St,Cedar Dr,Elm St,Elm St | depart,turn right,turn left,arrive |
+            | g,a       | Elm St,Cedar Dr,Oak St,Oak St | depart,turn right,turn left,arrive |
diff --git a/features/guidance/suffix-changes.feature b/features/guidance/suffix-changes.feature
index 9b53adb..ae7a79a 100644
--- a/features/guidance/suffix-changes.feature
+++ b/features/guidance/suffix-changes.feature
@@ -3,7 +3,7 @@ Feature: Suppress New Names on dedicated Suffices
 
     Background:
         Given the profile "car"
-        Given a grid size of 10 meters
+        Given a grid size of 2000 meters
 
     Scenario: Suffix To Suffix
         Given the node map
@@ -28,8 +28,8 @@ Feature: Suppress New Names on dedicated Suffices
             | bc     | 42 S | 101 |
 
        When I route I should get
-            | waypoints | route           | turns         |
-            | a,c       | 42 N,42 S (101) | depart,arrive |
+            | waypoints | route           | turns         | ref   |
+            | a,c       | 42 N,42 S       | depart,arrive | ,101  |
 
     Scenario: Prefix Change
         Given the node map
@@ -44,6 +44,19 @@ Feature: Suppress New Names on dedicated Suffices
             | waypoints | route           | turns         |
             | a,c       | West 42,East 42 | depart,arrive |
 
+    Scenario: Prefix Change ref
+        Given the node map
+            | a |   | b |   | c |
+
+        And the ways
+            | nodes  | name    |
+            | ab     | West 42 |
+            | bc     | 42      |
+
+       When I route I should get
+            | waypoints | route      | turns         |
+            | a,c       | West 42,42 | depart,arrive |
+
     Scenario: Prefix Change and Reference
         Given the node map
             | a |   | b |   | c |
@@ -54,8 +67,8 @@ Feature: Suppress New Names on dedicated Suffices
             | bc     | East 42 |     |
 
        When I route I should get
-            | waypoints | route                 | turns         |
-            | a,c       | West 42 (101),East 42 | depart,arrive |
+            | waypoints | route                 | turns         | ref  |
+            | a,c       | West 42,East 42       | depart,arrive | 101, |
 
     Scenario: Suffix To Suffix - Turn
         Given the node map
diff --git a/features/guidance/suppressed.feature b/features/guidance/suppressed.feature
index 89b5865..02555ad 100644
--- a/features/guidance/suppressed.feature
+++ b/features/guidance/suppressed.feature
@@ -32,8 +32,8 @@ Feature: Suppressed Turns
             | ef    | motorway | highway  | A1    |
 
         When I route I should get
-            | waypoints | route                     | turns         |
-            | a,f       | highway (A1),highway (A1) | depart,arrive |
+            | waypoints | route                     | turns         | ref    |
+            | a,f       | highway,highway           | depart,arrive | A1,A1  |
 
 
     Scenario: Don't Announce Turn on following major road class -- service
diff --git a/features/guidance/turn-lanes.feature b/features/guidance/turn-lanes.feature
index 8a57c42..81c094b 100644
--- a/features/guidance/turn-lanes.feature
+++ b/features/guidance/turn-lanes.feature
@@ -5,7 +5,7 @@ Feature: Turn Lane Guidance
         Given the profile "car"
         Given a grid size of 20 meters
 
-    @bug
+    @simple
     Scenario: Basic Turn Lane 3-way Turn with empty lanes
         Given the node map
             | a |   | b |   | c |
@@ -24,6 +24,23 @@ Feature: Turn Lane Guidance
             | c,a       | straight,in,in       | depart,new name straight,arrive | ,left:false straight:true none:true none:true,   |
             | c,d       | straight,right,right | depart,turn left,arrive         | ,left:true straight:false none:false none:false, |
 
+    Scenario: Basic Turn Lane 3-way Turn with designated lane
+        Given the node map
+            | a |   | b |   | c |
+            |   |   | d |   |   |
+
+        And the ways
+            | nodes  | turn:lanes     | turn:lanes:forward      | name     | vehicle:lanes:forward |
+            | ab     |                | through\|through\|right | in       | yes\|no\|yes          |
+            | bc     |                |                         | straight |                       |
+            | bd     |                |                         | right    |                       |
+
+       When I route I should get
+            | waypoints | route                | turns                           | lanes                       |
+            | a,c       | in,straight,straight | depart,new name straight,arrive | ,straight:true right:false, |
+            | a,d       | in,right,right       | depart,turn right,arrive        | ,straight:false right:true, |
+
+    @simple
     Scenario: Basic Turn Lane 4-Way Turn
         Given the node map
             |   |   | e |   |   |
@@ -46,6 +63,7 @@ Feature: Turn Lane Guidance
             | d,e       | right,left,left         | depart,new name straight,arrive | ,left:false none:true,  |
             | d,c       | right,straight,straight | depart,turn right,arrive        | ,left:false none:true,  |
 
+    @simple @none
     Scenario: Basic Turn Lane 4-Way Turn using none
         Given the node map
             |   |   | e |   |   |
@@ -65,6 +83,7 @@ Feature: Turn Lane Guidance
             | a,d       | in,right,right       | depart,turn right,arrive        | ,none:false right:true, |
             | a,e       | in,left,left         | depart,turn left,arrive         | ,none:true right:false, |
 
+    @simple @reverse
     Scenario: Basic Turn Lane 4-Way With U-Turn Lane
         Given the node map
             |   |   | e |   |   |
@@ -87,6 +106,7 @@ Feature: Turn Lane Guidance
 
 
     #this next test requires decision on how to announce lanes for going straight if there is no turn
+    @simple @psv @none
     Scenario: Turn with Bus-Lane
         Given the node map
             | a |   | b |   | c |
@@ -104,7 +124,44 @@ Feature: Turn Lane Guidance
             | a,d       | road,turn,turn | depart,turn right,arrive | ,straight:false right:true, |
             | a,c       | road,road      | depart,arrive            | ,                           |
 
-    @PROFILE @LANES
+    Scenario: Turn with Bus-Lane Left
+        Given the node map
+            |   |   | d |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            | a |   | b |   | c |   | f |
+            |   |   |   |   | e |   |   |
+
+        And the ways
+            | nodes | name | turn:lanes:forward  | lanes:psv:forward | oneway |
+            | ab    | road | left\|through\|     | 1                 | yes    |
+            | bc    | road |                     |                   | yes    |
+            | bd    | turn |                     |                   | yes    |
+            | cf    | turn |                     |                   | yes    |
+            | ce    | side |                     |                   | yes    |
+
+        When I route I should get
+            | waypoints | route          | turns                   | lanes                       |
+            | a,d       | road,turn,turn | depart,turn left,arrive | ,left:true straight:false,  |
+            | a,c       | road,road      | depart,arrive           | ,                           |
+
+    # This tests whether empty/invalid PSV tags cause osrm-extract to crash
+    Scenario: Turn with Bus-Lane
+        Given the node map
+            | a |   | b |   | c |
+            |   |   |   |   |   |
+            |   |   | d |   |   |
+
+        And the ways
+            | nodes | name | turn:lanes:forward | lanes:psv:forward | lanes:psv:backward |
+            | ab    | road | through\|right\|   | 1                 | foo                |
+            | bc    | road |                    |                   |                    |
+            | bd    | turn |                    |                   |                    |
+
+        When I route I should get
+            | waypoints | route          | turns                    | lanes                       |
+            | a,d       | road,turn,turn | depart,turn right,arrive | ,straight:false right:true, |
+
+    @simple @psv
     Scenario: Turn with Bus-Lane but without lanes
         Given the node map
             | a |   | b |   | c |
@@ -123,7 +180,7 @@ Feature: Turn Lane Guidance
             | a,c       | road,road      | depart,arrive            |
 
     #turn lanes are often drawn at the incoming road, even though the actual turn requires crossing the intersection first
-    @todo @bug
+    @todo @collapse @partition-lanes
     Scenario: Turn Lanes at Segregated Road
         Given the node map
             |   |   | i | l |   |   |
@@ -168,6 +225,7 @@ Feature: Turn Lane Guidance
             | i,l       | cross,cross,cross | depart,continue uturn,arrive    | ,left:true straight:false,                      |
 
     #copy of former case to prevent further regression
+    @collapse @partition-lanes
     Scenario: Turn Lanes at Segregated Road
         Given the node map
             |   |   | i | l |   |   |
@@ -201,6 +259,7 @@ Feature: Turn Lane Guidance
             | i,j       | cross,cross       | depart,arrive                | ,                                               |
             | i,l       | cross,cross,cross | depart,continue uturn,arrive | ,left:true straight:false,                      |
 
+    @partition-lanes
     Scenario: Turn Lanes at Segregated Road
         Given the node map
             |   |   | g | f |   |   |
@@ -223,6 +282,7 @@ Feature: Turn Lane Guidance
             | a,j       | road,cross,cross  | depart,turn right,arrive        | ,left:false straight:false right:true, |
 
     #this can happen due to traffic lights / lanes not drawn up to the intersection itself
+    @2654 @previous-lanes
     Scenario: Turn Lanes Given earlier than actual turn
         Given the node map
             | a |   | b | c |   | d |
@@ -241,6 +301,7 @@ Feature: Turn Lane Guidance
             | a,e       | road,turn,turn | depart,turn right,arrive | ,none:false right:true, |
             | a,d       | road,road      | depart,arrive            | ,                       |
 
+    @2654 @previous-lanes
     Scenario: Turn Lanes Given earlier than actual turn
         Given the node map
             | a |   | b | c | d |   | e |   | f | g | h |   | i |
@@ -263,6 +324,7 @@ Feature: Turn Lane Guidance
             | i,j       | road,first-turn,first-turn   | depart,turn left,arrive  | ,left:true none:false,  |
             | i,a       | road,road                    | depart,arrive            | ,                       |
 
+    @previous-lanes
     Scenario: Passing a one-way street
         Given the node map
             | e |   |   | f |   |
@@ -279,6 +341,7 @@ Feature: Turn Lane Guidance
             | waypoints | route          | turns                   | lanes                      |
             | a,f       | road,turn,turn | depart,turn left,arrive | ,left:true straight:false, |
 
+    @partition-lanes
     Scenario: Passing a one-way street, partly pulled back lanes
         Given the node map
             | e |   |   | f |   |
@@ -296,8 +359,10 @@ Feature: Turn Lane Guidance
         When I route I should get
             | waypoints | route            | turns                    | lanes                            |
             | a,f       | road,turn,turn   | depart,turn left,arrive  | ,left:true straight;right:false, |
+            | a,d       | road,road        | depart,arrive            | ,                                |
             | a,g       | road,right,right | depart,turn right,arrive | ,left:false straight;right:true, |
 
+    @partition-lanes @previous-lanes
     Scenario: Passing a one-way street, partly pulled back lanes, no through
         Given the node map
             | e |   |   | f |
@@ -317,7 +382,7 @@ Feature: Turn Lane Guidance
             | a,f       | road,turn,turn   | depart,turn left,arrive  | ,left:true right:false, |
             | a,g       | road,right,right | depart,turn right,arrive | ,left:false right:true, |
 
-    @todo @bug
+    @todo @partition-lanes @previous-lanes
     Scenario: Narrowing Turn Lanes
         Given the node map
             |   |   |   |   | g |   |
@@ -340,6 +405,7 @@ Feature: Turn Lane Guidance
             | a,e       | road,through,through | depart,new name straight,arrive | ,left:false straight:true right:false, |
             | a,f       | road,right,right     | depart,turn right,arrive        | ,left:false straight:false right:true, |
 
+    @previous-lanes
     Scenario: Turn at a traffic light
         Given the node map
             | a | b | c | d |
@@ -361,7 +427,7 @@ Feature: Turn Lane Guidance
             | a,d       | road,road      | depart,arrive            | ,                           |
             | a,e       | road,turn,turn | depart,turn right,arrive | ,straight:false right:true, |
 
-    @bug @todo
+    @todo @roundabout
     Scenario: Theodor Heuss Platz
         Given the node map
             |   |   |   | i | o |   |   | l |   |
@@ -398,6 +464,7 @@ Feature: Turn Lane Guidance
             | i,l       | top,top-right-out,top-right-out | depart,roundabout-exit-4,arrive | ,slight left:true slight left;slight right:true slight right:false slight right:false, |
             | i,o       | top,top,top                     | depart,roundabout-exit-5,arrive | ,,                                                                                     |
 
+    @sliproads
     Scenario: Turn Lanes Breaking up
         Given the node map
             |   |   |   | g |   |
@@ -408,30 +475,34 @@ Feature: Turn Lane Guidance
             |   |   |   | f |   |
 
         And the ways
-            | nodes | name  | turn:lanes:forward           | oneway | highway   |
-            | ab    | road  | left\|left\|through\|through | yes    | primary   |
-            | bd    | road  | through\|through             | yes    | primary   |
-            | bc    | road  | left\|left                   | yes    | primary   |
-            | de    | road  |                              | yes    | primary   |
-            | fdcg  | cross |                              |        | secondary |
+            | nodes | name  | turn:lanes:forward                  | oneway | highway   |
+            | ab    | road  | left\|left\|through\|through\|right | yes    | primary   |
+            | bd    | road  | through\|through\|right             | yes    | primary   |
+            | bc    | road  | left\|left                          | yes    | primary   |
+            | de    | road  |                                     | yes    | primary   |
+            | fd    | cross |                                     |        | secondary |
+            |  dc   | cross |                                     |        | secondary |
+            |   cg  | cross |                                     |        | secondary |
 
         And the relations
             | type        | way:from | way:to | node:via | restriction   |
-            | restriction | bd       | fdcg   | d        | no_left_turn  |
-            | restriction | bc       | fdcg   | c        | no_right_turn |
+            | restriction | bd       | dc     | d        | no_left_turn  |
+            | restriction | bc       | dc     | c        | no_right_turn |
 
         When I route I should get
-            | waypoints | route            | turns                   | lanes                                               |
-            | a,g       | road,cross,cross | depart,turn left,arrive | ,left:true left:true straight:false straight:false, |
-            | a,e       | road,road        | depart,arrive           | ,                                                   |
+            | waypoints | route            | turns                   | lanes                                                           |
+            | a,g       | road,cross,cross | depart,turn left,arrive | ,left:true left:true straight:false straight:false right:false, |
+            | a,e       | road,road        | depart,arrive           | ,                                                               |
 
+    #NEEDS TO BE INVESTIGATED. Turn restriction shouldn't be here. See #2867
+    @reverse @previous-lanes
     Scenario: U-Turn Road at Intersection
         Given the node map
-            |   |   |   |   |   | h |   |
-            |   |   |   |   | f | e | j |
-            | a | b |   |   |   |   |   |
-            |   |   |   |   | c | d | i |
-            |   |   |   |   |   | g |   |
+            |   |   |   |   |   |   | h |   |
+            |   |   |   |   | f |   | e | j |
+            | a | b |   |   |   |   |   |   |
+            |   |   |   |   | c |   | d | i |
+            |   |   |   |   |   |   | g |   |
 
         And the ways
             | nodes | name  | turn:lanes:forward | oneway | highway  |
@@ -440,16 +511,25 @@ Feature: Turn Lane Guidance
             | bc    | road  | \|through\|right   | yes    | primary  |
             | cd    | road  | \|through\|right   | yes    | primary  |
             | fc    | road  |                    | no     | tertiary |
-            | jefb  | road  |                    | yes    | primary  |
-            | gdeh  | cross |                    | no     | primary  |
+            | je    | road  |                    | yes    | primary  |
+            | ef    | road  |                    | yes    | primary  |
+            | fb    | road  |                    | yes    | primary  |
+            | eh    | cross |                    | no     | primary  |
+            | de    | cross |                    | no     | primary  |
+            | gd    | cross |                    | no     | primary  |
 
-       When I route I should get
+        And the relations
+            | type        | way:from | way:to | node:via | restriction   |
+            | restriction | de       | ef     | e        | no_left_turn  |
+
+        When I route I should get
             | from | to | bearings        | route            | turns                        | lanes                                  |
             | a    | g  | 180,180 180,180 | road,cross,cross | depart,turn right,arrive     | ,none:false straight:false right:true, |
             | a    | h  | 180,180 180,180 | road,cross,cross | depart,turn left,arrive      | ,none:true straight:false right:false, |
             | a    | i  | 180,180 180,180 | road,road        | depart,arrive                | ,                                      |
             | b    | a  | 90,2 270,2      | road,road,road   | depart,continue uturn,arrive | ,none:true straight:false right:false, |
 
+    @reverse
     Scenario: Segregated Intersection Merges With Lanes
         Given the node map
             |   |   |   |   |   |   | f |
@@ -474,7 +554,7 @@ Feature: Turn Lane Guidance
             | a,e       | road,road,road         | depart,turn uturn,arrive        | ,left:true left:false left:false straight:false straight:false, |
             | a,g       | road,straight,straight | depart,new name straight,arrive | ,left:false left:false left:false straight:true straight:true,  |
 
-    @bug @todo
+    @todo @roundabout
     Scenario: Passing Through a Roundabout
         Given the node map
             |   |   | h |   | g |   |   |
@@ -501,6 +581,7 @@ Feature: Turn Lane Guidance
             | i,j       | left,bottom,bottom | depart,round-exit-1,arrive | ,0,   |
             | i,k       | left,right,right   | depart,round-exit-2,arrive | ,1,   |
 
+    @previous-lanes
     Scenario: Crossing Traffic Light
         Given the node map
             | a |   | b |   | c |   | d |
@@ -521,6 +602,7 @@ Feature: Turn Lane Guidance
             | a,d       | road,road        | depart,arrive                   | ,                                                                            |
             | a,e       | road,cross,cross | depart,turn slight right,arrive | ,straight:false straight:false straight;slight right:true slight right:true, |
 
+    @ramp
     Scenario: Highway Ramp
         Given the node map
             | a |   | b |   | c |   | d |
@@ -537,7 +619,7 @@ Feature: Turn Lane Guidance
             | a,d       | hwy,hwy       | depart,arrive                       | ,                                                                            |
             | a,e       | hwy,ramp,ramp | depart,off ramp slight right,arrive | ,straight:false straight:false straight;slight right:true slight right:true, |
 
-    @bug @todo
+    @todo
     Scenario: Turning Off Ramp
         Given the node map
             |   | a |   |
@@ -559,6 +641,7 @@ Feature: Turn Lane Guidance
             | a,g       | off,road,road | depart,turn_left,arrive  | ,left:true right:false, |
             | a,h       |               |                          |                         |
 
+    @ramp
     Scenario: Off Ramp In a Turn
         Given the node map
             | a |   |   |   |   |   |   |   |   |   |   |   |
@@ -577,6 +660,7 @@ Feature: Turn Lane Guidance
             | a,c       | hwy,hwy       | depart,arrive                       | ,                                                 |
             | a,d       | hwy,ramp,ramp | depart,off ramp slight right,arrive | ,straight:false straight:false slight right:true, |
 
+    @reverse
     Scenario: Reverse Lane in Segregated Road
         Given the node map
             | h |   |   |   |   | g |   |   |   |   |   | f |
@@ -595,6 +679,7 @@ Feature: Turn Lane Guidance
             | waypoints | route          | turns                        | lanes                                     |
             | a,h       | road,road,road | depart,continue uturn,arrive | ,uturn:true straight:false straight:false,|
 
+    @reverse
     Scenario: Reverse Lane in Segregated Road with none
         Given the node map
             | h |   |   |   |   | g |   |   |   |   |   | f |
@@ -613,6 +698,7 @@ Feature: Turn Lane Guidance
             | waypoints | route          | turns                        | lanes                                  |
             | a,h       | road,road,road | depart,continue uturn,arrive | ,uturn:true straight:false none:false, |
 
+    @reverse
     Scenario: Reverse Lane in Segregated Road with none, Service Turn Prior
         Given the node map
             | h |   |   |   |   | g |   |   |   |   |   | f |
@@ -633,6 +719,7 @@ Feature: Turn Lane Guidance
             | waypoints | route          | turns                        | lanes                                  |
             | a,h       | road,road,road | depart,continue uturn,arrive | ,uturn:true straight:false none:false, |
 
+    @simple
     Scenario: Don't collapse everything to u-turn / too wide
         Given the node map
             | a |   | b |   | e |
@@ -652,6 +739,7 @@ Feature: Turn Lane Guidance
             | a,d       | depart,continue right,turn right,arrive | road,road,road,road | ,straight:false right:true,, |
             | d,a       | depart,continue left,turn left,arrive   | road,road,road,road | ,left:true straight:false,,  |
 
+    @simple
     Scenario: Merge Lanes Onto Freeway
         Given the node map
             | a |   |   | b | c |
@@ -666,6 +754,7 @@ Feature: Turn Lane Guidance
             | waypoints | turns                           | route        | lanes                                 |
             | d,c       | depart,merge slight left,arrive | ramp,Hwy,Hwy | ,slight right:true slight right:true, |
 
+    @2654 @simple
     Scenario: Fork on motorway links - don't fork on through but use lane
         Given the node map
             | i |   |   |   |   | a |
@@ -680,9 +769,9 @@ Feature: Turn Lane Guidance
             | ab    | on   | motorway_link |                    |
 
         When I route I should get
-            | waypoints | route           | turns                                             | lanes                           |
-            | a,j       | on,xbcj,xbcj    | depart,merge slight left,arrive                   | ,,                              |
-            | a,i       | on,xbcj,off,off | depart,merge slight left,turn slight right,arrive | ,,none:false slight right:true, |
+            | waypoints | route        | turns                           | lanes                          |
+            | a,j       | on,xbcj      | depart,arrive                   | ,                              |
+            | a,i       | on,off,off   | depart,turn slight right,arrive | ,none:false slight right:true, |
 
     #http://www.openstreetmap.org/#map=17/52.47414/13.35712
     @todo @ramp @2645
@@ -735,6 +824,27 @@ Feature: Turn Lane Guidance
             | waypoints | route     | turns         | lanes |
             | x,d       | road,road | depart,arrive | ,     |
 
+    @partition-lanes
+    Scenario: Partitioned turn, Slight Curve
+        Given the node map
+            |   |   | f |   | e |
+            |   |   |   |   |   |
+            |   |   |   |   |   |
+            |   |   |   |   | c |
+            | a |   | b |   |   |
+            |   |   | g |   | d |
+
+        And the ways
+            | nodes | name  | highway | oneway | turn:lanes:forward |
+            | ab    | road  | primary | yes    | left\|right        |
+            | bc    | cross | primary | yes    |                    |
+            | fbg   | cross | primary | yes    |                    |
+            | dce   | cross | primary | yes    |                    |
+
+        When I route I should get
+            | waypoints | route            | turns                    | lanes                   |
+            | a,g       | road,cross,cross | depart,turn right,arrive | ,left:false right:true, |
+            | a,e       | road,cross,cross | depart,turn left,arrive  | ,left:true right:false, |
 
     Scenario: Lane Parsing Issue #2694
         Given the node map
@@ -753,7 +863,7 @@ Feature: Turn Lane Guidance
             | a,c       | ab,bc,bc  | depart,turn left,arrive | ,left:true right:false, |
 
     # http://www.openstreetmap.org/#map=19/47.97685/7.82933&layers=D
-    @bug @todo
+    @todo
     Scenario: Lane Parsing Issue #2706: None Assignments I
         Given the node map
             |   | f |   |   | j  |   |
@@ -793,7 +903,7 @@ Feature: Turn Lane Guidance
             # Note: at the moment we don't care about routes, we care about the extract process triggering assertions
 
     # https://www.openstreetmap.org/#map=19/47.99257/7.83276&layers=D
-    @bug @todo
+    @todo
     Scenario: Lane Parsing Issue #2706: None Assignments II
         Given the node map
             |   | k | l |   |
@@ -828,7 +938,7 @@ Feature: Turn Lane Guidance
             | i,e ||||
             # Note: at the moment we don't care about routes, we care about the extract process triggering assertions
 
-    @bug @todo
+    @todo
     Scenario: Lane Parsing Issue #2706: None Assignments III - Minimal reproduction recipe
         Given the node map
             |   |   | l |   |
@@ -847,7 +957,7 @@ Feature: Turn Lane Guidance
             | d,a ||||
             # Note: at the moment we don't care about routes, we care about the extract process triggering assertions
 
-    @reverse @2730
+    @reverse @2730 @todo
     Scenario: Reverse on the right
         Given the node map
             | a |   |   | c |   |
@@ -867,3 +977,31 @@ Feature: Turn Lane Guidance
             | a,c       | in,left,left       | depart,turn left,arrive         | ,left:true straight:false right;uturn:false, |
             | a,d       | in,through,through | depart,new name straight,arrive | ,left:false straight:true right;uturn:false, |
             | a,e       | in,right,right     | depart,turn right,arrive        | ,left:false straight:false right;uturn:true, |
+
+    @todo @2654
+    #https://github.com/Project-OSRM/osrm-backend/issues/2645
+    #http://www.openstreetmap.org/export#map=19/52.56054/13.32152
+    Scenario: Kurt-Schuhmacher-Damm
+        Given the node map
+            |   |   |   | g |   | f |
+            |   |   |   |   |   |   |
+            | j |   |   | h |   | e |
+            |   |   |   |   |   |   |
+            | a |   |   | b |   | c |
+            |   |   |   | i |   | d |
+
+        And the ways
+            | nodes | name | highway        | oneway | turn:lanes        |
+            | ab    |      | motorway_link  | yes    | left\|none\|right |
+            | bc    |      | primary_link   | yes    |                   |
+            | cd    | ksd  | secondary      | yes    |                   |
+            | cef   | ksd  | primary        | yes    |                   |
+            | hj    |      | motorway_link  | yes    |                   |
+            | eh    |      | secondary_link | yes    |                   |
+            | gh    | ksd  | primary        | yes    |                   |
+            | hbi   | ksd  | secondary      | yes    |                   |
+
+        When I route I should get
+            | waypoints | route    | turns                    | lanes                             |
+            | a,f       | ,ksd,ksd | depart,turn left,arrive  | ,left:true none:true right:false, |
+            | a,i       | ,ksd,ksd | depart,turn right,arrive | ,left:false none:true right:true, |
diff --git a/features/guidance/turn.feature b/features/guidance/turn.feature
index e61f850..6c3184b 100644
--- a/features/guidance/turn.feature
+++ b/features/guidance/turn.feature
@@ -246,22 +246,28 @@ Feature: Simple Turns
 
     Scenario: Four Way Intersection Double Through Street Segregated
         Given the node map
-            |   | b |   | c |   |
-            | i |   |   |   | d |
-            |   |   | a |   |   |
-            | h |   |   |   | e |
-            |   | g |   | f |   |
+            |   |   |   |   | q |   | p |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   | b |   | c |   |   |   |   |
+            | j |   |   | i |   |   |   | d |   |   | o |
+            |   |   |   |   |   | a |   |   |   |   |   |
+            | k |   |   | h |   |   |   | e |   |   | n |
+            |   |   |   |   | g |   | f |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   |   |   |   |   |   |   |   |
+            |   |   |   |   | l |   | m |   |   |   |   |
 
         And the ways
             | nodes  | highway | oneway | name   |
-            | ha     | primary | yes    | first  |
-            | ai     | primary | yes    | first  |
-            | ae     | primary | yes    | first  |
-            | da     | primary | yes    | first  |
-            | ba     | primary | yes    | second |
-            | ac     | primary | yes    | second |
-            | fa     | primary | yes    | second |
-            | ag     | primary | yes    | second |
+            | khaij  | primary | yes    | first  |
+            | odaen  | primary | yes    | first  |
+            | qbacp  | primary | yes    | second |
+            | mfagl  | primary | yes    | second |
 
        When I route I should get
             | waypoints | route                | turns                        |
@@ -879,3 +885,182 @@ Feature: Simple Turns
             | waypoints | turns                   | route                                                                                   |
             | a,d       | depart,arrive           | Channing Street Northeast,Channing Street Northwest                                     |
             | a,h       | depart,turn left,arrive | Channing Street Northeast,North Capitol Street Northeast,North Capitol Street Northeast |
+
+    Scenario: V St NW, Florida Ave NW: Turn Instruction
+    # https://www.mapillary.com/app/?focus=map&lat=38.91815595&lng=-77.03880249&z=17&pKey=sCxepTOCTZD3OoBXuqGEOw
+    # http://www.openstreetmap.org/way/6062557#map=19/38.91805/-77.03892
+        Given the node map
+            | y |   |   | x |   |   |
+            |   |   | c |   |   |   |
+            |   | d |   |   | b | a |
+            |   |   |   |   |   |   |
+            | e |   |   |   |   |   |
+
+        And the ways
+            | nodes | name                           | highway     | oneway |
+            | abc   | V St NW                        | tertiary    | yes    |
+            | xcde  | Florida Ave NW                 | tertiary    | yes    |
+            | yd    | Champlain St NW                | residential |        |
+
+        When I route I should get
+            | waypoints | turns                   | route                                 |
+            | a,e       | depart,turn left,arrive | V St NW,Florida Ave NW,Florida Ave NW |
+
+    # http://www.openstreetmap.org/node/182805179
+    Scenario: Make Sharp Left at Traffic Signal
+        Given the node map
+            |   |   |   | g |   |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   | f |   |   | y |
+            | i |   |   |   |   |   |   |
+            | j | k | a |   | b |   | x |
+            |   |   |   | e |   | c |   |
+            |   |   |   |   | d |   |   |
+            |   |   |   |   |   |   |   |
+            |   |   |   | h |   |   |   |
+
+        And the nodes
+            | node | highway         |
+            | f    | traffic_signals |
+
+        And the ways
+            | nodes | name                           | highway     | oneway |
+            | yf    | yf                             | trunk_link  | yes    |
+            | gfeh  | Centreville Road               | primary     |        |
+            | fi    | fi                             | trunk_link  | yes    |
+            | ij    | Bloomingdale Road              | residential |        |
+            | jkabx | Blue Star Memorial Hwy         | trunk       |        |
+            | bcde  | bcde                           | trunk_link  | yes    |
+            | kh    | kh                             | trunk_link  | yes    |
+
+        When I route I should get
+            | waypoints | turns                                        | route                                                         |
+            | a,h       | depart,off ramp right,turn sharp left,arrive | Blue Star Memorial Hwy,bcde,Centreville Road,Centreville Road |
+
+    @todo
+    # https://www.openstreetmap.org/#map=20/52.51609/13.41080
+    Scenario: Unnecessary Slight Left onto Stralauer Strasse
+        Given the node map
+            |   | e |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            | a |   | b |   | c |   | d |
+
+        And the ways
+            | nodes | name          | highway   | oneway |
+            | ab    | Molkenmarkt   | secondary | yes    |
+            | bc    | Stralauer Str | secondary | yes    |
+            | cd    | Stralauer Str | secondary | yes    |
+            | ec    | Molkenmarkt   | secondary | yes    |
+
+        When I route I should get
+            | waypoints | turns         | route                     |
+            | a,d       | depart,arrive | Molkenmarkt,Stralauer Str |
+            | e,d       | depart,arrive | Molkenmarkt,Stralauer Str |
+
+    Scenario: Unnecessary Slight Left onto Stralauer Strasse
+        Given the node map
+            |   | e |   |   |   |   |   |
+            |   |   |   |   |   |   |   |
+            | a |   | b |   | c |   | d |
+
+        And the ways
+            | nodes | name          | highway   | oneway |
+            | ab    | Molkenmarkt   | secondary | yes    |
+            | bc    | Molkenmarkt   | secondary | yes    |
+            | cd    | Stralauer Str | secondary | yes    |
+            | ec    | Molkenmarkt   | secondary | yes    |
+
+        When I route I should get
+            | waypoints | turns                              | route                                   |
+            | a,d       | depart,new name straight,arrive    | Molkenmarkt,Stralauer Str,Stralauer Str |
+            | e,d       | depart,new name slight left,arrive | Molkenmarkt,Stralauer Str,Stralauer Str |
+
+    # https://www.mapillary.com/app/?lat=52.466483333333336&lng=13.431908333333332&z=17&focus=photo&pKey=LWXnKqoGqUNLnG0lofiO0Q
+    # http://www.openstreetmap.org/#map=19/52.46750/13.43171
+    Scenario: Collapse Turn Instruction, Issue #2725
+        Given the node map
+            |   | f |   |
+            |   | e |   |
+            |   |   |   |
+            |   |   |   |
+            | g |   | d |
+            |   |   |   |
+            |   |   |   |
+            | h |   | c |
+            |   |   |   |
+            |   |   |   |
+            |   | b |   |
+            |   | a |   |
+            |   |   |   |
+            |   |   |   |
+            | r | x | s |
+            |   | y |   |
+
+        And the ways
+            | nodes | name           | highway   | oneway |
+            | ab    | Hermannstr     | secondary |        |
+            | bc    | Hermannstr     | secondary | yes    |
+            | cd    | Hermannbruecke | secondary | yes    |
+            | de    | Hermannstr     | secondary | yes    |
+            | ef    | Hermannstr     | secondary |        |
+            | eg    | Hermannstr     | secondary | yes    |
+            | gh    | Hermannbruecke | secondary | yes    |
+            | hb    | Hermannstr     | secondary | yes    |
+            | xa    | Hermannstr     | secondary |        |
+            | yx    | Hermannstr     | secondary |        |
+            | rxs   | Silbersteinstr | tertiary  |        |
+
+        And the nodes
+            | node | highway         |
+            | x    | traffic_signals |
+
+        When I route I should get
+            | waypoints | turns         | route                 |
+            | a,f       | depart,arrive | Hermannstr,Hermannstr |
+            | f,a       | depart,arrive | Hermannstr,Hermannstr |
+            | y,f       | depart,arrive | Hermannstr,Hermannstr |
+            | f,y       | depart,arrive | Hermannstr,Hermannstr |
+
+    Scenario: Collapse Turn Instruction, Issue #2725 - not trivially mergable at e
+    # https://www.mapillary.com/app/?lat=52.466483333333336&lng=13.431908333333332&z=17&focus=photo&pKey=LWXnKqoGqUNLnG0lofiO0Q
+    # http://www.openstreetmap.org/#map=19/52.46750/13.43171
+        Given the node map
+            |   | f |   |
+            |   | e |   |
+            | g |   | d |
+            |   |   |   |
+            |   |   |   |
+            | h |   | c |
+            |   |   |   |
+            |   |   |   |
+            |   | b |   |
+            |   | a |   |
+            |   |   |   |
+            |   |   |   |
+            | r | x | s |
+            |   | y |   |
+
+        And the ways
+            | nodes | name           | highway   | oneway |
+            | ab    | Hermannstr     | secondary |        |
+            | bc    | Hermannstr     | secondary | yes    |
+            | cd    | Hermannbruecke | secondary | yes    |
+            | de    | Hermannstr     | secondary | yes    |
+            | ef    | Hermannstr     | secondary |        |
+            | eg    | Hermannstr     | secondary | yes    |
+            | gh    | Hermannbruecke | secondary | yes    |
+            | hb    | Hermannstr     | secondary | yes    |
+            | xa    | Hermannstr     | secondary |        |
+            | yx    | Hermannstr     | secondary |        |
+            | rxs   | Silbersteinstr | tertiary  |        |
+
+        And the nodes
+            | node | highway         |
+            | x    | traffic_signals |
+
+        When I route I should get
+            | waypoints | turns         | route                 |
+            | a,f       | depart,arrive | Hermannstr,Hermannstr |
+            | f,a       | depart,arrive | Hermannstr,Hermannstr |
+            | y,f       | depart,arrive | Hermannstr,Hermannstr |
+            | f,y       | depart,arrive | Hermannstr,Hermannstr |
diff --git a/features/lib/hash.js b/features/lib/hash.js
new file mode 100644
index 0000000..57563e3
--- /dev/null
+++ b/features/lib/hash.js
@@ -0,0 +1,31 @@
+'use strict';
+
+const fs = require('fs');
+const crypto = require('crypto');
+const d3 = require('d3-queue');
+
+module.exports =  {
+    hashOfFiles: (paths, cb) => {
+        let queue = d3.queue();
+        for (let i = 0; i < paths.length; ++i) {
+            queue.defer(fs.readFile, paths[i]);
+        }
+        queue.awaitAll((err, results) => {
+            if (err) return cb(err);
+            let checksum = crypto.createHash('md5');
+            for (let i = 0; i < results.length; ++i) {
+                checksum.update(results[i]);
+            }
+            cb(null, checksum.digest('hex'));
+        });
+    },
+
+     hashOfFile: (path, cb) => {
+        fs.readFile(path, (err, result) => {
+            if (err) return cb(err);
+            let checksum = crypto.createHash('md5');
+            checksum.update(result);
+            cb(null, checksum.digest('hex'));
+        });
+    }
+};
diff --git a/features/support/build_osm.js b/features/lib/osm.js
similarity index 96%
rename from features/support/build_osm.js
rename to features/lib/osm.js
index 7fe6874..5387b7f 100644
--- a/features/support/build_osm.js
+++ b/features/lib/osm.js
@@ -1,11 +1,7 @@
 'use strict';
 
-var builder = require('xmlbuilder');
-
-var ensureDecimal = (i) => {
-    if (parseInt(i) === i) return i.toFixed(1);
-    else return i;
-};
+const builder = require('xmlbuilder');
+const ensureDecimal = require('./utils').ensureDecimal;
 
 class DB {
     constructor () {
diff --git a/features/lib/osrm_loader.js b/features/lib/osrm_loader.js
new file mode 100644
index 0000000..28ec7ed
--- /dev/null
+++ b/features/lib/osrm_loader.js
@@ -0,0 +1,169 @@
+'use strict';
+
+const fs = require('fs');
+const util = require('util');
+const Timeout = require('node-timeout');
+const tryConnect = require('../lib/try_connect');
+const errorReason = require('./utils').errorReason;
+
+class OSRMBaseLoader{
+    constructor (scope) {
+        this.scope = scope;
+        this.child = null;
+    }
+
+    launch (callback) {
+        var limit = Timeout(this.scope.TIMEOUT, { err: new Error('*** Launching osrm-routed timed out.') });
+
+        var runLaunch = (cb) => {
+            this.osrmUp(() => { this.waitForConnection(cb); });
+        };
+
+        runLaunch(limit((e) => { if (e) callback(e); else callback(); }));
+    }
+
+    shutdown (callback) {
+        if (!this.osrmIsRunning()) return callback();
+
+        var limit = Timeout(this.scope.TIMEOUT, { err: new Error('*** Shutting down osrm-routed timed out.')});
+
+        this.osrmDown(limit(callback));
+    }
+
+    osrmIsRunning () {
+        return this.child && !this.child.killed;
+    }
+
+    osrmDown (callback) {
+        if (this.osrmIsRunning()) {
+            this.child.on('exit', (code, signal) => {callback();});
+            this.child.kill();
+        } else callback();
+    }
+
+    waitForConnection (callback) {
+        var retryCount = 0;
+        let retry = (err) => {
+          if (err) {
+            if (retryCount < 10) {
+              retryCount++;
+              setTimeout(() => { tryConnect(this.scope.OSRM_PORT, retry); }, 10);
+            } else {
+              callback(new Error("Could not connect to osrm-routed after ten retries."));
+            }
+          }
+          else
+          {
+            callback();
+          }
+        };
+
+        tryConnect(this.scope.OSRM_PORT, retry);
+    }
+};
+
+class OSRMDirectLoader extends OSRMBaseLoader {
+    constructor (scope) {
+        super(scope);
+    }
+
+    load (inputFile, callback) {
+        this.inputFile = inputFile;
+        this.shutdown(() => {
+            this.launch(callback);
+        });
+    }
+
+    osrmUp (callback) {
+        if (this.osrmIsRunning()) return callback(new Error("osrm-routed already running!"));
+
+        this.child = this.scope.runBin('osrm-routed', util.format("%s -p %d", this.inputFile, this.scope.OSRM_PORT), this.scope.environment, (err) => {
+          if (err) {
+              throw new Error(util.format('osrm-routed %s: %s', errorReason(err), err.cmd));
+          }
+        });
+        callback();
+    }
+};
+
+class OSRMDatastoreLoader extends OSRMBaseLoader {
+    constructor (scope) {
+        super(scope);
+    }
+
+    load (inputFile, callback) {
+        this.inputFile = inputFile;
+
+        this.loadData((err) => {
+            if (err) return callback(err);
+            if (!this.osrmIsRunning()) this.launch(callback);
+            else {
+                this.scope.setupOutputLog(this.child, fs.createWriteStream(this.scope.scenarioLogFile, {'flags': 'a'}));
+                callback();
+            }
+        });
+    }
+
+    loadData (callback) {
+        this.scope.runBin('osrm-datastore', this.inputFile, this.scope.environment, (err) => {
+            if (err) return callback(new Error('*** osrm-datastore exited with ' + err.code + ': ' + err));
+            callback();
+        });
+    }
+
+    osrmUp (callback) {
+        if (this.osrmIsRunning()) return callback();
+
+        this.child = this.scope.runBin('osrm-routed', util.format('--shared-memory=1 -p %d', this.scope.OSRM_PORT), this.scope.environment, (err) => {
+            if (err) {
+                throw new Error(util.format('osrm-routed %s: %s', errorReason(err), err.cmd));
+            }
+        });
+
+        // we call the callback here, becuase we don't want to wait for the child process to finish
+        callback();
+    }
+};
+
+class OSRMLoader {
+    constructor (scope) {
+        this.scope = scope;
+        this.sharedLoader = new OSRMDatastoreLoader(this.scope);
+        this.directLoader = new OSRMDirectLoader(this.scope);
+        this.method = scope.DEFAULT_LOAD_METHOD;
+    }
+
+    load (inputFile, callback) {
+        if (this.method === 'datastore') {
+            this.directLoader.shutdown((err) => {
+              if (err) return callback(err);
+              this.loader = this.sharedLoader;
+              this.sharedLoader.load(inputFile, callback);
+            });
+        } else if (this.method === 'directly') {
+            this.sharedLoader.shutdown((err) => {
+              if (err) return callback(err);
+              this.loader = this.directLoader;
+              this.directLoader.load(inputFile, callback);
+            });
+        } else {
+            callback(new Error('*** Unknown load method ' + method));
+        }
+    }
+
+    setLoadMethod (method) {
+        this.method = method;
+    }
+
+    shutdown (callback) {
+        if (!this.loader) return callback();
+
+        this.loader.shutdown(callback);
+    }
+
+    up () {
+        return this.loader ? this.loader.osrmIsRunning() : false;
+    }
+};
+
+module.exports = OSRMLoader;
diff --git a/features/lib/table_diff.js b/features/lib/table_diff.js
new file mode 100644
index 0000000..4acbd23
--- /dev/null
+++ b/features/lib/table_diff.js
@@ -0,0 +1,54 @@
+'use strict';
+
+var util = require('util');
+var path = require('path');
+var fs = require('fs');
+var chalk = require('chalk');
+
+var unescapeStr = (str) => str.replace(/\\\|/g, '\|').replace(/\\\\/g, '\\');
+
+module.exports = function (expected, actual) {
+    let headers = expected.raw()[0];
+    let expected_keys = expected.hashes();
+    let diff = [];
+    let hasErrors = false;
+
+    var good = 0, bad = 0;
+
+    expected_keys.forEach((row, i) => {
+        var rowError = false;
+
+        for (var j in row) {
+            if (unescapeStr(row[j]) != actual[i][j]) {
+                rowError = true;
+                hasErrors = true;
+                break;
+            }
+        }
+
+        if (rowError) {
+            bad++;
+            diff.push(Object.assign({}, row, {c_status: 'undefined'}));
+            diff.push(Object.assign({}, actual[i], {c_status: 'comment'}));
+        } else {
+            good++;
+            diff.push(row);
+        }
+    });
+
+    if (!hasErrors) return null;
+
+    var s = ['Tables were not identical:'];
+    s.push(headers.map(key => '    ' + key).join(' | '));
+    diff.forEach((row) => {
+        var rowString = '| ';
+        headers.forEach((header) => {
+            if (!row.c_status) rowString += chalk.green('    ' + row[header] + ' | ');
+            else if (row.c_status === 'undefined') rowString += chalk.yellow('(-) ' + row[header] + ' | ');
+            else rowString += chalk.red('(+) ' + row[header] + ' | ');
+        });
+        s.push(rowString);
+    });
+
+    return s.join('\n') + '\nTODO this is a temp workaround waiting for https://github.com/cucumber/cucumber-js/issues/534';
+};
diff --git a/features/lib/try_connect.js b/features/lib/try_connect.js
new file mode 100644
index 0000000..0461ddd
--- /dev/null
+++ b/features/lib/try_connect.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const net = require('net');
+const Timeout = require('node-timeout');
+
+module.exports = function tryConnect(port, callback) {
+  net.connect({ port: port, host: '127.0.0.1' })
+    .on('connect', () => { callback(); })
+    .on('error', () => {
+        callback(new Error('Could not connect.'));
+    });
+}
+
diff --git a/features/lib/utils.js b/features/lib/utils.js
new file mode 100644
index 0000000..27b63af
--- /dev/null
+++ b/features/lib/utils.js
@@ -0,0 +1,17 @@
+'use strict';
+
+const util = require('util');
+
+module.exports = {
+
+    ensureDecimal: (i) => {
+        if (parseInt(i) === i) return i.toFixed(1);
+        else return i;
+    },
+
+    errorReason: (err) => {
+        return err.signal ?
+            util.format('killed by signal %s', err.signal) :
+            util.format('exited with code %d', err.code);
+    }
+};
diff --git a/features/options/contract/datasources.feature b/features/options/contract/datasources.feature
index bf8eed5..1991388 100644
--- a/features/options/contract/datasources.feature
+++ b/features/options/contract/datasources.feature
@@ -1,8 +1,7 @@
 @prepare @options @files
 Feature: osrm-contract command line options: datasources
 # expansions:
-# {extracted_base} => path to current extracted input file
-# {profile} => path to current profile script
+# {processed_file} => path to .osrm file
 
     Background:
         Given the profile "testbot"
@@ -24,7 +23,6 @@ Feature: osrm-contract command line options: datasources
         And the data has been extracted
 
     Scenario: osrm-contract - Passing base file
-        When I run "osrm-contract --segment-speed-file speeds.csv {extracted_base}.osrm"
-        Then stderr should be empty
-        And datasource names should contain "lua profile,speeds"
-        And it should exit with code 0
+        When I run "osrm-contract --segment-speed-file {speeds_file} {processed_file}"
+        Then datasource names should contain "lua profile,25_osrmcontract_passing_base_file_speeds"
+        And it should exit successfully
diff --git a/features/options/contract/files.feature b/features/options/contract/files.feature
index 5e50e14..46cf4fb 100644
--- a/features/options/contract/files.feature
+++ b/features/options/contract/files.feature
@@ -1,9 +1,5 @@
 @prepare @options @files
 Feature: osrm-contract command line options: files
-# expansions:
-# {extracted_base} => path to current extracted input file
-# {profile} => path to current profile script
-
     Background:
         Given the profile "testbot"
         And the node map
@@ -14,12 +10,11 @@ Feature: osrm-contract command line options: files
         And the data has been extracted
 
     Scenario: osrm-contract - Passing base file
-        When I run "osrm-contract {extracted_base}.osrm"
-        Then stderr should be empty
-        And it should exit with code 0
+        When I run "osrm-contract {processed_file}"
+        Then it should exit successfully
 
     Scenario: osrm-contract - Missing input file
-        When I run "osrm-contract over-the-rainbow.osrm"
+        When I try to run "osrm-contract over-the-rainbow.osrm"
         And stderr should contain "over-the-rainbow.osrm"
         And stderr should contain "not found"
-        And it should exit with code 1
+        And it should exit with an error
diff --git a/features/options/contract/help.feature b/features/options/contract/help.feature
index 411bc55..b4d81c5 100644
--- a/features/options/contract/help.feature
+++ b/features/options/contract/help.feature
@@ -2,7 +2,7 @@
 Feature: osrm-contract command line options: help
 
     Scenario: osrm-contract - Help should be shown when no options are passed
-        When I run "osrm-contract"
+        When I try to run "osrm-contract"
         Then stderr should be empty
         And stdout should contain "osrm-contract <input.osrm> [options]:"
         And stdout should contain "Options:"
@@ -13,7 +13,7 @@ Feature: osrm-contract command line options: help
         And stdout should contain "--core"
         And stdout should contain "--level-cache"
         And stdout should contain "--segment-speed-file"
-        And it should exit with code 1
+        And it should exit with an error
 
     Scenario: osrm-contract - Help, short
         When I run "osrm-contract -h"
@@ -27,7 +27,7 @@ Feature: osrm-contract command line options: help
         And stdout should contain "--core"
         And stdout should contain "--level-cache"
         And stdout should contain "--segment-speed-file"
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-contract - Help, long
         When I run "osrm-contract --help"
@@ -41,4 +41,4 @@ Feature: osrm-contract command line options: help
         And stdout should contain "--core"
         And stdout should contain "--level-cache"
         And stdout should contain "--segment-speed-file"
-        And it should exit with code 0
+        And it should exit successfully
diff --git a/features/options/contract/invalid.feature b/features/options/contract/invalid.feature
index 38ee3ac..127761e 100644
--- a/features/options/contract/invalid.feature
+++ b/features/options/contract/invalid.feature
@@ -5,8 +5,8 @@ Feature: osrm-contract command line options: invalid options
         Given the profile "testbot"
 
     Scenario: osrm-contract - Non-existing option
-        When I run "osrm-contract --fly-me-to-the-moon"
+        When I try to run "osrm-contract --fly-me-to-the-moon"
         Then stdout should be empty
         And stderr should contain "option"
         And stderr should contain "fly-me-to-the-moon"
-        And it should exit with code 1
+        And it should exit with an error
diff --git a/features/options/contract/version.feature b/features/options/contract/version.feature
index be99bbe..f361bb1 100644
--- a/features/options/contract/version.feature
+++ b/features/options/contract/version.feature
@@ -12,11 +12,11 @@ Feature: osrm-contract command line options: version
         Then stderr should be empty
         And stdout should contain 1 line
         And stdout should contain /(v\d{1,2}\.\d{1,2}\.\d{1,2}|\w*-\d+-\w+)/
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-contract - Version, long
         When I run "osrm-contract --version"
         Then stderr should be empty
         And stdout should contain 1 line
         And stdout should contain /(v\d{1,2}\.\d{1,2}\.\d{1,2}|\w*-\d+-\w+)/
-        And it should exit with code 0
+        And it should exit successfully
diff --git a/features/options/extract/files.feature b/features/options/extract/files.feature
index aceab19..c4e14a2 100644
--- a/features/options/extract/files.feature
+++ b/features/options/extract/files.feature
@@ -14,17 +14,15 @@ Feature: osrm-extract command line options: files
         And the data has been saved to disk
 
     Scenario: osrm-extract - Passing base file
-        When I run "osrm-extract {osm_base}.osm --profile {profile}"
-        Then stderr should be empty
-        And it should exit with code 0
+        When I run "osrm-extract {osm_file} --profile {profile_file}"
+        Then it should exit successfully
 
     Scenario: osrm-extract - Order of options should not matter
-        When I run "osrm-extract --profile {profile} {osm_base}.osm"
-        Then stderr should be empty
-        And it should exit with code 0
+        When I run "osrm-extract --profile {profile_file} {osm_file}"
+        Then it should exit successfully
 
     Scenario: osrm-extract - Missing input file
-        When I run "osrm-extract over-the-rainbow.osrm --profile {profile}"
+        When I try to run "osrm-extract over-the-rainbow.osrm --profile {profile_file}"
         And stderr should contain "over-the-rainbow.osrm"
         And stderr should contain "not found"
-        And it should exit with code 1
+        And it should exit with an error
diff --git a/features/options/extract/help.feature b/features/options/extract/help.feature
index cdf1eb9..0d400ed 100644
--- a/features/options/extract/help.feature
+++ b/features/options/extract/help.feature
@@ -16,7 +16,7 @@ Feature: osrm-extract command line options: help
         And stdout should contain "--threads"
         And stdout should contain "--generate-edge-lookup"
         And stdout should contain "--small-component-size"
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-extract - Help, short
         When I run "osrm-extract -h"
@@ -30,7 +30,7 @@ Feature: osrm-extract command line options: help
         And stdout should contain "--threads"
         And stdout should contain "--generate-edge-lookup"
         And stdout should contain "--small-component-size"
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-extract - Help, long
         When I run "osrm-extract --help"
@@ -44,4 +44,4 @@ Feature: osrm-extract command line options: help
         And stdout should contain "--threads"
         And stdout should contain "--generate-edge-lookup"
         And stdout should contain "--small-component-size"
-        And it should exit with code 0
+        And it should exit successfully
diff --git a/features/options/extract/invalid.feature b/features/options/extract/invalid.feature
index 169e53c..936f456 100644
--- a/features/options/extract/invalid.feature
+++ b/features/options/extract/invalid.feature
@@ -5,8 +5,8 @@ Feature: osrm-extract command line options: invalid options
         Given the profile "testbot"
 
     Scenario: osrm-extract - Non-existing option
-        When I run "osrm-extract --fly-me-to-the-moon"
+        When I try to run "osrm-extract --fly-me-to-the-moon"
         Then stdout should be empty
         And stderr should contain "option"
         And stderr should contain "fly-me-to-the-moon"
-        And it should exit with code 1
+        And it should exit with an error
diff --git a/features/options/extract/version.feature b/features/options/extract/version.feature
index 0dd5f65..77ee46c 100644
--- a/features/options/extract/version.feature
+++ b/features/options/extract/version.feature
@@ -12,11 +12,11 @@ Feature: osrm-extract command line options: version
         Then stderr should be empty
         And stdout should contain 1 line
         And stdout should contain /(v\d{1,2}\.\d{1,2}\.\d{1,2}|\w*-\d+-\w+)/
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-extract - Version, long
         When I run "osrm-extract --version"
         Then stderr should be empty
         And stdout should contain 1 line
         And stdout should contain /(v\d{1,2}\.\d{1,2}\.\d{1,2}|\w*-\d+-\w+)/
-        And it should exit with code 0
+        And it should exit successfully
diff --git a/features/options/routed/files.feature b/features/options/routed/files.feature
index 59ce7c2..b28c8b1 100644
--- a/features/options/routed/files.feature
+++ b/features/options/routed/files.feature
@@ -29,4 +29,4 @@ Feature: osrm-routed command line options: files
         And stdout should contain /^\[info\] loaded plugin: viaroute/
         And stdout should contain /^\[info\] trial run/
         And stdout should contain /^\[info\] shutdown completed/
-        And it should exit with code 0
+        And it should exit successfully
diff --git a/features/options/routed/help.feature b/features/options/routed/help.feature
index 8f64bd9..e8c6430 100644
--- a/features/options/routed/help.feature
+++ b/features/options/routed/help.feature
@@ -21,7 +21,7 @@ Feature: osrm-routed command line options: help
         And stdout should contain "--max-trip-size"
         And stdout should contain "--max-table-size"
         And stdout should contain "--max-matching-size"
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-routed - Help, short
         When I run "osrm-routed -h"
@@ -40,7 +40,7 @@ Feature: osrm-routed command line options: help
         And stdout should contain "--max-trip-size"
         And stdout should contain "--max-table-size"
         And stdout should contain "--max-matching-size"
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-routed - Help, long
         When I run "osrm-routed --help"
@@ -59,4 +59,4 @@ Feature: osrm-routed command line options: help
         And stdout should contain "--max-table-size"
         And stdout should contain "--max-table-size"
         And stdout should contain "--max-matching-size"
-        And it should exit with code 0
+        And it should exit successfully
diff --git a/features/options/routed/invalid.feature b/features/options/routed/invalid.feature
index f3d90ea..78d28a0 100644
--- a/features/options/routed/invalid.feature
+++ b/features/options/routed/invalid.feature
@@ -5,14 +5,14 @@ Feature: osrm-routed command line options: invalid options
         Given the profile "testbot"
 
     Scenario: osrm-routed - Non-existing option
-        When I run "osrm-routed --fly-me-to-the-moon"
+        When I try to run "osrm-routed --fly-me-to-the-moon"
         Then stdout should be empty
-        And stderr should contain "exception"
+        And stderr should contain "unrecognised"
         And stderr should contain "fly-me-to-the-moon"
-        And it should exit with code 1
+        And it should exit with an error
 
     Scenario: osrm-routed - Missing file
-        When I run "osrm-routed over-the-rainbow.osrm"
+        When I try to run "osrm-routed over-the-rainbow.osrm"
         Then stderr should contain "over-the-rainbow.osrm"
         And stderr should contain "not found"
-        And it should exit with code 1
+        And it should exit with an error
diff --git a/features/options/routed/version.feature b/features/options/routed/version.feature
index b544e36..0a3cad5 100644
--- a/features/options/routed/version.feature
+++ b/features/options/routed/version.feature
@@ -12,11 +12,11 @@ Feature: osrm-routed command line options: version
         Then stderr should be empty
         And stdout should contain 1 line
         And stdout should contain /(v\d{1,2}\.\d{1,2}\.\d{1,2}|\w*-\d+-\w+)/
-        And it should exit with code 0
+        And it should exit successfully
 
     Scenario: osrm-routed - Version, long
         When I run "osrm-routed --version"
         Then stderr should be empty
         And stdout should contain 1 line
         And stdout should contain /(v\d{1,2}\.\d{1,2}\.\d{1,2}|\w*-\d+-\w+)/
-        And it should exit with code 0
+        And it should exit successfully
diff --git a/features/raster/extract.feature b/features/raster/extract.feature
index 9ca0635..86a7537 100644
--- a/features/raster/extract.feature
+++ b/features/raster/extract.feature
@@ -1,9 +1,5 @@
 @raster @extract
 Feature: osrm-extract with a profile containing raster source
-# expansions:
-# {osm_base} => path to current input file
-# {profile} => path to current profile script
-
     Scenario: osrm-extract on a valid profile
         Given the profile "rasterbot"
         And the node map
@@ -11,8 +7,15 @@ Feature: osrm-extract with a profile containing raster source
         And the ways
             | nodes |
             | ab    |
+        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 run "osrm-extract {osm_base}.osm -p {profile}"
-        Then stderr should be empty
-        And stdout should contain "source loader"
-        And it should exit with code 0
+        When I run "osrm-extract {osm_file} -p {profile_file}"
+        Then stdout should contain "source loader"
+        And it should exit successfully
diff --git a/features/raster/weights.feature b/features/raster/weights.feature
index ae782a7..1c03bdc 100644
--- a/features/raster/weights.feature
+++ b/features/raster/weights.feature
@@ -32,8 +32,8 @@ Feature: Raster - weights
 
     Scenario: Weighting not based on raster sources
         Given the profile "testbot"
-        When I run "osrm-extract {osm_base}.osm -p {profile}"
-        And I run "osrm-contract {osm_base}.osm"
+        When I run "osrm-extract {osm_file} -p {profile_file}"
+        And I run "osrm-contract {processed_file}"
         And I route I should get
             | from | to | route    | speed   |
             | a    | b  | ab,ab    | 36 km/h |
@@ -44,9 +44,9 @@ Feature: Raster - weights
 
     Scenario: Weighting based on raster sources
         Given the profile "rasterbot"
-        When I run "osrm-extract {osm_base}.osm -p {profile}"
+        When I run "osrm-extract {osm_file} -p {profile_file}"
         Then stdout should contain "evaluating segment"
-        And I run "osrm-contract {osm_base}.osm"
+        And I run "osrm-contract {processed_file}"
         And I route I should get
             | from | to | route    | speed   |
             | a    | b  | ab,ab    | 8 km/h  |
@@ -62,9 +62,9 @@ Feature: Raster - weights
 
     Scenario: Weighting based on raster sources
         Given the profile "rasterbotinterp"
-        When I run "osrm-extract {osm_base}.osm -p {profile}"
+        When I run "osrm-extract {osm_file} -p {profile_file}"
         Then stdout should contain "evaluating segment"
-        And I run "osrm-contract {osm_base}.osm"
+        And I run "osrm-contract {processed_file}"
         And I route I should get
             | from | to | route    | speed   |
             | a    | b  | ab,ab    | 8 km/h  |
diff --git a/features/step_definitions/data.js b/features/step_definitions/data.js
index 58a3fc5..dbb3882 100644
--- a/features/step_definitions/data.js
+++ b/features/step_definitions/data.js
@@ -2,19 +2,23 @@ var util = require('util');
 var path = require('path');
 var fs = require('fs');
 var d3 = require('d3-queue');
-var OSM = require('../support/build_osm');
+var OSM = require('../lib/osm');
 
 module.exports = function () {
     this.Given(/^the profile "([^"]*)"$/, (profile, callback) => {
-        this.setProfile(profile, callback);
+        this.profile = profile;
+        this.profileFile = path.join(this.PROFILES_PATH, this.profile + '.lua');
+        callback();
     });
 
     this.Given(/^the extract extra arguments "(.*?)"$/, (args, callback) => {
-        this.setExtractArgs(args, callback);
+        this.extractArgs = this.expandOptions(args);
+        callback();
     });
 
     this.Given(/^the contract extra arguments "(.*?)"$/, (args, callback) => {
-        this.setContractArgs(args, callback);
+        this.contractArgs = this.expandOptions(args);
+        callback();
     });
 
     this.Given(/^a grid size of ([0-9.]+) meters$/, (meters, callback) => {
@@ -228,58 +232,46 @@ module.exports = function () {
     });
 
     this.Given(/^the raster source$/, (data, callback) => {
-        this.updateFingerprintExtract(data);
-        fs.writeFile(path.resolve(this.TEST_FOLDER, 'rastersource.asc'), data, callback);
+        // TODO: Don't overwrite if it exists
+        fs.writeFile(this.rasterCacheFile, data, callback);
+        // we need this to pass it to the profiles
+        this.environment = Object.assign({OSRM_RASTER_SOURCE: this.rasterCacheFile}, this.environment);
     });
 
     this.Given(/^the speed file$/, (data, callback) => {
-        this.updateFingerprintContract(data);
-        fs.writeFile(path.resolve(this.TEST_FOLDER, 'speeds.csv'), data, callback);
+        // TODO: Don't overwrite if it exists
+        fs.writeFile(this.speedsCacheFile, data, callback);
     });
 
     this.Given(/^the turn penalty file$/, (data, callback) => {
-        this.updateFingerprintContract(data);
-        fs.writeFile(path.resolve(this.TEST_FOLDER, 'penalties.csv'), data, callback);
+        // TODO: Don't overwrite if it exists
+        fs.writeFile(this.penaltiesCacheFile, data, callback);
     });
 
     this.Given(/^the data has been saved to disk$/, (callback) => {
-        try {
-            this.reprocess(callback);
-        } catch(e) {
-            this.processError = e;
-            callback(e);
-        }
+        this.reprocess(callback);
     });
 
     this.Given(/^the data has been extracted$/, (callback) => {
-        this.osmData.populate(() => {
-            this.writeAndExtract((err) => {
-                if (err) this.processError = err;
-                callback();
-            });
-        });
+        this.reprocess(callback);
     });
 
     this.Given(/^the data has been contracted$/, (callback) => {
-        this.reprocess((err) => {
-            if (err) this.processError = err;
-            callback();
-        });
+        this.reprocess(callback);
     });
 
     this.Given(/^osrm\-routed is stopped$/, (callback) => {
-        this.OSRMLoader.shutdown((err) => {
-            if (err) this.processError = err;
-            callback();
-        });
+        this.OSRMLoader.shutdown(callback);
     });
 
-    this.Given(/^data is loaded directly/, () => {
-        this.loadMethod = 'directly';
+    this.Given(/^data is loaded directly/, (callback) => {
+        this.osrmLoader.setLoadMethod('directly');
+        callback();
     });
 
-    this.Given(/^data is loaded with datastore$/, () => {
-        this.loadMethod = 'datastore';
+    this.Given(/^data is loaded with datastore$/, (callback) => {
+        this.osrmLoader.setLoadMethod('datastore');
+        callback();
     });
 
     this.Given(/^the HTTP method "([^"]*)"$/, (method, callback) => {
diff --git a/features/step_definitions/distance_matrix.js b/features/step_definitions/distance_matrix.js
index 735847d..9c2bc0a 100644
--- a/features/step_definitions/distance_matrix.js
+++ b/features/step_definitions/distance_matrix.js
@@ -35,7 +35,8 @@ module.exports = function () {
         var actual = [];
         actual.push(table.headers);
 
-        this.reprocessAndLoadData(() => {
+        this.reprocessAndLoadData((e) => {
+            if (e) return callback(e);
             // compute matrix
             var params = this.queryParams;
 
@@ -52,8 +53,6 @@ module.exports = function () {
                 });
 
                 var testRow = (row, ri, cb) => {
-                    var ok = true;
-
                     for (var k in result[ri]) {
                         if (this.FuzzyMatch.match(result[ri][k], row[k])) {
                             result[ri][k] = row[k];
@@ -61,15 +60,9 @@ module.exports = function () {
                             result[ri][k] = '';
                         } else {
                             result[ri][k] = result[ri][k].toString();
-                            ok = false;
                         }
                     }
 
-                    if (!ok) {
-                        var failed = { attempt: 'distance_matrix', query: this.query, response: response };
-                        this.logFail(row, result[ri], [failed]);
-                    }
-
                     result[ri][''] = row[''];
                     cb(null, result[ri]);
                 };
diff --git a/features/step_definitions/hooks.js b/features/step_definitions/hooks.js
deleted file mode 100644
index d6ed251..0000000
--- a/features/step_definitions/hooks.js
+++ /dev/null
@@ -1,18 +0,0 @@
-var util = require('util');
-
-module.exports = function () {
-    this.Before((scenario, callback) => {
-        this.scenarioTitle = scenario.getName();
-
-        this.loadMethod = this.DEFAULT_LOAD_METHOD;
-        this.queryParams = {};
-        var d = new Date();
-        this.scenarioTime = util.format('%d-%d-%dT%s:%s:%sZ', d.getFullYear(), d.getMonth()+1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds());
-        this.resetData();
-        this.hasLoggedPreprocessInfo = false;
-        this.hasLoggedScenarioInfo = false;
-        this.setGridSize(this.DEFAULT_GRID_SIZE);
-        this.setOrigin(this.DEFAULT_ORIGIN);
-        callback();
-    });
-};
diff --git a/features/step_definitions/matching.js b/features/step_definitions/matching.js
index 0d85f09..41cd0ea 100644
--- a/features/step_definitions/matching.js
+++ b/features/step_definitions/matching.js
@@ -6,7 +6,8 @@ module.exports = function () {
     this.When(/^I match I should get$/, (table, callback) => {
         var got;
 
-        this.reprocessAndLoadData(() => {
+        this.reprocessAndLoadData((e) => {
+            if (e) return callback(e);
             var testRow = (row, ri, cb) => {
                 var afterRequest = (err, res) => {
                     if (err) return cb(err);
@@ -156,7 +157,6 @@ module.exports = function () {
                         } else {
                             got.matchings = encodedResult;
                             row.matchings = extendedTarget;
-                            this.logFail(row, got, { matching: { query: this.query, response: res } });
                         }
 
                         cb(null, got);
diff --git a/features/step_definitions/nearest.js b/features/step_definitions/nearest.js
index 300eefe..450dce2 100644
--- a/features/step_definitions/nearest.js
+++ b/features/step_definitions/nearest.js
@@ -2,7 +2,8 @@ var util = require('util');
 
 module.exports = function () {
     this.When(/^I request nearest I should get$/, (table, callback) => {
-        this.reprocessAndLoadData(() => {
+        this.reprocessAndLoadData((e) => {
+            if (e) return callback(e);
             var testRow = (row, ri, cb) => {
                 var inNode = this.findNodeByName(row.in);
                 if (!inNode) throw new Error(util.format('*** unknown in-node "%s"'), row.in);
@@ -21,24 +22,16 @@ module.exports = function () {
 
                         var got = { in: row.in, out: row.out };
 
-                        var ok = true;
-
                         Object.keys(row).forEach((key) => {
                             if (key === 'out') {
                                 if (this.FuzzyMatch.matchLocation(coord, outNode)) {
                                     got[key] = row[key];
                                 } else {
                                     row[key] = util.format('%s [%d,%d]', row[key], outNode.lat, outNode.lon);
-                                    ok = false;
                                 }
                             }
                         });
 
-                        if (!ok) {
-                            var failed = { attempt: 'nearest', query: this.query, response: response };
-                            this.logFail(row, got, [failed]);
-                        }
-
                         cb(null, got);
                     }
                     else {
diff --git a/features/step_definitions/options.js b/features/step_definitions/options.js
index 2f67f52..d2177bf 100644
--- a/features/step_definitions/options.js
+++ b/features/step_definitions/options.js
@@ -2,32 +2,58 @@ var assert = require('assert');
 var fs = require('fs');
 
 module.exports = function () {
-    this.When(/^I run "osrm\-routed\s?(.*?)"$/, { timeout: this.TIMEOUT }, (options, callback) => {
-        this.runBin('osrm-routed', options, () => {
-            callback();
+    this.resetOptionsOutput = () => {
+        this.stdout = null;
+        this.stderr = null;
+        this.exitCode = null;
+        this.termSignal = null;
+    };
+
+    this.runAndSafeOutput = (binary, options, callback) => {
+        this.runBin(binary, this.expandOptions(options), this.environment, (err, stdout, stderr) => {
+            this.stdout = stdout;
+            this.stderr = stderr;
+            this.exitCode = err && err.code || 0;
+            this.termSignal = err && err.signal || '';
+            callback(err);
         });
+    };
+
+    this.When(/^I run "osrm\-routed\s?(.*?)"$/, { timeout: this.TIMEOUT }, (options, callback) => {
+        this.runAndSafeOutput('osrm-routed', options, callback);
     });
 
     this.When(/^I run "osrm\-extract\s?(.*?)"$/, (options, callback) => {
-        this.runBin('osrm-extract', options, () => {
-            callback();
-        });
+        this.runAndSafeOutput('osrm-extract', options, callback);
     });
 
     this.When(/^I run "osrm\-contract\s?(.*?)"$/, (options, callback) => {
-        this.runBin('osrm-contract', options, () => {
-            callback();
-        });
+        this.runAndSafeOutput('osrm-contract', options, callback);
+    });
+
+    this.When(/^I try to run "osrm\-routed\s?(.*?)"$/, (options, callback) => {
+        this.runAndSafeOutput('osrm-routed', options, () => { callback(); });
+    });
+
+    this.When(/^I try to run "osrm\-extract\s?(.*?)"$/, (options, callback) => {
+        this.runAndSafeOutput('osrm-extract', options, () => { callback(); });
+    });
+
+    this.When(/^I try to run "osrm\-contract\s?(.*?)"$/, (options, callback) => {
+        this.runAndSafeOutput('osrm-contract', options, () => { callback(); });
     });
 
     this.When(/^I run "osrm\-datastore\s?(.*?)"$/, (options, callback) => {
-        this.runBin('osrm-datastore', options, () => {
-            callback();
-        });
+        this.runAndSafeOutput('osrm-datastore', options, callback);
+    });
+
+    this.Then(/^it should exit successfully$/, () => {
+        assert.equal(this.exitCode, 0);
+        assert.equal(this.termSignal, '');
     });
 
-    this.Then(/^it should exit with code (\d+)$/, (code) => {
-        assert.equal(this.exitCode, parseInt(code));
+    this.Then(/^it should exit with an error$/, () => {
+        assert.ok(this.exitCode !== 0 || this.termSignal);
     });
 
     this.Then(/^stdout should contain "(.*?)"$/, (str) => {
@@ -61,7 +87,7 @@ module.exports = function () {
     });
 
     this.Then(/^datasource names should contain "(.+)"$/, (expectedData) => {
-        var actualData = fs.readFileSync(this.osmData.extractedFile + '.osrm.datasource_names', {encoding:'UTF-8'}).trim().split('\n').join(',');
+        var actualData = fs.readFileSync(this.processedCacheFile + '.datasource_names', {encoding:'UTF-8'}).trim().split('\n').join(',');
         assert.equal(actualData, expectedData);
     });
 
diff --git a/features/step_definitions/requests.js b/features/step_definitions/requests.js
index 99adee6..36eef83 100644
--- a/features/step_definitions/requests.js
+++ b/features/step_definitions/requests.js
@@ -2,7 +2,8 @@ var assert = require('assert');
 
 module.exports = function () {
     this.When(/^I request \/(.*)$/, (path, callback) => {
-        this.reprocessAndLoadData(() => {
+        this.reprocessAndLoadData((e) => {
+            if (e) return callback(e);
             this.requestPath(path, {}, (err, res, body) => {
                 this.response = res;
                 callback(err, res, body);
@@ -50,7 +51,7 @@ module.exports = function () {
     });
 
     this.Then(/^"([^"]*)" should return code (\d+)$/, (binary, code) => {
-        assert.ok(this.processError instanceof this.OSRMError);
+        assert.ok(this.processError instanceof Error);
         assert.equal(this.processError.process, binary);
         assert.equal(parseInt(this.processError.code), parseInt(code));
     });
diff --git a/features/step_definitions/routability.js b/features/step_definitions/routability.js
index c4190c5..ea2a45e 100644
--- a/features/step_definitions/routability.js
+++ b/features/step_definitions/routability.js
@@ -13,7 +13,7 @@ module.exports = function () {
             }
 
             this.reprocessAndLoadData((e) => {
-                if (e) callback(e);
+                if (e) return callback(e);
                 var testRow = (row, i, cb) => {
                     var outputRow = row;
 
@@ -41,10 +41,6 @@ module.exports = function () {
                             }
                         });
 
-                        if (outputRow != row) {
-                            this.logFail(row, outputRow, result);
-                        }
-
                         cb(null, outputRow);
                     });
                 };
@@ -116,7 +112,7 @@ module.exports = function () {
                     sq.defer(parseRes, key);
                 });
 
-                sq.awaitAll(() => { cb(null, result); });
+                sq.awaitAll((err) => { cb(err, result); });
             });
     };
 };
diff --git a/features/step_definitions/trip.js b/features/step_definitions/trip.js
index 35eb9ac..57ce079 100644
--- a/features/step_definitions/trip.js
+++ b/features/step_definitions/trip.js
@@ -4,7 +4,8 @@ module.exports = function () {
     this.When(/^I plan a trip I should get$/, (table, callback) => {
         var got;
 
-        this.reprocessAndLoadData(() => {
+        this.reprocessAndLoadData((e) => {
+            if (e) return callback(e);
             var testRow = (row, ri, cb) => {
                 var afterRequest = (err, res) => {
                     if (err) return cb(err);
@@ -84,23 +85,14 @@ module.exports = function () {
                     } else {
                         got.trips = encodedResult;
                         got.trips = extendedTarget;
-                        this.logFail(row, got, { trip: { query: this.query, response: res }});
                     }
 
-                    ok = true;
-
                     for (var key in row) {
                         if (this.FuzzyMatch.match(got[key], row[key])) {
                             got[key] = row[key];
-                        } else {
-                            ok = false;
                         }
                     }
 
-                    if (!ok) {
-                        this.logFail(row, got, { trip: { query: this.query, response: res }});
-                    }
-
                     cb(null, got);
                 };
 
diff --git a/features/support/cache.js b/features/support/cache.js
new file mode 100644
index 0000000..e388f59
--- /dev/null
+++ b/features/support/cache.js
@@ -0,0 +1,184 @@
+'use strict';
+
+const d3 = require('d3-queue');
+const fs = require('fs');
+const util = require('util');
+const path = require('path');
+const mkdirp = require('mkdirp');
+const hash = require('../lib/hash');
+const rimraf = require('rimraf');
+
+module.exports = function() {
+    this.initializeCache = (callback) => {
+        this.getOSRMHash((err, osrmHash) => {
+            if (err) return callback(err);
+            this.osrmHash = osrmHash;
+            callback();
+        });
+    };
+
+    // computes all paths for every feature
+    this.setupFeatures = (features, callback) => {
+        this.featureIDs = {};
+        this.featureCacheDirectories = {};
+        this.featureProcessedCacheDirectories = {};
+        let queue = d3.queue();
+
+        function initializeFeature(feature, callback) {
+            let uri = feature.getUri();
+
+            // setup cache for feature data
+            hash.hashOfFile(uri, (err, hash) => {
+                if (err) return callback(err);
+
+                // shorten uri to be realtive to 'features/'
+                let featurePath = path.relative(path.resolve('./features'), uri);
+                // bicycle/bollards/{HASH}/
+                let featureID = path.join(featurePath, hash);
+                let featureCacheDirectory = this.getFeatureCacheDirectory(featureID);
+                let featureProcessedCacheDirectory = this.getFeatureProcessedCacheDirectory(featureCacheDirectory, this.osrmHash);
+                this.featureIDs[uri] = featureID;
+                this.featureCacheDirectories[uri] = featureCacheDirectory;
+                this.featureProcessedCacheDirectories[uri] = featureProcessedCacheDirectory;
+
+                d3.queue(1)
+                  .defer(mkdirp, featureProcessedCacheDirectory)
+                  .defer(this.cleanupFeatureCache.bind(this), featureCacheDirectory, hash)
+                  .defer(this.cleanupProcessedFeatureCache.bind(this), featureProcessedCacheDirectory, this.osrmHash)
+                  .awaitAll(callback);
+            });
+        }
+
+        for (let i = 0; i < features.length; ++i) {
+            queue.defer(initializeFeature.bind(this), features[i]);
+        }
+        queue.awaitAll(callback);
+    };
+
+    this.cleanupProcessedFeatureCache = (directory, osrmHash, callback) => {
+        let parentPath = path.resolve(path.join(directory, '..'));
+        fs.readdir(parentPath, (err, files) => {
+            let q = d3.queue();
+            function runStats(path, callback) {
+                fs.stat(path, (err, stat) => {
+                    if (err) return callback(err);
+                    callback(null, {file: path, stat: stat});
+                });
+            }
+            files.map(f => { q.defer(runStats, path.join(parentPath, f)); });
+            q.awaitAll((err, results) => {
+                if (err) return callback(err);
+                let q = d3.queue();
+                results.forEach(r => {
+                    if (r.stat.isDirectory() && r.file.search(osrmHash) < 0) {
+                        q.defer(rimraf, r.file);
+                    }
+                });
+                q.awaitAll(callback);
+            });
+        });
+    };
+
+    this.cleanupFeatureCache = (directory, featureHash, callback) => {
+        let parentPath = path.resolve(path.join(directory, '..'));
+        fs.readdir(parentPath, (err, files) => {
+            let q = d3.queue();
+            files.filter(name => { return name !== featureHash;})
+                 .map((f) => { q.defer(rimraf, path.join(parentPath, f)); });
+            q.awaitAll(callback);
+        });
+    };
+
+    this.setupFeatureCache = (feature) => {
+        let uri = feature.getUri();
+        this.featureID = this.featureIDs[uri];
+        this.featureCacheDirectory = this.featureCacheDirectories[uri];
+        this.featureProcessedCacheDirectory = this.featureProcessedCacheDirectories[uri];
+    };
+
+    this.setupScenarioCache = (scenarioID) => {
+        this.scenarioCacheFile = this.getScenarioCacheFile(this.featureCacheDirectory, scenarioID);
+        this.processedCacheFile = this.getProcessedCacheFile(this.featureProcessedCacheDirectory, scenarioID);
+        this.inputCacheFile = this.getInputCacheFile(this.featureProcessedCacheDirectory, scenarioID);
+        this.rasterCacheFile = this.getRasterCacheFile(this.featureProcessedCacheDirectory, scenarioID);
+        this.speedsCacheFile = this.getSpeedsCacheFile(this.featureProcessedCacheDirectory, scenarioID);
+        this.penaltiesCacheFile = this.getPenaltiesCacheFile(this.featureProcessedCacheDirectory, scenarioID);
+    };
+
+    // returns a hash of all OSRM code side dependencies
+    this.getOSRMHash = (callback) => {
+        let dependencies = [
+            this.OSRM_EXTRACT_PATH,
+            this.OSRM_CONTRACT_PATH,
+            this.LIB_OSRM_EXTRACT_PATH,
+            this.LIB_OSRM_CONTRACT_PATH
+        ];
+
+        var addLuaFiles = (directory, callback) => {
+            fs.readdir(path.normalize(directory), (err, files) => {
+                if (err) return callback(err);
+
+                var luaFiles = files.filter(f => !!f.match(/\.lua$/)).map(f => path.normalize(directory + '/' + f));
+                Array.prototype.push.apply(dependencies, luaFiles);
+
+                callback();
+            });
+        };
+
+        // Note: we need a serialized queue here to ensure that the order of the files
+        // passed is stable. Otherwise the hash will not be stable
+        d3.queue(1)
+            .defer(addLuaFiles, this.PROFILES_PATH)
+            .defer(addLuaFiles, this.PROFILES_PATH + '/lib')
+            .awaitAll(hash.hashOfFiles.bind(hash, dependencies, callback));
+    };
+
+    // test/cache/bicycle/bollards/{HASH}/
+    this.getFeatureCacheDirectory = (featureID) => {
+        return path.join(this.CACHE_PATH, featureID);
+    };
+
+    // converts the scenario titles in file prefixes
+    this.getScenarioID = (scenario) => {
+        let name = scenario.getName().toLowerCase().replace(/[\/\-'=,\(\)]/g, '').replace(/\s/g, '_').replace(/__/g, '_').replace(/\.\./g, '.');
+        return util.format('%d_%s', scenario.getLine(), name);
+    };
+
+    // test/cache/{feature_path}/{feature_hash}/{scenario}_raster.asc
+    this.getRasterCacheFile = (featureCacheDirectory, scenarioID) => {
+        return path.join(featureCacheDirectory, scenarioID) + '_raster.asc';
+    };
+
+    // test/cache/{feature_path}/{feature_hash}/{scenario}_speeds.csv
+    this.getSpeedsCacheFile = (featureCacheDirectory, scenarioID) => {
+        return path.join(featureCacheDirectory, scenarioID) + '_speeds.csv';
+    };
+
+    // test/cache/{feature_path}/{feature_hash}/{scenario}_penalties.csv
+    this.getPenaltiesCacheFile = (featureCacheDirectory, scenarioID) => {
+        return path.join(featureCacheDirectory, scenarioID) + '_penalties.csv';
+    };
+
+    // test/cache/{feature_path}/{feature_hash}/{scenario}.osm
+    this.getScenarioCacheFile = (featureCacheDirectory, scenarioID) => {
+        return path.join(featureCacheDirectory, scenarioID) + '.osm';
+    };
+
+    // test/cache/{feature_path}/{feature_hash}/{osrm_hash}/
+    this.getFeatureProcessedCacheDirectory = (featureCacheDirectory, osrmHash) => {
+        return path.join(featureCacheDirectory, osrmHash);
+    };
+
+    // test/cache/{feature_path}/{feature_hash}/{osrm_hash}/{scenario}.osrm
+    this.getProcessedCacheFile = (featureProcessedCacheDirectory, scenarioID) => {
+        return path.join(featureProcessedCacheDirectory, scenarioID) + '.osrm';
+    };
+
+    // test/cache/{feature_path}/{feature_hash}/{osrm_hash}/{scenario}.osm
+    this.getInputCacheFile = (featureProcessedCacheDirectory, scenarioID) => {
+        return path.join(featureProcessedCacheDirectory, scenarioID) + '.osm';
+    };
+
+
+    return this;
+};
diff --git a/features/support/config.js b/features/support/config.js
deleted file mode 100644
index 769755f..0000000
--- a/features/support/config.js
+++ /dev/null
@@ -1,127 +0,0 @@
-var fs = require('fs');
-var path = require('path');
-var util = require('util');
-var d3 = require('d3-queue');
-var OSM = require('./build_osm');
-var classes = require('./data_classes');
-
-module.exports = function () {
-    this.initializeOptions = (callback) => {
-        this.profile = this.profile || this.DEFAULT_SPEEDPROFILE;
-
-        this.OSMDB = this.OSMDB || new OSM.DB();
-
-        this.nameNodeHash = this.nameNodeHash || {};
-
-        this.locationHash = this.locationHash || {};
-
-        this.nameWayHash = this.nameWayHash || {};
-
-        this.osmData = new classes.osmData(this);
-
-        this.OSRMLoader = this._OSRMLoader();
-
-        this.PREPROCESS_LOG_FILE = path.resolve(this.TEST_FOLDER, 'preprocessing.log');
-
-        this.LOG_FILE = path.resolve(this.TEST_FOLDER, 'fail.log');
-
-        this.HOST = 'http://127.0.0.1:' + this.OSRM_PORT;
-
-        this.DESTINATION_REACHED = 15;  // OSRM instruction code
-
-        this.shortcutsHash = this.shortcutsHash || {};
-
-        var hashLuaLib = (cb) => {
-            fs.readdir(path.normalize(this.PROFILES_PATH + '/lib/'), (err, files) => {
-                if (err) cb(err);
-                var luaFiles = files.filter(f => !!f.match(/\.lua$/)).map(f => path.normalize(this.PROFILES_PATH + '/lib/' + f));
-                this.hashOfFiles(luaFiles, hash => {
-                    this.luaLibHash = hash;
-                    cb();
-                });
-            });
-        };
-
-        var hashProfile = (cb) => {
-            this.hashProfile((hash) => {
-                this.profileHash = hash;
-                cb();
-            });
-        };
-
-        var hashExtract = (cb) => {
-            var files = [ util.format('%s/osrm-extract%s', this.BIN_PATH, this.EXE),
-                          util.format('%s/libosrm_extract%s', this.BIN_PATH, this.LIB) ];
-            this.hashOfFiles(files, (hash) => {
-                this.binExtractHash = hash;
-                cb();
-            });
-        };
-
-        var hashContract = (cb) => {
-            var files = [ util.format('%s/osrm-contract%s', this.BIN_PATH, this.EXE),
-                          util.format('%s/libosrm_contract%s', this.BIN_PATH, this.LIB) ];
-            this.hashOfFiles(files, (hash) => {
-                this.binContractHash = hash;
-                cb();
-            });
-        };
-
-        var hashRouted = (cb) => {
-            var files = [ util.format('%s/osrm-routed%s', this.BIN_PATH, this.EXE),
-                          util.format('%s/libosrm%s', this.BIN_PATH, this.LIB) ];
-            this.hashOfFiles(files, (hash) => {
-                this.binRoutedHash = hash;
-                this.fingerprintRoute = this.hashString(this.binRoutedHash);
-                cb();
-            });
-        };
-
-        d3.queue()
-            .defer(hashLuaLib)
-            .defer(hashProfile)
-            .defer(hashExtract)
-            .defer(hashContract)
-            .defer(hashRouted)
-            .awaitAll(() => {
-                this.AfterConfiguration(() => {
-                    callback();
-                });
-            });
-    };
-
-    this.updateFingerprintExtract = (str) => {
-        this.fingerprintExtract = this.hashString([this.fingerprintExtract, str].join('-'));
-    };
-
-    this.updateFingerprintContract = (str) => {
-        this.fingerprintContract = this.hashString([this.fingerprintContract, str].join('-'));
-    };
-
-    this.setProfile = (profile, cb) => {
-        var lastProfile = this.profile;
-        if (profile !== lastProfile) {
-            this.profile = profile;
-            this.hashProfile((hash) => {
-                this.profileHash = hash;
-                this.updateFingerprintExtract(this.profileHash);
-                cb();
-            });
-        } else {
-            this.updateFingerprintExtract(this.profileHash);
-            cb();
-        }
-    };
-
-    this.setExtractArgs = (args, callback) => {
-        this.extractArgs = args;
-        this.updateFingerprintExtract(args);
-        callback();
-    };
-
-    this.setContractArgs = (args, callback) => {
-        this.contractArgs = args;
-        this.updateFingerprintContract(args);
-        callback();
-    };
-};
diff --git a/features/support/data.js b/features/support/data.js
index 17c5bbf..f0bf302 100644
--- a/features/support/data.js
+++ b/features/support/data.js
@@ -1,11 +1,14 @@
-var fs = require('fs');
-var path = require('path');
-var util = require('util');
-var exec = require('child_process').exec;
-var d3 = require('d3-queue');
+'use strict';
 
-var OSM = require('./build_osm');
-var classes = require('./data_classes');
+const fs = require('fs');
+const util = require('util');
+const d3 = require('d3-queue');
+
+const OSM = require('../lib/osm');
+const classes = require('./data_classes');
+const tableDiff = require('../lib/table_diff');
+const ensureDecimal = require('../lib/utils').ensureDecimal;
+const errorReason = require('../lib/utils').errorReason;
 
 module.exports = function () {
     this.setGridSize = (meters) => {
@@ -94,13 +97,8 @@ module.exports = function () {
         q.awaitAll(callback);
     };
 
-    this.ensureDecimal = (i) => {
-        if (parseInt(i) === i) return i.toFixed(1);
-        else return i;
-    };
-
     this.tableCoordToLonLat = (ci, ri) => {
-        return [this.origin[0] + ci * this.zoom, this.origin[1] - ri * this.zoom].map(this.ensureDecimal);
+        return [this.origin[0] + ci * this.zoom, this.origin[1] - ri * this.zoom].map(ensureDecimal);
     };
 
     this.addOSMNode = (name, lon, lat, id) => {
@@ -132,10 +130,6 @@ module.exports = function () {
         return this.nameWayHash[s.toString()] || this.nameWayHash[s.toString().split('').reverse().join('')];
     };
 
-    this.resetData = () => {
-        this.resetOSM();
-    };
-
     this.makeOSMId = () => {
         this.osmID = this.osmID + 1;
         return this.osmID;
@@ -143,205 +137,88 @@ module.exports = function () {
 
     this.resetOSM = () => {
         this.OSMDB.clear();
-        this.osmData.reset();
         this.nameNodeHash = {};
         this.locationHash = {};
+        this.shortcutsHash = {};
         this.nameWayHash = {};
         this.osmID = 0;
     };
 
     this.writeOSM = (callback) => {
-        fs.exists(this.DATA_FOLDER, (exists) => {
-            var mkDirFn = exists ? (cb) => { cb(); } : fs.mkdir.bind(fs.mkdir, this.DATA_FOLDER);
-            mkDirFn((err) => {
-                if (err) return callback(err);
-                var osmPath = path.resolve(this.DATA_FOLDER, util.format('%s.osm', this.osmData.osmFile));
-                fs.exists(osmPath, (exists) => {
-                    if (!exists) fs.writeFile(osmPath, this.osmData.str, callback);
-                    else callback();
+        fs.exists(this.scenarioCacheFile, (exists) => {
+            if (exists) callback();
+            else {
+                this.OSMDB.toXML((xml) => {
+                    fs.writeFile(this.scenarioCacheFile, xml, callback);
                 });
-            });
-        });
-    };
-
-    this.isExtracted = (callback) => {
-        fs.exists(util.format('%s.osrm', this.osmData.extractedFile), (core) => {
-            if (!core) return callback(false);
-            fs.exists(util.format('%s.osrm.names', this.osmData.extractedFile), (names) => {
-                if (!names) return callback(false);
-                fs.exists(util.format('%s.osrm.restrictions', this.osmData.extractedFile), (restrictions) => {
-                    return callback(restrictions);
-                });
-            });
-        });
-    };
-
-    this.isContracted = (callback) => {
-        fs.exists(util.format('%s.osrm.hsgr', this.osmData.contractedFile), callback);
-    };
-
-    this.writeTimestamp = (callback) => {
-        fs.writeFile(util.format('%s.osrm.timestamp', this.osmData.contractedFile), this.OSM_TIMESTAMP, callback);
-    };
-
-    this.writeInputData = (callback) => {
-        this.writeOSM((err) => {
-            if (err) return callback(err);
-            this.writeTimestamp(callback);
+            }
         });
     };
 
-    this.extractData = (callback) => {
-        this.logPreprocessInfo();
-        this.log(util.format('== Extracting %s.osm...', this.osmData.osmFile), 'preprocess');
-        var cmd = util.format('%s/osrm-extract %s.osm %s --profile %s/%s.lua >>%s 2>&1',
-            this.BIN_PATH, this.osmData.osmFile, this.extractArgs || '', this.PROFILES_PATH, this.profile, this.PREPROCESS_LOG_FILE);
-        this.log(cmd);
-        process.chdir(this.TEST_FOLDER);
-        exec(cmd, (err) => {
-            if (err) {
-                this.log(util.format('*** Exited with code %d', err.code), 'preprocess');
-                process.chdir('../');
-                return callback(this.ExtractError(err.code, util.format('osrm-extract exited with code %d', err.code)));
+    this.linkOSM = (callback) => {
+        fs.exists(this.inputCacheFile, (exists) => {
+            if (exists) callback();
+            else {
+                fs.link(this.scenarioCacheFile, this.inputCacheFile, callback);
             }
-
-            var q = d3.queue();
-
-            var rename = (file, cb) => {
-                this.log(util.format('Renaming %s.%s to %s.%s', this.osmData.osmFile, file, this.osmData.extractedFile, file), 'preprocess');
-                fs.rename([this.osmData.osmFile, file].join('.'), [this.osmData.extractedFile, file].join('.'), (err) => {
-                    if (err) return cb(this.FileError(null, 'failed to rename data file after extracting'));
-                    cb();
-                });
-            };
-
-            var renameIfExists = (file, cb) => {
-                fs.stat([this.osmData.osmFile, file].join('.'), (doesNotExistErr, exists) => {
-                    if (exists) rename(file, cb);
-                    else cb();
-                });
-            };
-
-            ['osrm', 'osrm.ebg', 'osrm.edges', 'osrm.enw', 'osrm.fileIndex', 'osrm.geometry', 'osrm.icd',
-             'osrm.names', 'osrm.nodes', 'osrm.properties', 'osrm.ramIndex', 'osrm.restrictions', 'osrm.tld', 'osrm.tls'].forEach(file => {
-                 q.defer(rename, file);
-             });
-
-            ['osrm.edge_penalties', 'osrm.edge_segment_lookup'].forEach(file => {
-                q.defer(renameIfExists, file);
-            });
-
-            q.awaitAll((err) => {
-                this.log('Finished extracting ' + this.osmData.extractedFile, 'preprocess');
-                process.chdir('../');
-                callback(err);
-            });
         });
     };
 
-    this.contractData = (callback) => {
-        this.logPreprocessInfo();
-        this.log(util.format('== Contracting %s.osm...', this.osmData.extractedFile), 'preprocess');
-        var cmd = util.format('%s/osrm-contract %s %s.osrm >>%s 2>&1',
-            this.BIN_PATH, this.contractArgs || '', this.osmData.extractedFile, this.PREPROCESS_LOG_FILE);
-        this.log(cmd);
-        process.chdir(this.TEST_FOLDER);
-        exec(cmd, (err) => {
-            if (err) {
-                this.log(util.format('*** Exited with code %d', err.code), 'preprocess');
-                process.chdir('../');
-                return callback(this.ContractError(err.code, util.format('osrm-contract exited with code %d', err.code)));
-            }
-
-            var rename = (file, cb) => {
-                this.log(util.format('Renaming %s.%s to %s.%s', this.osmData.extractedFile, file, this.osmData.contractedFile, file), 'preprocess');
-                fs.rename([this.osmData.extractedFile, file].join('.'), [this.osmData.contractedFile, file].join('.'), (err) => {
-                    if (err) return cb(this.FileError(null, 'failed to rename data file after contracting.'));
-                    cb();
-                });
-            };
-
-            var renameIfExists = (file, cb) => {
-                fs.stat([this.osmData.extractedFile, file].join('.'), (doesNotExistErr, exists) => {
-                    if (exists) rename(file, cb);
-                    else cb();
-                });
-            };
-
-            var copy = (file, cb) => {
-                this.log(util.format('Copying %s.%s to %s.%s', this.osmData.extractedFile, file, this.osmData.contractedFile, file), 'preprocess');
-                fs.createReadStream([this.osmData.extractedFile, file].join('.'))
-                    .pipe(fs.createWriteStream([this.osmData.contractedFile, file].join('.'))
-                            .on('finish', cb)
-                        )
-                    .on('error', () => {
-                        return cb(this.FileError(null, 'failed to copy data after contracting.'));
-                    });
-            };
-
-            var q = d3.queue();
-
-            ['osrm', 'osrm.core', 'osrm.datasource_indexes', 'osrm.datasource_names', 'osrm.ebg','osrm.edges',
-             'osrm.enw', 'osrm.fileIndex', 'osrm.geometry', 'osrm.hsgr', 'osrm.icd','osrm.level', 'osrm.names',
-             'osrm.nodes', 'osrm.properties', 'osrm.ramIndex', 'osrm.restrictions', 'osrm.tld', 'osrm.tls'].forEach((file) => {
-                 q.defer(rename, file);
-             });
+    this.extractData = (p, callback) => {
+        let stamp = p.processedCacheFile + '.extract';
+        fs.exists(stamp, (exists) => {
+            if (exists) return callback();
 
-            ['osrm.edge_penalties', 'osrm.edge_segment_lookup'].forEach(file => {
-                q.defer(renameIfExists, file);
-            });
-
-            [].forEach((file) => {
-                q.defer(copy, file);
-            });
-
-            q.awaitAll((err) => {
-                this.log('Finished contracting ' + this.osmData.contractedFile, 'preprocess');
-                process.chdir('../');
-                callback(err);
+            this.runBin('osrm-extract', util.format('%s --profile %s %s', p.extractArgs, p.profileFile, p.inputCacheFile), p.environment, (err) => {
+                if (err) {
+                    return callback(new Error(util.format('osrm-extract %s: %s', errorReason(err), err.cmd)));
+                }
+                fs.writeFile(stamp, 'ok', callback);
             });
         });
     };
 
-    var noop = (cb) => cb();
+    this.contractData = (p, callback) => {
+        let stamp = p.processedCacheFile + '.contract';
+        fs.exists(stamp, (exists) => {
+            if (exists) return callback();
 
-    this.reprocess = (callback) => {
-        this.osmData.populate(() => {
-            this.isContracted((isContracted) => {
-                if (!isContracted) {
-                    this.writeAndExtract((e) => {
-                        if (e) return callback(e);
-                        this.contractData((e) => {
-                            if (e) return callback(e);
-                            this.logPreprocessDone();
-                            callback();
-                        });
-                    });
-                } else {
-                    this.log('Already contracted ' + this.osmData.contractedFile, 'preprocess');
-                    callback();
+            this.runBin('osrm-contract', util.format('%s %s', p.contractArgs, p.processedCacheFile), p.environment, (err) => {
+                if (err) {
+                    return callback(new Error(util.format('osrm-contract %s: %s', errorReason(err), err)));
                 }
+                fs.writeFile(stamp, 'ok', callback);
             });
         });
     };
 
-    this.writeAndExtract = (callback) => {
-        this.writeInputData((e) => {
-            if (e) return callback(e);
-            this.isExtracted((isExtracted) => {
-                var extractFn = isExtracted ? noop : this.extractData;
-                if (isExtracted) this.log('Already extracted ' + this.osmData.extractedFile, 'preprocess');
-                extractFn((e) => {
-                    callback(e);
-                });
-            });
-        });
+    this.extractAndContract = (callback) => {
+        // a shallow copy of scenario parameters to avoid data inconsistency
+        // if a cucumber timeout occurs during deferred jobs
+        let p = {extractArgs: this.extractArgs, contractArgs: this.contractArgs,
+                 profileFile: this.profileFile, inputCacheFile: this.inputCacheFile,
+                 processedCacheFile: this.processedCacheFile, environment: this.environment};
+        let queue = d3.queue(1);
+        queue.defer(this.extractData.bind(this), p);
+        queue.defer(this.contractData.bind(this), p);
+        queue.awaitAll(callback);
+    };
+
+    this.reprocess = (callback) => {
+        let queue = d3.queue(1);
+        queue.defer(this.writeOSM.bind(this));
+        queue.defer(this.linkOSM.bind(this));
+        queue.defer(this.extractAndContract.bind(this));
+        queue.awaitAll(callback);
     };
 
     this.reprocessAndLoadData = (callback) => {
-        this.reprocess(() => {
-            this.OSRMLoader.load(util.format('%s.osrm', this.osmData.contractedFile), callback);
-        });
+        let queue = d3.queue(1);
+        queue.defer(this.writeOSM.bind(this));
+        queue.defer(this.linkOSM.bind(this));
+        queue.defer(this.extractAndContract.bind(this));
+        queue.defer(this.osrmLoader.load.bind(this.osrmLoader), this.processedCacheFile);
+        queue.awaitAll(callback);
     };
 
     this.processRowsAndDiff = (table, fn, callback) => {
@@ -351,7 +228,9 @@ module.exports = function () {
 
         q.awaitAll((err, actual) => {
             if (err) return callback(err);
-            this.diffTables(table, actual, {}, callback);
+            let diff = tableDiff(table, actual);
+            if (diff) callback(new Error(diff));
+            else callback();
         });
     };
 };
diff --git a/features/support/data_classes.js b/features/support/data_classes.js
index 391bb1c..a17141e 100644
--- a/features/support/data_classes.js
+++ b/features/support/data_classes.js
@@ -1,7 +1,6 @@
 'use strict';
 
-var util = require('util');
-var path = require('path');
+const util = require('util');
 
 module.exports = {
     Location: class {
@@ -11,43 +10,6 @@ module.exports = {
         }
     },
 
-    osmData: class {
-        constructor (scope) {
-            this.scope          = scope;
-            this.str            = null;
-            this.hash           = null;
-            this.fingerprintOSM = null;
-            this.osmFile        = null;
-            this.extractedFile  = null;
-            this.contractedFile   = null;
-        }
-
-        populate (callback) {
-            this.scope.OSMDB.toXML((str) => {
-                this.str = str;
-
-                this.hash = this.scope.hashString(str);
-                this.fingerprintOSM = this.scope.hashString(this.hash);
-
-                this.osmFile        = path.resolve(this.scope.DATA_FOLDER, this.fingerprintOSM);
-
-                this.extractedFile  = path.resolve([this.osmFile, this.scope.fingerprintExtract].join('_'));
-                this.contractedFile   = path.resolve([this.osmFile, this.scope.fingerprintExtract, this.scope.fingerprintContract].join('_'));
-
-                callback();
-            });
-        }
-
-        reset () {
-            this.str            = null;
-            this.hash           = null;
-            this.fingerprintOSM = null;
-            this.osmFile        = null;
-            this.extractedFile  = null;
-            this.contractedFile   = null;
-        }
-    },
-
     FuzzyMatch: class {
         match (got, want) {
             var matchPercent = want.match(/(.*)\s+~(.+)%$/),
diff --git a/features/support/env.js b/features/support/env.js
index c99d544..5df8c66 100644
--- a/features/support/env.js
+++ b/features/support/env.js
@@ -1,32 +1,46 @@
-var path = require('path');
-var util = require('util');
-var fs = require('fs');
-var exec = require('child_process').exec;
-var d3 = require('d3-queue');
+'use strict';
 
+const path = require('path');
+const util = require('util');
+const fs = require('fs');
+const d3 = require('d3-queue');
+const child_process = require('child_process');
+const tryConnect = require('../lib/try_connect');
+
+// Sets up all constants that are valid for all features
 module.exports = function () {
     this.initializeEnv = (callback) => {
-        this.OSRM_PORT = process.env.OSRM_PORT && parseInt(process.env.OSRM_PORT) || 5000;
         this.TIMEOUT = process.env.CUCUMBER_TIMEOUT && parseInt(process.env.CUCUMBER_TIMEOUT) || 5000;
+        // set cucumber default timeout
         this.setDefaultTimeout(this.TIMEOUT);
-        this.ROOT_FOLDER = process.cwd();
+        this.ROOT_PATH = process.cwd();
+
+        this.TEST_PATH = path.resolve(this.ROOT_PATH, 'test');
+        this.CACHE_PATH = path.resolve(this.TEST_PATH, 'cache');
+        this.LOGS_PATH = path.resolve(this.TEST_PATH, 'logs');
+
+        this.PROFILES_PATH = path.resolve(this.ROOT_PATH, 'profiles');
+        this.FIXTURES_PATH = path.resolve(this.ROOT_PATH, 'unit_tests/fixtures');
+        this.BIN_PATH = process.env.OSRM_BUILD_DIR && process.env.OSRM_BUILD_DIR || path.resolve(this.ROOT_PATH, 'build');
+        var stxxl_config = path.resolve(this.ROOT_PATH, 'test/.stxxl');
+        if (!fs.existsSync(stxxl_config)) {
+            return callback(new Error('*** '+stxxl_config+ 'does not exist'));
+        }
+
+        this.DEFAULT_ENVIRONMENT = Object.assign({STXXLCFG: stxxl_config}, process.env);
+        this.DEFAULT_PROFILE = 'bicycle';
+        this.DEFAULT_INPUT_FORMAT = 'osm';
+        this.DEFAULT_LOAD_METHOD = 'datastore';
+        this.DEFAULT_ORIGIN = [1,1];
         this.OSM_USER = 'osrm';
         this.OSM_GENERATOR = 'osrm-test';
         this.OSM_UID = 1;
-        this.TEST_FOLDER = path.resolve(this.ROOT_FOLDER, 'test');
-        this.DATA_FOLDER = path.resolve(this.TEST_FOLDER, 'cache');
         this.OSM_TIMESTAMP = '2000-01-01T00:00:00Z';
-        this.DEFAULT_SPEEDPROFILE = 'bicycle';
         this.WAY_SPACING = 100;
-        this.DEFAULT_GRID_SIZE = 100;    // meters
-        this.PROFILES_PATH = path.resolve(this.ROOT_FOLDER, 'profiles');
-        this.FIXTURES_PATH = path.resolve(this.ROOT_FOLDER, 'unit_tests/fixtures');
-        this.BIN_PATH = process.env.OSRM_BUILD_DIR && process.env.OSRM_BUILD_DIR || path.resolve(this.ROOT_FOLDER, 'build');
-        this.DEFAULT_INPUT_FORMAT = 'osm';
-        this.DEFAULT_ORIGIN = [1,1];
-        this.DEFAULT_LOAD_METHOD = 'datastore';
-        this.OSRM_ROUTED_LOG_FILE = path.resolve(this.TEST_FOLDER, 'osrm-routed.log');
-        this.ERROR_LOG_FILE = path.resolve(this.TEST_FOLDER, 'error.log');
+        this.DEFAULT_GRID_SIZE = 100; // meters
+
+        this.OSRM_PORT = process.env.OSRM_PORT && parseInt(process.env.OSRM_PORT) || 5000;
+        this.HOST = 'http://127.0.0.1:' + this.OSRM_PORT;
 
         // TODO make sure this works on win
         if (process.platform.match(/indows.*/)) {
@@ -37,36 +51,67 @@ module.exports = function () {
         } else {
             this.TERMSIGNAL = 'SIGTERM';
             this.EXE = '';
-            this.LIB = '.so';
+
+            // heuristically detect .so/.a suffix
+            this.LIB = null;
+
+            try {
+                const dot_a = util.format('%s/libosrm%s', this.BIN_PATH, '.a');
+                fs.accessSync(dot_a, fs.F_OK);
+                this.LIB = '.a';
+            } catch(e) { /*nop*/ }
+
+            try {
+                const dot_so = util.format('%s/libosrm%s', this.BIN_PATH, '.so');
+                fs.accessSync(dot_so, fs.F_OK);
+                this.LIB = '.so';
+            } catch(e) { /*nop*/ }
+
+            if (!this.LIB) {
+                throw new Error('*** Unable to detect dynamic or static libosrm libraries');
+            }
+
             this.QQ = '';
         }
 
+        this.OSRM_EXTRACT_PATH = path.resolve(util.format('%s/%s%s', this.BIN_PATH, 'osrm-extract', this.EXE));
+        this.OSRM_CONTRACT_PATH = path.resolve(util.format('%s/%s%s', this.BIN_PATH, 'osrm-contract', this.EXE));
+        this.OSRM_ROUTED_PATH = path.resolve(util.format('%s/%s%s', this.BIN_PATH, 'osrm-routed', this.EXE));
+        this.LIB_OSRM_EXTRACT_PATH = util.format('%s/libosrm_extract%s', this.BIN_PATH, this.LIB),
+        this.LIB_OSRM_CONTRACT_PATH = util.format('%s/libosrm_contract%s', this.BIN_PATH, this.LIB),
+        this.LIB_OSRM_PATH = util.format('%s/libosrm%s', this.BIN_PATH, this.LIB);
+
         // eslint-disable-next-line no-console
         console.info(util.format('Node Version', process.version));
-        if (parseInt(process.version.match(/v(\d)/)[1]) < 4) throw new Error('*** PLease upgrade to Node 4.+ to run OSRM cucumber tests');
+        if (parseInt(process.version.match(/v(\d)/)[1]) < 4) throw new Error('*** Please upgrade to Node 4.+ to run OSRM cucumber tests');
 
-        fs.exists(this.TEST_FOLDER, (exists) => {
-            if (!exists) throw new Error(util.format('*** Test folder %s doesn\'t exist.', this.TEST_FOLDER));
-            callback();
+        fs.exists(this.TEST_PATH, (exists) => {
+            if (exists)
+                return callback();
+            else
+                return callback(new Error('*** Test folder doesn\'t exist.'));
         });
     };
 
-    this.verifyOSRMIsNotRunning = () => {
-        if (this.OSRMLoader.up()) {
-            throw new Error('*** osrm-routed is already running.');
-        }
+    this.getProfilePath = (profile) => {
+        return path.resolve(this.PROFILES_PATH, profile + '.lua');
+    };
+
+    this.verifyOSRMIsNotRunning = (callback) => {
+        tryConnect(this.OSRM_PORT, (err) => {
+            if (!err) return callback(new Error('*** osrm-routed is already running.'));
+            else callback();
+        });
     };
 
     this.verifyExistenceOfBinaries = (callback) => {
-        var verify = (bin, cb) => {
-            var binPath = path.resolve(util.format('%s/%s%s', this.BIN_PATH, bin, this.EXE));
+        var verify = (binPath, cb) => {
             fs.exists(binPath, (exists) => {
-                if (!exists) throw new Error(util.format('%s is missing. Build failed?', binPath));
+                if (!exists) return cb(new Error(util.format('%s is missing. Build failed?', binPath)));
                 var helpPath = util.format('%s --help > /dev/null 2>&1', binPath);
-                exec(helpPath, (err) => {
+                child_process.exec(helpPath, (err) => {
                     if (err) {
-                        this.log(util.format('*** Exited with code %d', err.code), 'preprocess');
-                        throw new Error(util.format('*** %s exited with code %d', helpPath, err.code));
+                        return cb(new Error(util.format('*** %s exited with code %d', helpPath, err.code)));
                     }
                     cb();
                 });
@@ -74,23 +119,12 @@ module.exports = function () {
         };
 
         var q = d3.queue();
-        ['osrm-extract', 'osrm-contract', 'osrm-routed'].forEach(bin => { q.defer(verify, bin); });
-        q.awaitAll(() => {
-            callback();
-        });
-    };
-
-    this.AfterConfiguration = (callback) => {
-        this.clearLogFiles(() => {
-            this.verifyOSRMIsNotRunning();
-            this.verifyExistenceOfBinaries(() => {
-                callback();
-            });
-        });
+        [this.OSRM_EXTRACT_PATH, this.OSRM_CONTRACT_PATH, this.OSRM_ROUTED_PATH].forEach(bin => { q.defer(verify, bin); });
+        q.awaitAll(callback);
     };
 
     process.on('exit', () => {
-        if (this.OSRMLoader.loader) this.OSRMLoader.shutdown(() => {});
+        this.osrmLoader.shutdown(() => {});
     });
 
     process.on('SIGINT', () => {
diff --git a/features/support/exception_classes.js b/features/support/exception_classes.js
deleted file mode 100644
index 36bdffe..0000000
--- a/features/support/exception_classes.js
+++ /dev/null
@@ -1,132 +0,0 @@
-'use strict';
-
-var util = require('util');
-var path = require('path');
-var fs = require('fs');
-var chalk = require('chalk');
-
-var OSRMError = class extends Error {
-    constructor (process, code, msg, log, lines) {
-        super(msg);
-        this.process = process;
-        this.code = code;
-        this.msg = msg;
-        this.lines = lines;
-        this.log = log;
-    }
-
-    extract (callback) {
-        this.logTail(this.log, this.lines, callback);
-    }
-
-    // toString (callback) {
-    //     this.extract((tail) => {
-    //         callback(util.format('*** %s\nLast %s from %s:\n%s\n', this.msg, this.lines, this.log, tail));
-    //     });
-    // }
-
-    logTail (logPath, n, callback) {
-        var expanded = path.resolve(this.TEST_FOLDER, logPath);
-        fs.exists(expanded, (exists) => {
-            if (exists) {
-                fs.readFile(expanded, (err, data) => {
-                    var lines = data.toString().trim().split('\n');
-                    callback(lines
-                        .slice(lines.length - n)
-                        .map(line => util.format('    %s', line))
-                        .join('\n'));
-                });
-            } else {
-                callback(util.format('File %s does not exist!', expanded));
-            }
-        });
-    }
-};
-
-var unescapeStr = (str) => str.replace(/\\\|/g, '\|').replace(/\\\\/g, '\\');
-
-module.exports = {
-    OSRMError: OSRMError,
-
-    FileError: class extends OSRMError {
-        constructor (logFile, code, msg) {
-            super ('fileutil', code, msg, logFile, 5);
-        }
-    },
-
-    LaunchError: class extends OSRMError {
-        constructor (logFile, launchProcess, code, msg) {
-            super (launchProcess, code, msg, logFile, 5);
-        }
-    },
-
-    ExtractError: class extends OSRMError {
-        constructor (logFile, code, msg) {
-            super('osrm-extract', code, msg, logFile, 3);
-        }
-    },
-
-    ContractError:  class extends OSRMError {
-        constructor (logFile, code, msg) {
-            super('osrm-contract', code, msg, logFile, 3);
-        }
-    },
-
-    RoutedError: class extends OSRMError {
-        constructor (logFile, msg) {
-            super('osrm-routed', null, msg, logFile, 3);
-        }
-    },
-
-    TableDiffError: class extends Error {
-        constructor (expected, actual) {
-            super();
-            this.headers = expected.raw()[0];
-            this.expected = expected.hashes();
-            this.actual = actual;
-            this.diff = [];
-            this.hasErrors = false;
-
-            var good = 0, bad = 0;
-
-            this.expected.forEach((row, i) => {
-                var rowError = false;
-
-                for (var j in row) {
-                    if (unescapeStr(row[j]) != actual[i][j]) {
-                        rowError = true;
-                        this.hasErrors = true;
-                        break;
-                    }
-                }
-
-                if (rowError) {
-                    bad++;
-                    this.diff.push(Object.assign({}, row, {c_status: 'undefined'}));
-                    this.diff.push(Object.assign({}, actual[i], {c_status: 'comment'}));
-                } else {
-                    good++;
-                    this.diff.push(row);
-                }
-            });
-        }
-
-        get string () {
-            if (!this.hasErrors) return null;
-
-            var s = ['Tables were not identical:'];
-            s.push(this.headers.map(key => '    ' + key).join(' | '));
-            this.diff.forEach((row) => {
-                var rowString = '| ';
-                this.headers.forEach((header) => {
-                    if (!row.c_status) rowString += chalk.green('    ' + row[header] + ' | ');
-                    else if (row.c_status === 'undefined') rowString += chalk.yellow('(-) ' + row[header] + ' | ');
-                    else rowString += chalk.red('(+) ' + row[header] + ' | ');
-                });
-                s.push(rowString);
-            });
-
-            return s.join('\n') + '\nTODO this is a temp workaround waiting for https://github.com/cucumber/cucumber-js/issues/534';
-        }
-    }
-};
diff --git a/features/support/exceptions.js b/features/support/exceptions.js
deleted file mode 100644
index 6af1a93..0000000
--- a/features/support/exceptions.js
+++ /dev/null
@@ -1,15 +0,0 @@
-var exceptions = require('./exception_classes');
-
-module.exports = function () {
-    this.OSRMError = exceptions.OSRMError,
-
-    this.FileError = (code, msg) => new (exceptions.FileError.bind(exceptions.FileError, this.PREPROCESS_LOG_FILE))(code, msg);
-
-    this.LaunchError = (code, launchProcess, msg) => new (exceptions.LaunchError.bind(exceptions.LaunchError, this.ERROR_LOG_FILE))(code, launchProcess, msg);
-
-    this.ExtractError = (code, msg) => new (exceptions.ExtractError.bind(exceptions.ExtractError, this.PREPROCESS_LOG_FILE))(code, msg);
-
-    this.ContractError = (code, msg) => new (exceptions.ContractError.bind(exceptions.ContractError, this.PREPROCESS_LOG_FILE))(code, msg);
-
-    this.RoutedError = (msg) => new (exceptions.RoutedError.bind(exceptions.RoutedError, this.OSRM_ROUTED_LOG_FILE))(msg);
-};
diff --git a/features/support/hash.js b/features/support/hash.js
deleted file mode 100644
index 399dd51..0000000
--- a/features/support/hash.js
+++ /dev/null
@@ -1,43 +0,0 @@
-var fs = require('fs');
-var path = require('path');
-var crypto = require('crypto');
-var d3 = require('d3-queue');
-
-module.exports = function () {
-    this.hashOfFiles = (paths, cb) => {
-        paths = Array.isArray(paths) ? paths : [paths];
-        var shasum = crypto.createHash('sha1'), hashedFiles = false;
-
-        var q = d3.queue(1);
-
-        var addFile = (path, cb) => {
-            fs.readFile(path, (err, data) => {
-                if (err && err.code === 'ENOENT') cb(); // ignore non-existing files
-                else if (err) cb(err);
-                else {
-                    shasum.update(data);
-                    hashedFiles = true;
-                    cb();
-                }
-            });
-        };
-
-        paths.forEach(path => { q.defer(addFile, path); });
-
-        q.awaitAll(err => {
-            if (err) throw new Error('*** Error reading files:', err);
-            if (!hashedFiles) throw new Error('*** No files found: [' + paths.join(', ') + ']');
-            cb(shasum.digest('hex'));
-        });
-    };
-
-    this.hashProfile = (cb) => {
-        this.hashOfFiles(path.resolve(this.PROFILES_PATH, this.profile + '.lua'), cb);
-    };
-
-    this.hashString = (str) => {
-        return crypto.createHash('sha1').update(str).digest('hex');
-    };
-
-    return this;
-};
diff --git a/features/support/hooks.js b/features/support/hooks.js
index 1e265ea..b0f6b0b 100644
--- a/features/support/hooks.js
+++ b/features/support/hooks.js
@@ -1,36 +1,61 @@
-var util = require('util');
+'use strict';
+
+var d3 = require('d3-queue');
+var path = require('path');
+var mkdirp = require('mkdirp');
+var rimraf = require('rimraf');
+var OSM = require('../lib/osm');
+var OSRMLoader = require('../lib/osrm_loader');
 
 module.exports = function () {
-    this.BeforeFeatures((features, callback) => {
-        this.pid = null;
-        this.initializeEnv(() => {
-            this.initializeOptions(callback);
-        });
+    this.registerHandler('BeforeFeatures', {timeout: 30000},  (features, callback) => {
+        this.osrmLoader = new OSRMLoader(this);
+        this.OSMDB = new OSM.DB();
+
+        let queue = d3.queue(1);
+        queue.defer(this.initializeEnv.bind(this));
+        queue.defer(this.verifyOSRMIsNotRunning.bind(this));
+        queue.defer(this.verifyExistenceOfBinaries.bind(this));
+        queue.defer(this.initializeCache.bind(this));
+        queue.defer(this.setupFeatures.bind(this, features));
+        queue.awaitAll(callback);
     });
 
-    this.Before((scenario, callback) => {
-        this.scenarioTitle = scenario.getName();
+    this.BeforeFeature((feature, callback) => {
+        this.profile = this.DEFAULT_PROFILE;
+        this.profileFile = path.join(this.PROFILES_PATH, this.profile + '.lua');
+        this.setupFeatureCache(feature);
+        callback();
+    });
 
-        this.loadMethod = this.DEFAULT_LOAD_METHOD;
-        this.queryParams = {};
-        var d = new Date();
-        this.scenarioTime = util.format('%d-%d-%dT%s:%s:%sZ', d.getFullYear(), d.getMonth()+1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds());
-        this.resetData();
-        this.hasLoggedPreprocessInfo = false;
-        this.hasLoggedScenarioInfo = false;
+    this.Before((scenario, callback) => {
+        this.osrmLoader.setLoadMethod(this.DEFAULT_LOAD_METHOD);
         this.setGridSize(this.DEFAULT_GRID_SIZE);
         this.setOrigin(this.DEFAULT_ORIGIN);
-        this.fingerprintExtract = this.hashString([this.luaLibHash, this.binExtractHash].join('-'));
-        this.fingerprintContract = this.hashString(this.binContractHash);
-        callback();
+        this.queryParams = {};
+        this.extractArgs = '';
+        this.contractArgs = '';
+        this.environment = Object.assign(this.DEFAULT_ENVIRONMENT);
+        this.resetOSM();
+
+        this.scenarioID = this.getScenarioID(scenario);
+        this.setupScenarioCache(this.scenarioID);
+
+        // setup output logging
+        let logDir = path.join(this.LOGS_PATH, this.featureID);
+        this.scenarioLogFile = path.join(logDir, this.scenarioID) + '.log';
+        d3.queue(1)
+            .defer(mkdirp, logDir)
+            .defer(rimraf, this.scenarioLogFile)
+            .awaitAll(callback);
     });
 
     this.After((scenario, callback) => {
-        this.setExtractArgs('', () => {
-            this.setContractArgs('', () => {
-                if (this.loadMethod === 'directly' && !!this.OSRMLoader.loader) this.OSRMLoader.shutdown(callback);
-                else callback();
-            });
-        });
+        this.resetOptionsOutput();
+        callback();
+    });
+
+    this.AfterFeatures((features, callback) => {
+        callback();
     });
 };
diff --git a/features/support/http.js b/features/support/http.js
index 3ae2edc..71f6176 100644
--- a/features/support/http.js
+++ b/features/support/http.js
@@ -19,6 +19,9 @@ module.exports = function () {
         return paramString;
     };
 
+    // FIXME this needs to be simplified!
+    // - remove usage of node-timeout
+    // - replace with node's native timout mechanism
     this.sendRequest = (baseUri, parameters, callback) => {
         var limit = Timeout(this.TIMEOUT, { err: { statusCode: 408 } });
 
@@ -28,9 +31,9 @@ module.exports = function () {
 
             request(this.query, (err, res, body) => {
                 if (err && err.code === 'ECONNREFUSED') {
-                    throw new Error('*** osrm-routed is not running.');
+                    return cb(new Error('*** osrm-routed is not running.'));
                 } else if (err && err.statusCode === 408) {
-                    throw new Error();
+                    return cb(new Error());
                 }
 
                 return cb(err, res, body);
@@ -40,11 +43,10 @@ module.exports = function () {
         runRequest(limit((err, res, body) => {
             if (err) {
                 if (err.statusCode === 408)
-                    return callback(this.RoutedError('*** osrm-routed did not respond'));
+                    return callback(new Error('*** osrm-routed did not respond'));
                 else if (err.code === 'ECONNREFUSED')
-                    return callback(this.RoutedError('*** osrm-routed is not running'));
+                    return callback(new Error('*** osrm-routed is not running'));
             }
-            //console.log(body+"\n");
             return callback(err, res, body);
         }));
     };
diff --git a/features/support/launch.js b/features/support/launch.js
deleted file mode 100644
index ee335e3..0000000
--- a/features/support/launch.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var launchClasses = require('./launch_classes');
-
-module.exports = function () {
-    this._OSRMLoader = () => new (launchClasses._OSRMLoader.bind(launchClasses._OSRMLoader, this))();
-};
diff --git a/features/support/launch_classes.js b/features/support/launch_classes.js
deleted file mode 100644
index 2aace88..0000000
--- a/features/support/launch_classes.js
+++ /dev/null
@@ -1,164 +0,0 @@
-'use strict';
-
-var fs = require('fs');
-var spawn = require('child_process').spawn;
-var util = require('util');
-var net = require('net');
-var Timeout = require('node-timeout');
-
-var OSRMBaseLoader = class {
-    constructor (scope) {
-        this.scope = scope;
-    }
-
-    launch (callback) {
-        var limit = Timeout(this.scope.TIMEOUT, { err: this.scope.RoutedError('Launching osrm-routed timed out.') });
-
-        var runLaunch = (cb) => {
-            this.osrmUp(() => { this.waitForConnection(cb); });
-        };
-
-        runLaunch(limit((e) => { if (e) callback(e); else callback(); }));
-    }
-
-    shutdown (callback) {
-        var limit = Timeout(this.scope.TIMEOUT, { err: this.scope.RoutedError('Shutting down osrm-routed timed out.')});
-
-        var runShutdown = (cb) => {
-            this.osrmDown(cb);
-        };
-
-        runShutdown(limit((e) => { if (e) callback(e); else callback(); }));
-    }
-
-    osrmIsRunning () {
-        return !!this.scope.pid && this.child && !this.child.killed;
-    }
-
-    osrmDown (callback) {
-        if (this.scope.pid) {
-            process.kill(this.scope.pid, this.scope.TERMSIGNAL);
-            this.waitForShutdown(callback);
-            this.scope.pid = null;
-        } else callback(true);
-    }
-
-    waitForConnection (callback) {
-        var retryCount = 0;
-        var connectWithRetry = () => {
-            net.connect({ port: this.scope.OSRM_PORT, host: '127.0.0.1' })
-               .on('connect', () => { callback(); })
-               .on('error', () => {
-                   if (retryCount < 2) {
-                       retryCount++;
-                       setTimeout(connectWithRetry, 100);
-                   } else {
-                       callback(new Error('Could not connect to osrm-routed after three retires'));
-                   }
-               });
-        };
-
-        connectWithRetry();
-    }
-
-    waitForShutdown (callback) {
-        var check = () => {
-            if (!this.osrmIsRunning()) return callback();
-        };
-        setTimeout(check, 100);
-    }
-};
-
-var OSRMDirectLoader = class extends OSRMBaseLoader {
-    constructor (scope) {
-        super(scope);
-    }
-
-    load (inputFile, callback) {
-        this.inputFile = inputFile;
-        this.shutdown(() => {
-            this.launch(callback);
-        });
-    }
-
-    osrmUp (callback) {
-        if (this.scope.pid) return callback();
-        var writeToLog = (data) => {
-            fs.appendFile(this.scope.OSRM_ROUTED_LOG_FILE, data, (err) => { if (err) throw err; });
-        };
-
-        var child = spawn(util.format('%s/osrm-routed', this.scope.BIN_PATH), [this.inputFile, util.format('-p%d', this.scope.OSRM_PORT)]);
-        this.scope.pid = child.pid;
-        child.stdout.on('data', writeToLog);
-        child.stderr.on('data', writeToLog);
-
-        callback();
-    }
-};
-
-var OSRMDatastoreLoader = class extends OSRMBaseLoader {
-    constructor (scope) {
-        super(scope);
-    }
-
-    load (inputFile, callback) {
-        this.inputFile = inputFile;
-        this.loadData((err) => {
-            if (err) return callback(err);
-            if (!this.scope.pid) return this.launch(callback);
-            else callback();
-        });
-    }
-
-    loadData (callback) {
-        this.scope.runBin('osrm-datastore', this.inputFile, (err) => {
-            if (err) return callback(this.scope.LaunchError(this.exitCode, 'datastore', err));
-            callback();
-        });
-    }
-
-    osrmUp (callback) {
-        if (this.scope.pid) return callback();
-        var writeToLog = (data) => {
-            fs.appendFile(this.scope.OSRM_ROUTED_LOG_FILE, data, (err) => { if (err) throw err; });
-        };
-
-        var child = spawn(util.format('%s/osrm-routed', this.scope.BIN_PATH), ['--shared-memory=1', util.format('-p%d', this.scope.OSRM_PORT)]);
-        this.child = child;
-        this.scope.pid = child.pid;
-        child.stdout.on('data', writeToLog);
-        child.stderr.on('data', writeToLog);
-
-        callback();
-    }
-};
-
-module.exports = {
-    _OSRMLoader: class {
-        constructor (scope) {
-            this.scope = scope;
-            this.loader = null;
-        }
-
-        load (inputFile, callback) {
-            var method = this.scope.loadMethod;
-            if (method === 'datastore') {
-                this.loader = new OSRMDatastoreLoader(this.scope);
-                this.loader.load(inputFile, callback);
-            } else if (method === 'directly') {
-                this.loader = new OSRMDirectLoader(this.scope);
-                this.loader.load(inputFile, callback);
-            } else {
-                callback(new Error('*** Unknown load method ' + method));
-            }
-        }
-
-        shutdown (callback) {
-            this.loader.shutdown(callback);
-        }
-
-        up () {
-            return this.loader ? this.loader.osrmIsRunning() : false;
-        }
-    }
-};
diff --git a/features/support/log.js b/features/support/log.js
deleted file mode 100644
index c428cb9..0000000
--- a/features/support/log.js
+++ /dev/null
@@ -1,90 +0,0 @@
-var fs = require('fs');
-
-module.exports = function () {
-    this.clearLogFiles = (callback) => {
-        // emptying existing files, rather than deleting and writing new ones makes it
-        // easier to use tail -f from the command line
-        fs.writeFile(this.OSRM_ROUTED_LOG_FILE, '', err => {
-            if (err) throw err;
-            fs.writeFile(this.PREPROCESS_LOG_FILE, '', err => {
-                if (err) throw err;
-                fs.writeFile(this.LOG_FILE, '', err => {
-                    if (err) throw err;
-                    callback();
-                });
-            });
-        });
-    };
-
-    var log = this.log = (s, type) => {
-        s = s || '';
-        type = type || null;
-        var file = type === 'preprocess' ? this.PREPROCESS_LOG_FILE : this.LOG_FILE;
-        fs.appendFile(file, s + '\n', err => {
-            if (err) throw err;
-        });
-    };
-
-    this.logScenarioFailInfo = () => {
-        if (this.hasLoggedScenarioInfo) return;
-
-        log('=========================================');
-        log('Failed scenario: ' + this.scenarioTitle);
-        log('Time: ' + this.scenarioTime);
-        log('Fingerprint osm stage: ' + this.osmData.fingerprintOSM);
-        log('Fingerprint extract stage: ' + this.fingerprintExtract);
-        log('Fingerprint contract stage: ' + this.fingerprintContract);
-        log('Fingerprint route stage: ' + this.fingerprintRoute);
-        log('Profile: ' + this.profile);
-        log();
-        log('```xml');               // so output can be posted directly to github comment fields
-        log(this.osmData.str.trim());
-        log('```');
-        log();
-        log();
-
-        this.hasLoggedScenarioInfo = true;
-    };
-
-    this.logFail = (expected, got, attempts) => {
-        this.logScenarioFailInfo();
-        log('== ');
-        log('Expected: ' + JSON.stringify(expected));
-        log('Got:      ' + JSON.stringify(got));
-        log();
-        ['route','forw','backw'].forEach((direction) => {
-            if (attempts[direction]) {
-                log('Direction: ' + direction);
-                log('Query: ' + attempts[direction].query);
-                log('Response: ' + attempts[direction].response.body);
-                log();
-            }
-        });
-    };
-
-    this.logPreprocessInfo = () => {
-        if (this.hasLoggedPreprocessInfo) return;
-        log('=========================================', 'preprocess');
-        log('Preprocessing data for scenario: ' + this.scenarioTitle, 'preprocess');
-        log('Time: ' + this.scenarioTime, 'preprocess');
-        log('', 'preprocess');
-        log('== OSM data:', 'preprocess');
-        log('```xml', 'preprocess');            // so output can be posted directly to github comment fields
-        log(this.osmData.str, 'preprocess');
-        log('```', 'preprocess');
-        log('', 'preprocess');
-        log('== Profile:', 'preprocess');
-        log(this.profile, 'preprocess');
-        log('', 'preprocess');
-        this.hasLoggedPreprocessInfo = true;
-    };
-
-    this.logPreprocess = (str) => {
-        this.logPreprocessInfo();
-        log(str, 'preprocess');
-    };
-
-    this.logPreprocessDone = () => {
-        log('Done with preprocessing at ' + new Date(), 'preprocess');
-    };
-};
diff --git a/features/support/route.js b/features/support/route.js
index a453ca4..32401b7 100644
--- a/features/support/route.js
+++ b/features/support/route.js
@@ -1,7 +1,8 @@
 'use strict';
 
-var Timeout = require('node-timeout');
-var request = require('request');
+const Timeout = require('node-timeout');
+const request = require('request');
+const ensureDecimal = require('../lib/utils').ensureDecimal;
 
 module.exports = function () {
     this.requestPath = (service, params, callback) => {
@@ -42,7 +43,7 @@ module.exports = function () {
     };
 
     var encodeWaypoints = (waypoints) => {
-        return waypoints.map(w => [w.lon, w.lat].map(this.ensureDecimal).join(','));
+        return waypoints.map(w => [w.lon, w.lat].map(ensureDecimal).join(','));
     };
 
     this.requestRoute = (waypoints, bearings, userParams, callback) => {
@@ -131,7 +132,7 @@ module.exports = function () {
 
     this.summary = (instructions) => {
         if (instructions) {
-            return instructions.legs.map(l => l.summary).join(',');
+            return instructions.legs.map(l => l.summary).join(';');
         }
     };
 
@@ -139,6 +140,10 @@ module.exports = function () {
         return this.extractInstructionList(instructions, s => s.name);
     };
 
+    this.refList = (instructions) => {
+        return this.extractInstructionList(instructions, s => s.ref || '');
+    };
+
     this.pronunciationList = (instructions) => {
         return this.extractInstructionList(instructions, s => s.pronunciation || '');
     };
@@ -152,15 +157,15 @@ module.exports = function () {
     };
 
     this.annotationList = (instructions) => {
-        function zip(list_1, list_2)
+        function zip(list_1, list_2, list_3)
         {
-            let pairs = [];
+            let tuples = [];
             for (let i = 0; i <  list_1.length; ++i) {
-                pairs.push([list_1[i], list_2[i]]);
+                tuples.push([list_1[i], list_2[i], list_3[i]]);
             }
-            return pairs;
+            return tuples;
         }
-        return instructions.legs.map(l => {return zip(l.annotation.duration, l.annotation.distance).map(p => { return p.join(':'); }).join(','); }).join(',');
+        return instructions.legs.map(l => {return zip(l.annotation.duration, l.annotation.distance, l.annotation.datasources).map(p => { return p.join(':'); }).join(','); }).join(',');
     };
 
     this.OSMIDList = (instructions) => {
diff --git a/features/support/run.js b/features/support/run.js
index 35561d8..f6be197 100644
--- a/features/support/run.js
+++ b/features/support/run.js
@@ -1,40 +1,52 @@
-var fs = require('fs');
-var util = require('util');
-var exec = require('child_process').exec;
+'use strict';
+
+const fs = require('fs');
+const util = require('util');
+const child_process = require('child_process');
 
 module.exports = function () {
-    this.runBin = (bin, options, callback) => {
-        var opts = options.slice();
+    // replaces placeholders for in user supplied commands
+    this.expandOptions = (options) => {
+        let opts = options.slice();
+        let table = {
+            '{osm_file}': this.inputCacheFile,
+            '{processed_file}': this.processedCacheFile,
+            '{profile_file}': this.profileFile,
+            '{rastersource_file}': this.rasterCacheFile,
+            '{speeds_file}': this.speedsCacheFile,
+            '{penalties_file}': this.penaltiesCacheFile
+        };
 
-        if (opts.match('{osm_base}')) {
-            if (!this.osmData.osmFile) throw new Error('*** {osm_base} is missing');
-            opts = opts.replace('{osm_base}', this.osmData.osmFile);
+        for (let k in table) {
+            opts = opts.replace(k, table[k]);
         }
 
-        if (opts.match('{extracted_base}')) {
-            if (!this.osmData.extractedFile) throw new Error('*** {extracted_base} is missing');
-            opts = opts.replace('{extracted_base}', this.osmData.extractedFile);
-        }
+        return opts;
+    };
 
-        if (opts.match('{contracted_base}')) {
-            if (!this.osmData.contractedFile) throw new Error('*** {contracted_base} is missing');
-            opts = opts.replace('{contracted_base}', this.osmData.contractedFile);
+    this.setupOutputLog = (process, log) => {
+        if (process.logFunc) {
+            process.stdout.removeListener('data', process.logFunc);
+            process.stderr.removeListener('data', process.logFunc);
         }
 
-        if (opts.match('{profile}')) {
-            opts = opts.replace('{profile}', [this.PROFILES_PATH, this.profile + '.lua'].join('/'));
-        }
+        process.logFunc = (message) => { log.write(message); };
+        process.stdout.on('data', process.logFunc);
+        process.stderr.on('data', process.logFunc);
+    };
 
-        var cmd = util.format('%s%s/%s%s%s %s 2>%s', this.QQ, this.BIN_PATH, bin, this.EXE, this.QQ, opts, this.ERROR_LOG_FILE);
-        process.chdir(this.TEST_FOLDER);
-        exec(cmd, (err, stdout, stderr) => {
-            this.stdout = stdout.toString();
-            fs.readFile(this.ERROR_LOG_FILE, (e, data) => {
-                this.stderr = data ? data.toString() : '';
-                this.exitCode = err && err.code || 0;
-                process.chdir('../');
-                callback(err, stdout, stderr);
-            });
-        });
+    this.runBin = (bin, options, env, callback) => {
+        let cmd = util.format('%s%s/%s%s%s', this.QQ, this.BIN_PATH, bin, this.EXE, this.QQ);
+        let opts = options.split(' ').filter((x) => { return x && x.length > 0; });
+        let log = fs.createWriteStream(this.scenarioLogFile, {'flags': 'a'});
+        log.write(util.format('*** running %s %s\n', cmd, options));
+        // we need to set a large maxbuffer here because we have long running processes like osrm-routed
+        // with lots of log output
+        let child = child_process.execFile(cmd, opts, {maxBuffer: 1024 * 1024 * 1000, env: env}, callback);
+        child.on('exit', function(code) {
+            log.write(util.format('*** %s exited with code %d\n', bin, code));
+        }.bind(this));
+        this.setupOutputLog(child, log);
+        return child;
     };
 };
diff --git a/features/support/shared_steps.js b/features/support/shared_steps.js
index 6db5400..0252cd4 100644
--- a/features/support/shared_steps.js
+++ b/features/support/shared_steps.js
@@ -24,7 +24,8 @@ module.exports = function () {
     };
 
     this.WhenIRouteIShouldGet = (table, callback) => {
-        this.reprocessAndLoadData(() => {
+        this.reprocessAndLoadData((e) => {
+            if (e) return callback(e);
             var headers = new Set(table.raw()[0]);
 
             var requestRow = (row, ri, cb) => {
@@ -33,7 +34,7 @@ module.exports = function () {
                 var afterRequest = (err, res, body) => {
                     if (err) return cb(err);
                     if (body && body.length) {
-                        let destinations, pronunciations, instructions, bearings, turns, modes, times,
+                        let destinations, pronunciations, instructions, refs, bearings, turns, modes, times,
                             distances, summary, intersections, lanes;
 
                         let json = JSON.parse(body);
@@ -43,6 +44,7 @@ module.exports = function () {
                         if (hasRoute) {
                             instructions = this.wayList(json.routes[0]);
                             pronunciations = this.pronunciationList(json.routes[0]);
+                            refs = this.refList(json.routes[0]);
                             destinations = this.destinationsList(json.routes[0]);
                             bearings = this.bearingList(json.routes[0]);
                             turns = this.turnList(json.routes[0]);
@@ -91,7 +93,7 @@ module.exports = function () {
                             if (headers.has('distance')) {
                                 if (row.distance.length) {
                                     if (!row.distance.match(/\d+m/))
-                                        throw new Error('*** Distance must be specified in meters. (ex: 250m)');
+                                        return cb(new Error('*** Distance must be specified in meters. (ex: 250m)'));
                                     got.distance = instructions ? util.format('%dm', distance) : '';
                                 } else {
                                     got.distance = '';
@@ -100,7 +102,7 @@ module.exports = function () {
 
                             if (headers.has('time')) {
                                 if (!row.time.match(/\d+s/))
-                                    throw new Error('*** Time must be specied in seconds. (ex: 60s)');
+                                    return cb(new Error('*** Time must be specied in seconds. (ex: 60s)'));
                                 got.time = instructions ? util.format('%ds', time) : '';
                             }
 
@@ -111,7 +113,7 @@ module.exports = function () {
                             if (headers.has('speed')) {
                                 if (row.speed !== '' && instructions) {
                                     if (!row.speed.match(/\d+ km\/h/))
-                                        throw new Error('*** Speed must be specied in km/h. (ex: 50 km/h)');
+                                        cb(new Error('*** Speed must be specied in km/h. (ex: 50 km/h)'));
                                     var speed = time > 0 ? Math.round(3.6*distance/time) : null;
                                     got.speed = util.format('%d km/h', speed);
                                 } else {
@@ -127,6 +129,7 @@ module.exports = function () {
                                 if (headers.has(key)) got[key] = instructions ? value : '';
                             };
 
+                            putValue('ref', refs);
                             putValue('bearing', bearings);
                             putValue('turns', turns);
                             putValue('modes', modes);
@@ -136,20 +139,12 @@ module.exports = function () {
                             putValue('destinations', destinations);
                         }
 
-                        var ok = true;
-
                         for (var key in row) {
                             if (this.FuzzyMatch.match(got[key], row[key])) {
                                 got[key] = row[key];
-                            } else {
-                                ok = false;
                             }
                         }
 
-                        if (!ok) {
-                            this.logFail(row, got, { route: { query: this.query, response: res }});
-                        }
-
                         cb(null, got);
                     } else {
                         cb(new Error('request failed to return valid body'));
@@ -186,11 +181,11 @@ module.exports = function () {
 
                     if (row.from && row.to) {
                         var fromNode = this.findNodeByName(row.from);
-                        if (!fromNode) throw new Error(util.format('*** unknown from-node "%s"'), row.from);
+                        if (!fromNode) return cb(new Error(util.format('*** unknown from-node "%s"'), row.from));
                         waypoints.push(fromNode);
 
                         var toNode = this.findNodeByName(row.to);
-                        if (!toNode) throw new Error(util.format('*** unknown to-node "%s"'), row.to);
+                        if (!toNode) return cb(new Error(util.format('*** unknown to-node "%s"'), row.to));
                         waypoints.push(toNode);
 
                         got.from = row.from;
@@ -199,13 +194,13 @@ module.exports = function () {
                     } else if (row.waypoints) {
                         row.waypoints.split(',').forEach((n) => {
                             var node = this.findNodeByName(n.trim());
-                            if (!node) throw new Error('*** unknown waypoint node "%s"', n.trim());
+                            if (!node) return cb(new Error('*** unknown waypoint node "%s"', n.trim()));
                             waypoints.push(node);
                         });
                         got.waypoints = row.waypoints;
                         this.requestRoute(waypoints, bearings, params, afterRequest);
                     } else {
-                        throw new Error('*** no waypoints');
+                        return cb(new Error('*** no waypoints'));
                     }
                 }
             };
diff --git a/features/support/table_patch.js b/features/support/table_patch.js
deleted file mode 100644
index 16ffebb..0000000
--- a/features/support/table_patch.js
+++ /dev/null
@@ -1,11 +0,0 @@
-var DifferentError = require('./exception_classes').TableDiffError;
-
-module.exports = function () {
-    this.diffTables = (expected, actual, options, callback) => {
-        // this is a temp workaround while waiting for https://github.com/cucumber/cucumber-js/issues/534
-
-        var error = new DifferentError(expected, actual);
-
-        return callback(error.string);
-    };
-};
diff --git a/features/testbot/bad.feature b/features/testbot/bad.feature
index 18ee5f8..aeddc95 100644
--- a/features/testbot/bad.feature
+++ b/features/testbot/bad.feature
@@ -11,8 +11,8 @@ Feature: Handle bad data in a graceful manner
         Given the ways
             | nodes |
 
-        When the data has been contracted
-        Then "osrm-extract" should return code 1
+        When I try to run "osrm-extract {osm_file} --profile {profile_file}"
+        Then it should exit with an error
 
     Scenario: Only dead-end oneways
         Given the node map
diff --git a/features/testbot/bearing_param.feature b/features/testbot/bearing_param.feature
index 8650398..7693cdb 100644
--- a/features/testbot/bearing_param.feature
+++ b/features/testbot/bearing_param.feature
@@ -104,12 +104,12 @@ Feature: Bearing parameter
             | ha    | yes    | ring |
 
         When I route I should get
-            | from | to | bearings | route                       | bearing                                   |
-            | 0    | q  | 0 90     | ia,ring,ring,ring,ring,ring | 0->0,0->90,180->270,270->0,0->90,90->0    |
-            | 0    | a  | 45 90    | jb,ring,ring,ring,ring,ring | 0->45,45->180,180->270,270->0,0->90,90->0 |
-            | 0    | q  | 90 90    | kc,ring,ring,ring,ring      | 0->90,90->180,270->0,0->90,90->0          |
-            | 0    | a  | 135 90   | ld,ring,ring,ring,ring      | 0->135,135->270,270->0,0->90,90->0        |
-            | 0    | a  | 180 90   | me,ring,ring,ring           | 0->180,180->270,0->90,90->0               |
-            | 0    | a  | 225 90   | nf,ring,ring,ring           | 0->225,225->0,0->90,90->0                 |
-            | 0    | a  | 270 90   | og,ring,ring                | 0->270,270->0,90->0                       |
-            | 0    | a  | 315 90   | ph,ring,ring                | 0->315,315->90,90->0                      |
+            | from | to | bearings | route        | bearing               |
+            | 0    | q  | 0 90     | ia,ring,ring | 0->0,0->90,90->0      |
+            | 0    | a  | 45 90    | jb,ring,ring | 0->45,45->180,90->0   |
+            | 0    | q  | 90 90    | kc,ring,ring | 0->90,90->180,90->0   |
+            | 0    | a  | 135 90   | ld,ring,ring | 0->135,135->270,90->0 |
+            | 0    | a  | 180 90   | me,ring,ring | 0->180,180->270,90->0 |
+            | 0    | a  | 225 90   | nf,ring,ring | 0->225,225->0,90->0   |
+            | 0    | a  | 270 90   | og,ring,ring | 0->270,270->0,90->0   |
+            | 0    | a  | 315 90   | ph,ring,ring | 0->315,315->90,90->0  |
diff --git a/features/testbot/bugs.feature b/features/testbot/bugs.feature
deleted file mode 100644
index 26be28a..0000000
--- a/features/testbot/bugs.feature
+++ /dev/null
@@ -1,5 +0,0 @@
- at routing @testbot @bug
-Feature: Known bugs
-
-    Background:
-        Given the profile "testbot"
diff --git a/features/testbot/matching.feature b/features/testbot/matching.feature
index 2de39f5..ead4b6a 100644
--- a/features/testbot/matching.feature
+++ b/features/testbot/matching.feature
@@ -4,6 +4,7 @@ Feature: Basic Map Matching
     Background:
         Given the profile "testbot"
         Given a grid size of 10 meters
+        Given the extract extra arguments "--generate-edge-lookup"
 
     Scenario: Testbot - Map matching with outlier that has no candidate
         Given a grid size of 100 meters
@@ -118,10 +119,17 @@ Feature: Basic Map Matching
             | abcdegh  | no     |
             | ci       | no     |
 
+        And the speed file
+        """
+        1,2,36
+        """
+
+        And the contract extra arguments "--segment-speed-file {speeds_file}"
+
         When I match I should get
-            | trace | matchings | annotation                                                                     |
-            | abeh  | abcedgh   | 1:9.897633,0:0,1:10.008842,1:10.008842,1:10.008842,0:0,2:20.017685,1:10.008842 |
-            | abci  | abc,ci    | 1:9.897633,0:0,1:10.008842,0:0.111209,1:10.010367                              |
+            | trace | matchings | annotation                                                                                     |
+            | abeh  | abcedgh   | 1:9.897633:1,0:0:0,1:10.008842:0,1:10.008842:0,1:10.008842:0,0:0:0,2:20.017685:0,1:10.008842:0 |
+            | abci  | abc,ci    | 1:9.897633:1,0:0:0,1:10.008842:0,0:0.111209:0,1:10.010367:0                                    |
 
         # The following is the same as the above, but separated for readability (line length)
         When I match I should get
diff --git a/features/testbot/side_bias.feature b/features/testbot/side_bias.feature
new file mode 100644
index 0000000..fe24117
--- /dev/null
+++ b/features/testbot/side_bias.feature
@@ -0,0 +1,83 @@
+ at routing @testbot @sidebias
+Feature: Testbot - side bias
+
+    Scenario: Left hand bias
+        Given the profile "lhs"
+        And the node map
+            | a |   | b |   | c |
+            |   |   |   |   |   |
+            |   |   | d |   |   |
+        And the ways
+            | nodes |
+            | ab    |
+            | bc    |
+            | bd    |
+
+        When I route I should get
+            | from | to | route    | time       |
+            | d    | a  | bd,ab,ab | 82s +-1    |
+            | d    | c  | bd,bc,bc | 100s +-1   |
+
+    Scenario: Right hand bias
+        Given the profile "rhs"
+        And the node map
+            | a |   | b |   | c |
+            |   |   |   |   |   |
+            |   |   | d |   |   |
+        And the ways
+            | nodes |
+            | ab    |
+            | bc    |
+            | bd    |
+
+        When I route I should get
+            | from | to | route    | time       |
+            | d    | a  | bd,ab,ab | 100s +-1   |
+            | d    | c  | bd,bc,bc | 82s +-1    |
+
+    Scenario: Roundabout exit counting for left sided driving
+        Given the profile "lhs"
+        And a grid size of 10 meters
+        And the node map
+            |   |   | a |   |   |
+            |   |   | b |   |   |
+            | h | g |   | c | d |
+            |   |   | e |   |   |
+            |   |   | f |   |   |
+       And the ways
+            | nodes  | junction   |
+            | ab     |            |
+            | cd     |            |
+            | ef     |            |
+            | gh     |            |
+            | bcegb  | roundabout |
+
+       When I route I should get
+           | waypoints | route    | turns                                         |
+           | a,d       | ab,cd,cd | depart,roundabout turn left exit-1,arrive     |
+           | a,f       | ab,ef,ef | depart,roundabout turn straight exit-2,arrive |
+           | a,h       | ab,gh,gh | depart,roundabout turn right exit-3,arrive    |
+
+     Scenario: Mixed Entry and Exit
+        Given the profile "lhs"
+        And a grid size of 10 meters
+        And the node map
+           |   | c |   | a |   |
+           | j |   | b |   | f |
+           |   | k |   | e |   |
+           | l |   | h |   | d |
+           |   | g |   | i |   |
+
+        And the ways
+           | nodes | junction   | oneway |
+           | cba   |            | yes    |
+           | fed   |            | yes    |
+           | ihg   |            | yes    |
+           | lkj   |            | yes    |
+           | behkb | roundabout | yes    |
+
+        When I route I should get
+           | waypoints | route       | turns                           |
+           | c,a       | cba,cba,cba | depart,roundabout-exit-1,arrive |
+           | l,a       | lkj,cba,cba | depart,roundabout-exit-2,arrive |
+           | i,a       | ihg,cba,cba | depart,roundabout-exit-3,arrive |
diff --git a/features/testbot/summary.feature b/features/testbot/summary.feature
index 563230a..83c0568 100644
--- a/features/testbot/summary.feature
+++ b/features/testbot/summary.feature
@@ -53,9 +53,9 @@ Feature: Basic Routing
             | ab    |
 
         When I route I should get
-            | from | to | route | summary  |
-            | a    | b  | ab,ab | ab       |
-            | b    | a  | ab,ab | ab       |
+            | from | to | route | summary |
+            | a    | b  | ab,ab | ab      |
+            | b    | a  | ab,ab | ab      |
 
     @repeated
     Scenario: Check handling empty values
diff --git a/features/testbot/traffic_turn_penalties.feature b/features/testbot/traffic_turn_penalties.feature
index 6f6ab58..7ee9424 100644
--- a/features/testbot/traffic_turn_penalties.feature
+++ b/features/testbot/traffic_turn_penalties.feature
@@ -23,10 +23,10 @@ Feature: Traffic - turn penalties applied to turn onto which a phantom node snap
     Scenario: Weighting based on turn penalty file, with an extreme negative value -- clamps and does not fail
         Given the turn penalty file
             """
-            1,2,5,0
+            1,2,5,0,comment
             3,4,7,-20
             """
-        And the contract extra arguments "--turn-penalty-file penalties.csv"
+        And the contract extra arguments "--turn-penalty-file {penalties_file}"
         When I route I should get
             | from | to | route    | speed   | time    |
             | a    | e  | ab,be,be | 36 km/h | 40s +-1 |
diff --git a/features/testbot/utf.feature b/features/testbot/utf.feature
index b537163..7bf2422 100644
--- a/features/testbot/utf.feature
+++ b/features/testbot/utf.feature
@@ -19,3 +19,18 @@ Feature: Handling of UTF characters
             | a    | b  | Scandinavian København,Scandinavian København |
             | b    | c  | Japanese 東京,Japanese 東京                    |
             | c    | d  | Cyrillic Москва,Cyrillic Москва               |
+
+
+    @todo
+    Scenario: Up to 255 Unicode Code Points (255 x Panda Code Point)
+        Given the node map
+            | a | b | c |
+
+        And the ways
+            | nodes | name | highway |
+            | ab    | ab   | primary |
+            | bc    | 🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼 | primary |
+
+        When I route 100 times I should get
+            | from | to | route |
+            | a    | c  | ab,🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼,🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼🐼 [...]
diff --git a/features/testbot/via.feature b/features/testbot/via.feature
index d3092cb..62699b8 100644
--- a/features/testbot/via.feature
+++ b/features/testbot/via.feature
@@ -173,7 +173,7 @@ Feature: Via points
             | c,d,a     | abc,bd,bd,bd,abc,abc |
 
     # See issue #2349
-    @bug
+    @todo
     Scenario: Via point at a dead end with oneway
         Given the node map
             | a | b | c |
diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..17b26b6
--- /dev/null
+++ b/fuzz/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Fuzz testing using LLVM's libFuzzer.
+#
+# See:
+#  - http://llvm.org/docs/LibFuzzer.html
+#  - http://llvm.org/releases/3.8.0/docs/LibFuzzer.html
+#
+# TODO(daniel-j-h):
+#  - make more user friendly, at the moment we require you to build and install libFuzzer.a
+#  - pick up LLVM_ROOT
+#  - build libFuzzer on the fly
+#
+# clang++ -std=c++11 -stdlib=libc++ -c -g -O2 ~/llvm/lib/Fuzzer/*.cpp -I~/llvm/lib/Fuzzer
+# ar ruv libFuzzer.a Fuzzer*.o
+
+if (ENABLE_FUZZING)
+
+  include(ProcessorCount)
+  ProcessorCount(nproc)
+
+  macro(add_fuzz_target binary)
+    add_executable(${binary} ${binary}.cc $<TARGET_OBJECTS:UTIL> $<TARGET_OBJECTS:SERVER>)
+    target_link_libraries(${binary} Fuzzer osrm)
+    target_include_directories(${binary} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+
+    add_custom_target(fuzz-${binary}
+	    DEPENDS ${binary}
+	    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+	    COMMAND ${CMAKE_COMMAND} -E make_directory "corpus/${binary}"
+	    COMMAND ${binary} -use_traces=1 -jobs=${nproc} -workers=${nproc} -max_len=4096 "corpus/${binary}"
+	    COMMENT "Fuzzing ${binary}" VERBATIM)
+  endmacro ()
+
+
+  set(ServerTargets
+	  "match_parameters"
+	  "nearest_parameters"
+	  "route_parameters"
+	  "table_parameters"
+	  "tile_parameters"
+	  "trip_parameters"
+	  "url_parser"
+	  "request_parser")
+
+  foreach (target ${ServerTargets})
+	  add_fuzz_target(${target})
+  endforeach ()
+
+
+  set(UtilTargets
+	  "escape_json"
+	  "uri_decode")
+
+  foreach (target ${UtilTargets})
+	  add_fuzz_target(${target})
+  endforeach ()
+
+
+endif ()
diff --git a/fuzz/escape_json.cc b/fuzz/escape_json.cc
new file mode 100644
index 0000000..5f5fdc8
--- /dev/null
+++ b/fuzz/escape_json.cc
@@ -0,0 +1,18 @@
+#include "util/string_util.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::util::escape_JSON;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    const std::string in(reinterpret_cast<const char *>(data), size);
+
+    const auto escaped = escape_JSON(in);
+    escape(escaped.data());
+
+    return 0;
+}
diff --git a/fuzz/match_parameters.cc b/fuzz/match_parameters.cc
new file mode 100644
index 0000000..19da8b1
--- /dev/null
+++ b/fuzz/match_parameters.cc
@@ -0,0 +1,23 @@
+#include "engine/api/match_parameters.hpp"
+#include "server/api/parameters_parser.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::api::parseParameters;
+using osrm::engine::api::MatchParameters;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    const auto last = end(in);
+
+    const auto param = parseParameters<MatchParameters>(first, last);
+    escape(&param);
+
+    return 0;
+}
diff --git a/fuzz/nearest_parameters.cc b/fuzz/nearest_parameters.cc
new file mode 100644
index 0000000..71c95bf
--- /dev/null
+++ b/fuzz/nearest_parameters.cc
@@ -0,0 +1,23 @@
+#include "engine/api/nearest_parameters.hpp"
+#include "server/api/parameters_parser.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::api::parseParameters;
+using osrm::engine::api::NearestParameters;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    const auto last = end(in);
+
+    const auto param = parseParameters<NearestParameters>(first, last);
+    escape(&param);
+
+    return 0;
+}
diff --git a/fuzz/request_parser.cc b/fuzz/request_parser.cc
new file mode 100644
index 0000000..e292ab2
--- /dev/null
+++ b/fuzz/request_parser.cc
@@ -0,0 +1,28 @@
+#include "server/request_parser.hpp"
+#include "server/http/request.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::RequestParser;
+using osrm::server::http::request;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    auto last = end(in);
+
+    RequestParser parser;
+    request req;
+
+    // &(*it) is needed to go from iterator to underlying item to pointer to underlying item
+    parser.parse(req, &(*first), &(*last));
+
+    escape(&req);
+
+    return 0;
+}
diff --git a/fuzz/route_parameters.cc b/fuzz/route_parameters.cc
new file mode 100644
index 0000000..b3a80b0
--- /dev/null
+++ b/fuzz/route_parameters.cc
@@ -0,0 +1,23 @@
+#include "engine/api/route_parameters.hpp"
+#include "server/api/parameters_parser.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::api::parseParameters;
+using osrm::engine::api::RouteParameters;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    const auto last = end(in);
+
+    const auto param = parseParameters<RouteParameters>(first, last);
+    escape(&param);
+
+    return 0;
+}
diff --git a/fuzz/table_parameters.cc b/fuzz/table_parameters.cc
new file mode 100644
index 0000000..851f7a4
--- /dev/null
+++ b/fuzz/table_parameters.cc
@@ -0,0 +1,23 @@
+#include "engine/api/table_parameters.hpp"
+#include "server/api/parameters_parser.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::api::parseParameters;
+using osrm::engine::api::TableParameters;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    const auto last = end(in);
+
+    const auto param = parseParameters<TableParameters>(first, last);
+    escape(&param);
+
+    return 0;
+}
diff --git a/fuzz/tile_parameters.cc b/fuzz/tile_parameters.cc
new file mode 100644
index 0000000..405544e
--- /dev/null
+++ b/fuzz/tile_parameters.cc
@@ -0,0 +1,23 @@
+#include "engine/api/tile_parameters.hpp"
+#include "server/api/parameters_parser.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::api::parseParameters;
+using osrm::engine::api::TileParameters;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    const auto last = end(in);
+
+    const auto param = parseParameters<TileParameters>(first, last);
+    escape(&param);
+
+    return 0;
+}
diff --git a/fuzz/trip_parameters.cc b/fuzz/trip_parameters.cc
new file mode 100644
index 0000000..501d65d
--- /dev/null
+++ b/fuzz/trip_parameters.cc
@@ -0,0 +1,23 @@
+#include "engine/api/trip_parameters.hpp"
+#include "server/api/parameters_parser.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::api::parseParameters;
+using osrm::engine::api::TripParameters;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    const auto last = end(in);
+
+    const auto param = parseParameters<TripParameters>(first, last);
+    escape(&param);
+
+    return 0;
+}
diff --git a/fuzz/uri_decode.cc b/fuzz/uri_decode.cc
new file mode 100644
index 0000000..a25bb4e
--- /dev/null
+++ b/fuzz/uri_decode.cc
@@ -0,0 +1,21 @@
+#include "server/api/url_parser.hpp"
+#include "util/string_util.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::util::URIDecode;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    const std::string in(reinterpret_cast<const char *>(data), size);
+    std::string out;
+
+    (void)URIDecode(in, out);
+
+    escape(out.data());
+
+    return 0;
+}
diff --git a/fuzz/url_parser.cc b/fuzz/url_parser.cc
new file mode 100644
index 0000000..5a4b98f
--- /dev/null
+++ b/fuzz/url_parser.cc
@@ -0,0 +1,21 @@
+#include "server/api/url_parser.hpp"
+
+#include "util.hpp"
+
+#include <iterator>
+#include <string>
+
+using osrm::server::api::parseURL;
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
+{
+    std::string in(reinterpret_cast<const char *>(data), size);
+
+    auto first = begin(in);
+    const auto last = end(in);
+
+    const auto param = parseURL(first, last);
+    escape(&param);
+
+    return 0;
+}
diff --git a/fuzz/util.hpp b/fuzz/util.hpp
new file mode 100644
index 0000000..490957a
--- /dev/null
+++ b/fuzz/util.hpp
@@ -0,0 +1,16 @@
+#ifndef OSRM_FUZZ_UTIL_HPP
+#define OSRM_FUZZ_UTIL_HPP
+
+#include <type_traits>
+
+// Fakes observable side effects the compiler can not optimize away
+template <typename T> inline void escape(T p)
+{
+    static_assert(std::is_pointer<T>::value, "");
+    asm volatile("" : : "g"((void *)p) : "memory");
+}
+
+// Possibly reads and writes all the memory in your system
+inline void clobber() { asm volatile("" : : : "memory"); }
+
+#endif
diff --git a/include/engine/api/route_api.hpp b/include/engine/api/route_api.hpp
index 25e9f7a..0ef32a1 100644
--- a/include/engine/api/route_api.hpp
+++ b/include/engine/api/route_api.hpp
@@ -150,6 +150,7 @@ class RouteAPI : public BaseAPI
                                                               leg_geometry,
                                                               phantoms.source_phantom,
                                                               phantoms.target_phantom);
+                leg.steps = guidance::removeLanesFromRoundabouts(std::move(leg.steps));
                 leg.steps = guidance::anticipateLaneChange(std::move(leg.steps));
                 leg.steps = guidance::collapseUseLane(std::move(leg.steps));
                 leg_geometry = guidance::resyncGeometry(std::move(leg_geometry), leg.steps);
@@ -206,19 +207,22 @@ class RouteAPI : public BaseAPI
                 util::json::Array durations;
                 util::json::Array distances;
                 util::json::Array nodes;
+                util::json::Array datasources;
                 auto &leg_geometry = leg_geometries[idx];
 
                 durations.values.reserve(leg_geometry.annotations.size());
                 distances.values.reserve(leg_geometry.annotations.size());
                 nodes.values.reserve(leg_geometry.osm_node_ids.size());
-
-                std::for_each(
-                    leg_geometry.annotations.begin(),
-                    leg_geometry.annotations.end(),
-                    [this, &durations, &distances](const guidance::LegGeometry::Annotation &step) {
-                        durations.values.push_back(step.duration);
-                        distances.values.push_back(step.distance);
-                    });
+                datasources.values.reserve(leg_geometry.annotations.size());
+
+                std::for_each(leg_geometry.annotations.begin(),
+                              leg_geometry.annotations.end(),
+                              [this, &durations, &distances, &datasources](
+                                  const guidance::LegGeometry::Annotation &step) {
+                                  durations.values.push_back(step.duration);
+                                  distances.values.push_back(step.distance);
+                                  datasources.values.push_back(step.datasource);
+                              });
                 std::for_each(leg_geometry.osm_node_ids.begin(),
                               leg_geometry.osm_node_ids.end(),
                               [this, &nodes](const OSMNodeID &node_id) {
@@ -228,6 +232,7 @@ class RouteAPI : public BaseAPI
                 annotation.values["distance"] = std::move(distances);
                 annotation.values["duration"] = std::move(durations);
                 annotation.values["nodes"] = std::move(nodes);
+                annotation.values["datasources"] = std::move(datasources);
                 annotations.push_back(std::move(annotation));
             }
         }
diff --git a/include/engine/api/route_parameters.hpp b/include/engine/api/route_parameters.hpp
index bca6e4d..0c2d1e3 100644
--- a/include/engine/api/route_parameters.hpp
+++ b/include/engine/api/route_parameters.hpp
@@ -72,6 +72,21 @@ struct RouteParameters : public BaseParameters
     template <typename... Args>
     RouteParameters(const bool steps_,
                     const bool alternatives_,
+                    const GeometriesType geometries_,
+                    const OverviewType overview_,
+                    const boost::optional<bool> continue_straight_,
+                    Args... args_)
+        : BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
+          annotations{false}, geometries{geometries_}, overview{overview_},
+          continue_straight{continue_straight_}
+    // Once we perfectly-forward `args` (see #2990) this constructor can delegate to the one below.
+    {
+    }
+
+    // RouteParameters constructor adding the `annotations` setting in a API-compatible way.
+    template <typename... Args>
+    RouteParameters(const bool steps_,
+                    const bool alternatives_,
                     const bool annotations_,
                     const GeometriesType geometries_,
                     const OverviewType overview_,
diff --git a/include/engine/datafacade/datafacade_base.hpp b/include/engine/datafacade/datafacade_base.hpp
index 81cdde1..e837e07 100644
--- a/include/engine/datafacade/datafacade_base.hpp
+++ b/include/engine/datafacade/datafacade_base.hpp
@@ -152,6 +152,8 @@ class BaseDataFacade
 
     virtual std::string GetNameForID(const unsigned name_id) const = 0;
 
+    virtual std::string GetRefForID(const unsigned name_id) const = 0;
+
     virtual std::string GetPronunciationForID(const unsigned name_id) const = 0;
 
     virtual std::string GetDestinationsForID(const unsigned name_id) const = 0;
diff --git a/include/engine/datafacade/internal_datafacade.hpp b/include/engine/datafacade/internal_datafacade.hpp
index 92c2c7e..20dfe89 100644
--- a/include/engine/datafacade/internal_datafacade.hpp
+++ b/include/engine/datafacade/internal_datafacade.hpp
@@ -360,7 +360,7 @@ class InternalDataFacade final : public BaseDataFacade
             std::vector<util::guidance::BearingClass> bearing_classes;
             // and the actual bearing values
             std::uint64_t num_bearings;
-            intersection_stream.read(reinterpret_cast<char*>(&num_bearings),sizeof(num_bearings));
+            intersection_stream.read(reinterpret_cast<char *>(&num_bearings), sizeof(num_bearings));
             m_bearing_values_table.resize(num_bearings);
             intersection_stream.read(reinterpret_cast<char *>(&m_bearing_values_table[0]),
                                      sizeof(m_bearing_values_table[0]) * num_bearings);
@@ -636,6 +636,15 @@ class InternalDataFacade final : public BaseDataFacade
         return result;
     }
 
+    std::string GetRefForID(const unsigned name_id) const override final
+    {
+        // We store the ref after the name, destination and pronunciation of a street.
+        // We do this to get around the street length limit of 255 which would hit
+        // if we concatenate these. Order (see extractor_callbacks):
+        // name (0), destination (1), pronunciation (2), ref (3)
+        return GetNameForID(name_id + 3);
+    }
+
     std::string GetPronunciationForID(const unsigned name_id) const override final
     {
         // We store the pronunciation after the name and destination of a street.
diff --git a/include/engine/datafacade/shared_datafacade.hpp b/include/engine/datafacade/shared_datafacade.hpp
index bdcd5bc..eb58c57 100644
--- a/include/engine/datafacade/shared_datafacade.hpp
+++ b/include/engine/datafacade/shared_datafacade.hpp
@@ -707,12 +707,21 @@ class SharedDataFacade final : public BaseDataFacade
         return result;
     }
 
+    std::string GetRefForID(const unsigned name_id) const override final
+    {
+        // We store the ref after the name, destination and pronunciation of a street.
+        // We do this to get around the street length limit of 255 which would hit
+        // if we concatenate these. Order (see extractor_callbacks):
+        // name (0), destination (1), pronunciation (2), ref (3)
+        return GetNameForID(name_id + 3);
+    }
+
     std::string GetPronunciationForID(const unsigned name_id) const override final
     {
         // We store the pronunciation after the name and destination of a street.
         // We do this to get around the street length limit of 255 which would hit
         // if we concatenate these. Order (see extractor_callbacks):
-        // name (0), destination (1), pronunciation (2)
+        // name (0), destination (1), pronunciation (2), ref (3)
         return GetNameForID(name_id + 2);
     }
 
@@ -721,7 +730,7 @@ class SharedDataFacade final : public BaseDataFacade
         // We store the destination after the name of a street.
         // We do this to get around the street length limit of 255 which would hit
         // if we concatenate these. Order (see extractor_callbacks):
-        // name (0), destination (1), pronunciation (2)
+        // name (0), destination (1), pronunciation (2), ref (3)
         return GetNameForID(name_id + 1);
     }
 
@@ -835,7 +844,8 @@ class SharedDataFacade final : public BaseDataFacade
         else
             return extractor::guidance::TurnLaneDescription(
                 m_lane_description_masks.begin() + m_lane_description_offsets[lane_description_id],
-                m_lane_description_masks.begin() + m_lane_description_offsets[lane_description_id + 1]);
+                m_lane_description_masks.begin() +
+                    m_lane_description_offsets[lane_description_id + 1]);
     }
 };
 }
diff --git a/include/engine/engine.hpp b/include/engine/engine.hpp
index 634ece6..d2c1531 100644
--- a/include/engine/engine.hpp
+++ b/include/engine/engine.hpp
@@ -55,7 +55,7 @@ class Engine final
     // Needs to be public
     struct EngineLock;
 
-    explicit Engine(EngineConfig &config);
+    explicit Engine(const EngineConfig &config);
 
     Engine(Engine &&) noexcept;
     Engine &operator=(Engine &&) noexcept;
@@ -63,12 +63,12 @@ class Engine final
     // Impl. in cpp since for unique_ptr of incomplete types
     ~Engine();
 
-    Status Route(const api::RouteParameters &parameters, util::json::Object &result);
-    Status Table(const api::TableParameters &parameters, util::json::Object &result);
-    Status Nearest(const api::NearestParameters &parameters, util::json::Object &result);
-    Status Trip(const api::TripParameters &parameters, util::json::Object &result);
-    Status Match(const api::MatchParameters &parameters, util::json::Object &result);
-    Status Tile(const api::TileParameters &parameters, std::string &result);
+    Status Route(const api::RouteParameters &parameters, util::json::Object &result) const;
+    Status Table(const api::TableParameters &parameters, util::json::Object &result) const;
+    Status Nearest(const api::NearestParameters &parameters, util::json::Object &result) const;
+    Status Trip(const api::TripParameters &parameters, util::json::Object &result) const;
+    Status Match(const api::MatchParameters &parameters, util::json::Object &result) const;
+    Status Tile(const api::TileParameters &parameters, std::string &result) const;
 
   private:
     std::unique_ptr<EngineLock> lock;
diff --git a/include/engine/engine_config.hpp b/include/engine/engine_config.hpp
index eb62719..e73b374 100644
--- a/include/engine/engine_config.hpp
+++ b/include/engine/engine_config.hpp
@@ -51,6 +51,7 @@ namespace engine
  *  - Route
  *  - Table
  *  - Match
+ *  - Nearest
  *
  * In addition, shared memory can be used for datasets loaded with osrm-datastore.
  *
@@ -65,6 +66,7 @@ struct EngineConfig final
     int max_locations_viaroute = -1;
     int max_locations_distance_table = -1;
     int max_locations_map_matching = -1;
+    int max_results_nearest = -1;
     bool use_shared_memory = true;
 };
 }
diff --git a/include/engine/geospatial_query.hpp b/include/engine/geospatial_query.hpp
index f61dfeb..1ff5650 100644
--- a/include/engine/geospatial_query.hpp
+++ b/include/engine/geospatial_query.hpp
@@ -20,6 +20,12 @@ namespace osrm
 namespace engine
 {
 
+inline std::pair<bool, bool> boolPairAnd(const std::pair<bool, bool> &A,
+                                         const std::pair<bool, bool> &B)
+{
+    return std::make_pair(A.first && B.first, A.second && B.second);
+}
+
 // Implements complex queries on top of an RTree and builds PhantomNodes from it.
 //
 // Only holds a weak reference on the RTree and coordinates!
@@ -48,7 +54,7 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
     {
         auto results =
             rtree.Nearest(input_coordinate,
-                          [](const CandidateSegment &) { return std::make_pair(true, true); },
+                          [this](const CandidateSegment &segment) { return HasValidEdge(segment); },
                           [this, max_distance, input_coordinate](const std::size_t,
                                                                  const CandidateSegment &segment) {
                               return CheckSegmentDistance(input_coordinate, segment, max_distance);
@@ -68,7 +74,8 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
         auto results = rtree.Nearest(
             input_coordinate,
             [this, bearing, bearing_range, max_distance](const CandidateSegment &segment) {
-                return CheckSegmentBearing(segment, bearing, bearing_range);
+                return boolPairAnd(CheckSegmentBearing(segment, bearing, bearing_range),
+                                   HasValidEdge(segment));
             },
             [this, max_distance, input_coordinate](const std::size_t,
                                                    const CandidateSegment &segment) {
@@ -86,14 +93,15 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
                         const int bearing,
                         const int bearing_range) const
     {
-        auto results =
-            rtree.Nearest(input_coordinate,
-                          [this, bearing, bearing_range](const CandidateSegment &segment) {
-                              return CheckSegmentBearing(segment, bearing, bearing_range);
-                          },
-                          [max_results](const std::size_t num_results, const CandidateSegment &) {
-                              return num_results >= max_results;
-                          });
+        auto results = rtree.Nearest(
+            input_coordinate,
+            [this, bearing, bearing_range](const CandidateSegment &segment) {
+                return boolPairAnd(CheckSegmentBearing(segment, bearing, bearing_range),
+                                   HasValidEdge(segment));
+            },
+            [max_results](const std::size_t num_results, const CandidateSegment &) {
+                return num_results >= max_results;
+            });
 
         return MakePhantomNodes(input_coordinate, results);
     }
@@ -108,16 +116,17 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
                         const int bearing,
                         const int bearing_range) const
     {
-        auto results =
-            rtree.Nearest(input_coordinate,
-                          [this, bearing, bearing_range](const CandidateSegment &segment) {
-                              return CheckSegmentBearing(segment, bearing, bearing_range);
-                          },
-                          [this, max_distance, max_results, input_coordinate](
-                              const std::size_t num_results, const CandidateSegment &segment) {
-                              return num_results >= max_results ||
-                                     CheckSegmentDistance(input_coordinate, segment, max_distance);
-                          });
+        auto results = rtree.Nearest(
+            input_coordinate,
+            [this, bearing, bearing_range](const CandidateSegment &segment) {
+                return boolPairAnd(CheckSegmentBearing(segment, bearing, bearing_range),
+                                   HasValidEdge(segment));
+            },
+            [this, max_distance, max_results, input_coordinate](const std::size_t num_results,
+                                                                const CandidateSegment &segment) {
+                return num_results >= max_results ||
+                       CheckSegmentDistance(input_coordinate, segment, max_distance);
+            });
 
         return MakePhantomNodes(input_coordinate, results);
     }
@@ -129,7 +138,7 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
     {
         auto results =
             rtree.Nearest(input_coordinate,
-                          [](const CandidateSegment &) { return std::make_pair(true, true); },
+                          [this](const CandidateSegment &segment) { return HasValidEdge(segment); },
                           [max_results](const std::size_t num_results, const CandidateSegment &) {
                               return num_results >= max_results;
                           });
@@ -146,7 +155,7 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
     {
         auto results =
             rtree.Nearest(input_coordinate,
-                          [](const CandidateSegment &) { return std::make_pair(true, true); },
+                          [this](const CandidateSegment &segment) { return HasValidEdge(segment); },
                           [this, max_distance, max_results, input_coordinate](
                               const std::size_t num_results, const CandidateSegment &segment) {
                               return num_results >= max_results ||
@@ -166,14 +175,18 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
         bool has_big_component = false;
         auto results = rtree.Nearest(
             input_coordinate,
-            [&has_big_component, &has_small_component](const CandidateSegment &segment) {
+            [this, &has_big_component, &has_small_component](const CandidateSegment &segment) {
                 auto use_segment = (!has_small_component ||
                                     (!has_big_component && !segment.data.component.is_tiny));
                 auto use_directions = std::make_pair(use_segment, use_segment);
+                const auto valid_edges = HasValidEdge(segment);
 
-                has_big_component = has_big_component || !segment.data.component.is_tiny;
-                has_small_component = has_small_component || segment.data.component.is_tiny;
-
+                if (valid_edges.first || valid_edges.second)
+                {
+                    has_big_component = has_big_component || !segment.data.component.is_tiny;
+                    has_small_component = has_small_component || segment.data.component.is_tiny;
+                }
+                use_directions = boolPairAnd(use_directions, valid_edges);
                 return use_directions;
             },
             [this, &has_big_component, max_distance, input_coordinate](
@@ -201,14 +214,22 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
         bool has_big_component = false;
         auto results = rtree.Nearest(
             input_coordinate,
-            [&has_big_component, &has_small_component](const CandidateSegment &segment) {
+            [this, &has_big_component, &has_small_component](const CandidateSegment &segment) {
                 auto use_segment = (!has_small_component ||
                                     (!has_big_component && !segment.data.component.is_tiny));
                 auto use_directions = std::make_pair(use_segment, use_segment);
+                if (!use_directions.first && !use_directions.second)
+                    return use_directions;
+                const auto valid_edges = HasValidEdge(segment);
+
+                if (valid_edges.first || valid_edges.second)
+                {
 
-                has_big_component = has_big_component || !segment.data.component.is_tiny;
-                has_small_component = has_small_component || segment.data.component.is_tiny;
+                    has_big_component = has_big_component || !segment.data.component.is_tiny;
+                    has_small_component = has_small_component || segment.data.component.is_tiny;
+                }
 
+                use_directions = boolPairAnd(use_directions, valid_edges);
                 return use_directions;
             },
             [&has_big_component](const std::size_t num_results, const CandidateSegment &) {
@@ -239,10 +260,13 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
                 auto use_segment = (!has_small_component ||
                                     (!has_big_component && !segment.data.component.is_tiny));
                 auto use_directions = std::make_pair(use_segment, use_segment);
+                use_directions = boolPairAnd(use_directions, HasValidEdge(segment));
 
                 if (use_segment)
                 {
-                    use_directions = CheckSegmentBearing(segment, bearing, bearing_range);
+                    use_directions =
+                        boolPairAnd(CheckSegmentBearing(segment, bearing, bearing_range),
+                                    HasValidEdge(segment));
                     if (use_directions.first || use_directions.second)
                     {
                         has_big_component = has_big_component || !segment.data.component.is_tiny;
@@ -283,10 +307,13 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
                 auto use_segment = (!has_small_component ||
                                     (!has_big_component && !segment.data.component.is_tiny));
                 auto use_directions = std::make_pair(use_segment, use_segment);
+                use_directions = boolPairAnd(use_directions, HasValidEdge(segment));
 
                 if (use_segment)
                 {
-                    use_directions = CheckSegmentBearing(segment, bearing, bearing_range);
+                    use_directions =
+                        boolPairAnd(CheckSegmentBearing(segment, bearing, bearing_range),
+                                    HasValidEdge(segment));
                     if (use_directions.first || use_directions.second)
                     {
                         has_big_component = has_big_component || !segment.data.component.is_tiny;
@@ -440,6 +467,48 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
         return std::make_pair(forward_bearing_valid, backward_bearing_valid);
     }
 
+    /**
+     * Checks to see if the edge weights are valid.  We might have an edge,
+     * but a traffic update might set the speed to 0 (weight == INVALID_EDGE_WEIGHT).
+     * which means that this edge is not currently traversible.  If this is the case,
+     * then we shouldn't snap to this edge.
+     */
+    std::pair<bool, bool> HasValidEdge(const CandidateSegment &segment) const
+    {
+
+        bool forward_edge_valid = false;
+        bool reverse_edge_valid = false;
+
+        if (segment.data.forward_packed_geometry_id != SPECIAL_EDGEID)
+        {
+            std::vector<EdgeWeight> forward_weight_vector;
+            datafacade.GetUncompressedWeights(segment.data.forward_packed_geometry_id,
+                                              forward_weight_vector);
+
+            if (forward_weight_vector[segment.data.fwd_segment_position] != INVALID_EDGE_WEIGHT)
+            {
+                forward_edge_valid = segment.data.forward_segment_id.enabled;
+            }
+        }
+
+        if (segment.data.reverse_packed_geometry_id != SPECIAL_EDGEID)
+        {
+            std::vector<EdgeWeight> reverse_weight_vector;
+            datafacade.GetUncompressedWeights(segment.data.reverse_packed_geometry_id,
+                                              reverse_weight_vector);
+
+            BOOST_ASSERT(segment.data.fwd_segment_position < reverse_weight_vector.size());
+
+            if (reverse_weight_vector[reverse_weight_vector.size() -
+                                      segment.data.fwd_segment_position - 1] != INVALID_EDGE_WEIGHT)
+            {
+                reverse_edge_valid = segment.data.reverse_segment_id.enabled;
+            }
+        }
+
+        return std::make_pair(forward_edge_valid, reverse_edge_valid);
+    }
+
     const RTreeT &rtree;
     const CoordinateList &coordinates;
     DataFacadeT &datafacade;
diff --git a/include/engine/guidance/assemble_geometry.hpp b/include/engine/guidance/assemble_geometry.hpp
index 991a3e4..fb9e8fa 100644
--- a/include/engine/guidance/assemble_geometry.hpp
+++ b/include/engine/guidance/assemble_geometry.hpp
@@ -50,6 +50,10 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
     geometry.osm_node_ids.push_back(facade.GetOSMNodeIDOfNode(
         reverse_geometry[reverse_geometry.size() - source_node.fwd_segment_position - 1]));
 
+    std::vector<uint8_t> forward_datasource_vector;
+    facade.GetUncompressedDatasources(source_node.forward_packed_geometry_id,
+                                      forward_datasource_vector);
+
     auto cumulative_distance = 0.;
     auto current_distance = 0.;
     auto prev_coordinate = geometry.locations.front();
@@ -69,8 +73,8 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
         }
 
         prev_coordinate = coordinate;
-        geometry.annotations.emplace_back(
-            LegGeometry::Annotation{current_distance, path_point.duration_until_turn / 10.});
+        geometry.annotations.emplace_back(LegGeometry::Annotation{
+            current_distance, path_point.duration_until_turn / 10., path_point.datasource_id});
         geometry.locations.push_back(std::move(coordinate));
         geometry.osm_node_ids.push_back(facade.GetOSMNodeIDOfNode(path_point.turn_via_node));
     }
@@ -79,8 +83,14 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade,
     cumulative_distance += current_distance;
     // segment leading to the target node
     geometry.segment_distances.push_back(cumulative_distance);
+
+    std::vector<DatasourceID> forward_datasources;
+    facade.GetUncompressedDatasources(target_node.forward_packed_geometry_id, forward_datasources);
+
     geometry.annotations.emplace_back(
-        LegGeometry::Annotation{current_distance, target_node.forward_weight / 10.});
+        LegGeometry::Annotation{current_distance,
+                                target_node.forward_weight / 10.,
+                                forward_datasources[target_node.fwd_segment_position]});
     geometry.segment_offsets.push_back(geometry.locations.size());
     geometry.locations.push_back(target_node.location);
 
diff --git a/include/engine/guidance/assemble_leg.hpp b/include/engine/guidance/assemble_leg.hpp
index fe8832d..f2f8ad9 100644
--- a/include/engine/guidance/assemble_leg.hpp
+++ b/include/engine/guidance/assemble_leg.hpp
@@ -6,6 +6,11 @@
 #include "engine/guidance/route_leg.hpp"
 #include "engine/guidance/route_step.hpp"
 #include "engine/internal_route_result.hpp"
+#include "util/typedefs.hpp"
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/adaptor/transformed.hpp>
 
 #include <cstddef>
 #include <cstdint>
@@ -107,7 +112,7 @@ std::array<std::uint32_t, SegmentNumber> summarizeRoute(const std::vector<PathDa
         });
 
     std::array<std::uint32_t, SegmentNumber> summary;
-    std::fill(summary.begin(), summary.end(), 0);
+    std::fill(summary.begin(), summary.end(), EMPTY_NAMEID);
     std::transform(segments.begin(),
                    segments.end(),
                    summary.begin(),
@@ -172,21 +177,28 @@ inline RouteLeg assembleLeg(const datafacade::BaseDataFacade &facade,
     {
         auto summary_array = detail::summarizeRoute<detail::MAX_USED_SEGMENTS>(
             route_data, target_node, target_traversed_in_reverse);
-        if (route_data.empty())
-            summary_array[0] = source_node.name_id;
 
         BOOST_ASSERT(detail::MAX_USED_SEGMENTS > 0);
         BOOST_ASSERT(summary_array.begin() != summary_array.end());
-        summary = std::accumulate(std::next(summary_array.begin()),
-                                  summary_array.end(),
-                                  facade.GetNameForID(summary_array.front()),
-                                  [&facade](std::string previous, const std::uint32_t name_id) {
-                                      if (name_id != 0)
-                                      {
-                                          previous += ", " + facade.GetNameForID(name_id);
-                                      }
-                                      return previous;
-                                  });
+
+        // transform a name_id into a string containing either the name, or -if the name is empty-
+        // the reference.
+        const auto name_id_to_string = [&](const NameID name_id) {
+            const auto name = facade.GetNameForID(name_id);
+            if (!name.empty())
+                return name;
+            else
+            {
+                const auto ref = facade.GetRefForID(name_id);
+                return ref;
+            }
+        };
+
+        const auto not_empty = [&](const std::string &name) { return !name.empty(); };
+
+        const auto summary_names = summary_array | boost::adaptors::transformed(name_id_to_string) |
+                                   boost::adaptors::filtered(not_empty);
+        summary = boost::algorithm::join(summary_names, ", ");
     }
 
     return RouteLeg{duration, distance, summary, {}};
diff --git a/include/engine/guidance/assemble_steps.hpp b/include/engine/guidance/assemble_steps.hpp
index 2febaea..116312f 100644
--- a/include/engine/guidance/assemble_steps.hpp
+++ b/include/engine/guidance/assemble_steps.hpp
@@ -103,15 +103,18 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
             {
                 BOOST_ASSERT(segment_duration >= 0);
                 const auto name = facade.GetNameForID(step_name_id);
+                const auto ref = facade.GetRefForID(step_name_id);
                 const auto pronunciation = facade.GetPronunciationForID(step_name_id);
                 const auto destinations = facade.GetDestinationsForID(step_name_id);
                 const auto distance = leg_geometry.segment_distances[segment_index];
 
                 steps.push_back(RouteStep{step_name_id,
                                           std::move(name),
+                                          std::move(ref),
                                           std::move(pronunciation),
                                           std::move(destinations),
                                           NO_ROTARY_NAME,
+                                          NO_ROTARY_NAME,
                                           segment_duration / 10.0,
                                           distance,
                                           path_point.travel_mode,
@@ -167,9 +170,11 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
         BOOST_ASSERT(duration >= 0);
         steps.push_back(RouteStep{step_name_id,
                                   facade.GetNameForID(step_name_id),
+                                  facade.GetRefForID(step_name_id),
                                   facade.GetPronunciationForID(step_name_id),
                                   facade.GetDestinationsForID(step_name_id),
                                   NO_ROTARY_NAME,
+                                  NO_ROTARY_NAME,
                                   duration / 10.,
                                   distance,
                                   target_mode,
@@ -192,9 +197,11 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
 
         steps.push_back(RouteStep{source_node.name_id,
                                   facade.GetNameForID(source_node.name_id),
+                                  facade.GetRefForID(source_node.name_id),
                                   facade.GetPronunciationForID(source_node.name_id),
                                   facade.GetDestinationsForID(source_node.name_id),
                                   NO_ROTARY_NAME,
+                                  NO_ROTARY_NAME,
                                   duration / 10.,
                                   leg_geometry.segment_distances[segment_index],
                                   source_mode,
@@ -226,9 +233,11 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
     BOOST_ASSERT(!leg_geometry.locations.empty());
     steps.push_back(RouteStep{target_node.name_id,
                               facade.GetNameForID(target_node.name_id),
+                              facade.GetRefForID(target_node.name_id),
                               facade.GetPronunciationForID(target_node.name_id),
                               facade.GetDestinationsForID(target_node.name_id),
                               NO_ROTARY_NAME,
+                              NO_ROTARY_NAME,
                               ZERO_DURATION,
                               ZERO_DISTANCE,
                               target_mode,
@@ -247,7 +256,8 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
     BOOST_ASSERT(steps.back().intersections.front().entry.size() == 1);
     BOOST_ASSERT(steps.back().maneuver.waypoint_type == WaypointType::Arrive);
     BOOST_ASSERT(steps.back().intersections.front().lanes.lanes_in_turn == 0);
-    BOOST_ASSERT(steps.back().intersections.front().lanes.first_lane_from_the_right == INVALID_LANEID);
+    BOOST_ASSERT(steps.back().intersections.front().lanes.first_lane_from_the_right ==
+                 INVALID_LANEID);
     BOOST_ASSERT(steps.back().intersections.front().lane_description.empty());
     return steps;
 }
diff --git a/include/engine/guidance/lane_processing.hpp b/include/engine/guidance/lane_processing.hpp
index 8e6f6e1..9d5c3ca 100644
--- a/include/engine/guidance/lane_processing.hpp
+++ b/include/engine/guidance/lane_processing.hpp
@@ -4,6 +4,7 @@
 #include <vector>
 
 #include "engine/guidance/route_step.hpp"
+#include "util/attributes.hpp"
 
 namespace osrm
 {
@@ -17,9 +18,14 @@ namespace guidance
 // we anticipate lane changes emitting only matching lanes early on.
 // the second parameter describes the duration that we feel two segments need to be apart to count
 // as separate maneuvers.
+OSRM_ATTR_WARN_UNUSED
 std::vector<RouteStep> anticipateLaneChange(std::vector<RouteStep> steps,
                                             const double min_duration_needed_for_lane_change = 15);
 
+// Remove all lane information from roundabouts. See #2626.
+OSRM_ATTR_WARN_UNUSED
+std::vector<RouteStep> removeLanesFromRoundabouts(std::vector<RouteStep> steps);
+
 } // namespace guidance
 } // namespace engine
 } // namespace osrm
diff --git a/include/engine/guidance/leg_geometry.hpp b/include/engine/guidance/leg_geometry.hpp
index 72b0e65..3c6fb81 100644
--- a/include/engine/guidance/leg_geometry.hpp
+++ b/include/engine/guidance/leg_geometry.hpp
@@ -39,6 +39,7 @@ struct LegGeometry
     {
         double distance;
         double duration;
+        DatasourceID datasource;
     };
     std::vector<Annotation> annotations;
 
diff --git a/include/engine/guidance/post_processing.hpp b/include/engine/guidance/post_processing.hpp
index ad0b3d6..f720321 100644
--- a/include/engine/guidance/post_processing.hpp
+++ b/include/engine/guidance/post_processing.hpp
@@ -4,6 +4,7 @@
 #include "engine/guidance/leg_geometry.hpp"
 #include "engine/guidance/route_step.hpp"
 #include "engine/phantom_node.hpp"
+#include "util/attributes.hpp"
 
 #include <vector>
 
@@ -13,8 +14,8 @@ namespace engine
 {
 namespace guidance
 {
-
 // passed as none-reference to modify in-place and move out again
+OSRM_ATTR_WARN_UNUSED
 std::vector<RouteStep> postProcess(std::vector<RouteStep> steps);
 
 // Multiple possible reasons can result in unnecessary/confusing instructions
@@ -22,8 +23,17 @@ std::vector<RouteStep> postProcess(std::vector<RouteStep> steps);
 // intersection would result in two instructions to turn left.
 // Collapsing such turns into a single turn instruction, we give a clearer
 // set of instructionst that is not cluttered by unnecessary turns/name changes.
+OSRM_ATTR_WARN_UNUSED
 std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps);
 
+// A check whether two instructions can be treated as one. This is only the case for very short
+// maneuvers that can, in some form, be seen as one. Lookahead of one step.
+bool collapsable(const RouteStep &step, const RouteStep &next);
+
+// Elongate a step by another. the data is added either at the front, or the back
+OSRM_ATTR_WARN_UNUSED
+RouteStep elongate(RouteStep step, const RouteStep &by_step);
+
 // trim initial/final segment of very short length.
 // This function uses in/out parameter passing to modify both steps and geometry in place.
 // We use this method since both steps and geometry are closely coupled logically but
@@ -32,15 +42,18 @@ std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps);
 void trimShortSegments(std::vector<RouteStep> &steps, LegGeometry &geometry);
 
 // assign relative locations to depart/arrive instructions
+OSRM_ATTR_WARN_UNUSED
 std::vector<RouteStep> assignRelativeLocations(std::vector<RouteStep> steps,
                                                const LegGeometry &geometry,
                                                const PhantomNode &source_node,
                                                const PhantomNode &target_node);
 
 // collapse suppressed instructions remaining into intersections array
+OSRM_ATTR_WARN_UNUSED
 std::vector<RouteStep> buildIntersections(std::vector<RouteStep> steps);
 
 // remove steps invalidated by post-processing
+OSRM_ATTR_WARN_UNUSED
 std::vector<RouteStep> removeNoTurnInstructions(std::vector<RouteStep> steps);
 
 // remove use lane information that is not actually a turn. For post-processing, we need to
@@ -50,6 +63,7 @@ std::vector<RouteStep> removeNoTurnInstructions(std::vector<RouteStep> steps);
 // FIXME this is currently only a heuristic. We need knowledge on which lanes actually might become
 // turn lanes. If a straight lane becomes a turn lane, this might be something to consider. Right
 // now we bet on lane-anticipation to catch this.
+OSRM_ATTR_WARN_UNUSED
 std::vector<RouteStep> collapseUseLane(std::vector<RouteStep> steps);
 
 // postProcess will break the connection between the leg geometry
@@ -57,6 +71,7 @@ std::vector<RouteStep> collapseUseLane(std::vector<RouteStep> steps);
 // between routing maneuvers and the route steps itself.
 // If required, we can get both in sync again using this function.
 // Move in LegGeometry for modification in place.
+OSRM_ATTR_WARN_UNUSED
 LegGeometry resyncGeometry(LegGeometry leg_geometry, const std::vector<RouteStep> &steps);
 
 } // namespace guidance
diff --git a/include/engine/guidance/route_step.hpp b/include/engine/guidance/route_step.hpp
index 7106091..5e15c8a 100644
--- a/include/engine/guidance/route_step.hpp
+++ b/include/engine/guidance/route_step.hpp
@@ -25,10 +25,10 @@ namespace guidance
 //  a --> b --> c
 // this struct saves the information of the segment b,c.
 // Notable exceptions are Departure and Arrival steps.
-// Departue: s --> a --> b. Represents the segment s,a with location being s.
+// Departure: s --> a --> b. Represents the segment s,a with location being s.
 // Arrive: a --> b --> t. The segment (b,t) is already covered by the previous segment.
 
-// A represenetation of intermediate intersections
+// A representation of intermediate intersections
 struct Intersection
 {
     static const constexpr std::size_t NO_INDEX = std::numeric_limits<std::size_t>::max();
@@ -58,9 +58,11 @@ struct RouteStep
 {
     unsigned name_id;
     std::string name;
+    std::string ref;
     std::string pronunciation;
     std::string destinations;
     std::string rotary_name;
+    std::string rotary_pronunciation;
     double duration;
     double distance;
     extractor::TravelMode mode;
@@ -78,6 +80,8 @@ inline RouteStep getInvalidRouteStep()
             "",
             "",
             "",
+            "",
+            "",
             0,
             0,
             TRAVEL_MODE_INACCESSIBLE,
diff --git a/include/engine/guidance/step_maneuver.hpp b/include/engine/guidance/step_maneuver.hpp
index 114a822..982a60b 100644
--- a/include/engine/guidance/step_maneuver.hpp
+++ b/include/engine/guidance/step_maneuver.hpp
@@ -17,9 +17,10 @@ namespace guidance
 
 enum class WaypointType : std::uint8_t
 {
-    None,
-    Arrive,
-    Depart,
+    None = 0,
+    Arrive = 1,
+    Depart = 2,
+    MaxWaypointType = 3
 };
 
 struct StepManeuver
diff --git a/include/engine/internal_route_result.hpp b/include/engine/internal_route_result.hpp
index 6f10769..b87b143 100644
--- a/include/engine/internal_route_result.hpp
+++ b/include/engine/internal_route_result.hpp
@@ -33,6 +33,9 @@ struct PathData
     extractor::TravelMode travel_mode : 4;
     // entry class of the turn, indicating possibility of turns
     EntryClassID entry_classid;
+
+    // Source of the speed value on this road segment
+    DatasourceID datasource_id;
 };
 
 struct InternalRouteResult
diff --git a/include/engine/plugins/nearest.hpp b/include/engine/plugins/nearest.hpp
index 657aec9..f2e2551 100644
--- a/include/engine/plugins/nearest.hpp
+++ b/include/engine/plugins/nearest.hpp
@@ -15,9 +15,12 @@ namespace plugins
 class NearestPlugin final : public BasePlugin
 {
   public:
-    explicit NearestPlugin(datafacade::BaseDataFacade &facade);
+    explicit NearestPlugin(datafacade::BaseDataFacade &facade, const int max_results);
 
     Status HandleRequest(const api::NearestParameters &params, util::json::Object &result);
+
+  private:
+    const int max_results;
 };
 }
 }
diff --git a/include/engine/routing_algorithms/routing_base.hpp b/include/engine/routing_algorithms/routing_base.hpp
index 1956cd8..6951209 100644
--- a/include/engine/routing_algorithms/routing_base.hpp
+++ b/include/engine/routing_algorithms/routing_base.hpp
@@ -298,16 +298,18 @@ template <class DataFacadeT, class Derived> class BasicRoutingInterface
                         ? phantom_node_pair.source_phantom.backward_travel_mode
                         : facade->GetTravelModeForEdgeID(ed.id);
 
+                const auto geometry_index = facade->GetGeometryIndexForEdgeID(ed.id);
                 std::vector<NodeID> id_vector;
-                facade->GetUncompressedGeometry(facade->GetGeometryIndexForEdgeID(ed.id),
-                                                id_vector);
+                facade->GetUncompressedGeometry(geometry_index, id_vector);
                 BOOST_ASSERT(id_vector.size() > 0);
 
                 std::vector<EdgeWeight> weight_vector;
-                facade->GetUncompressedWeights(facade->GetGeometryIndexForEdgeID(ed.id),
-                                               weight_vector);
+                facade->GetUncompressedWeights(geometry_index, weight_vector);
                 BOOST_ASSERT(weight_vector.size() > 0);
 
+                std::vector<DatasourceID> datasource_vector;
+                facade->GetUncompressedDatasources(geometry_index, datasource_vector);
+
                 auto total_weight = std::accumulate(weight_vector.begin(), weight_vector.end(), 0);
 
                 BOOST_ASSERT(weight_vector.size() == id_vector.size());
@@ -333,7 +335,8 @@ template <class DataFacadeT, class Derived> class BasicRoutingInterface
                                  extractor::guidance::TurnInstruction::NO_TURN(),
                                  {{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID},
                                  travel_mode,
-                                 INVALID_ENTRY_CLASSID});
+                                 INVALID_ENTRY_CLASSID,
+                                 datasource_vector[i]});
                 }
                 BOOST_ASSERT(unpacked_path.size() > 0);
                 if (facade->hasLaneData(ed.id))
@@ -347,6 +350,7 @@ template <class DataFacadeT, class Derived> class BasicRoutingInterface
         std::size_t start_index = 0, end_index = 0;
         std::vector<unsigned> id_vector;
         std::vector<EdgeWeight> weight_vector;
+        std::vector<DatasourceID> datasource_vector;
         const bool is_local_path = (phantom_node_pair.source_phantom.forward_packed_geometry_id ==
                                     phantom_node_pair.target_phantom.forward_packed_geometry_id) &&
                                    unpacked_path.empty();
@@ -359,6 +363,9 @@ template <class DataFacadeT, class Derived> class BasicRoutingInterface
             facade->GetUncompressedWeights(
                 phantom_node_pair.target_phantom.reverse_packed_geometry_id, weight_vector);
 
+            facade->GetUncompressedDatasources(
+                phantom_node_pair.target_phantom.reverse_packed_geometry_id, datasource_vector);
+
             if (is_local_path)
             {
                 start_index =
@@ -379,6 +386,9 @@ template <class DataFacadeT, class Derived> class BasicRoutingInterface
 
             facade->GetUncompressedWeights(
                 phantom_node_pair.target_phantom.forward_packed_geometry_id, weight_vector);
+
+            facade->GetUncompressedDatasources(
+                phantom_node_pair.target_phantom.forward_packed_geometry_id, datasource_vector);
         }
 
         // Given the following compressed geometry:
@@ -400,7 +410,8 @@ template <class DataFacadeT, class Derived> class BasicRoutingInterface
                 {{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID},
                 target_traversed_in_reverse ? phantom_node_pair.target_phantom.backward_travel_mode
                                             : phantom_node_pair.target_phantom.forward_travel_mode,
-                INVALID_ENTRY_CLASSID});
+                INVALID_ENTRY_CLASSID,
+                datasource_vector[i]});
         }
 
         if (unpacked_path.size() > 0)
@@ -691,15 +702,15 @@ template <class DataFacadeT, class Derived> class BasicRoutingInterface
             }
         }
 
-        const auto insertInCoreHeap =
-            [](const CoreEntryPoint &p, SearchEngineData::QueryHeap &core_heap) {
-                NodeID id;
-                EdgeWeight weight;
-                NodeID parent;
-                // TODO this should use std::apply when we get c++17 support
-                std::tie(id, weight, parent) = p;
-                core_heap.Insert(id, weight, parent);
-            };
+        const auto insertInCoreHeap = [](const CoreEntryPoint &p,
+                                         SearchEngineData::QueryHeap &core_heap) {
+            NodeID id;
+            EdgeWeight weight;
+            NodeID parent;
+            // TODO this should use std::apply when we get c++17 support
+            std::tie(id, weight, parent) = p;
+            core_heap.Insert(id, weight, parent);
+        };
 
         forward_core_heap.Clear();
         for (const auto &p : forward_entry_points)
diff --git a/include/extractor/edge_based_graph_factory.hpp b/include/extractor/edge_based_graph_factory.hpp
index f8d1327..130e078 100644
--- a/include/extractor/edge_based_graph_factory.hpp
+++ b/include/extractor/edge_based_graph_factory.hpp
@@ -36,13 +36,13 @@
 
 #include <boost/filesystem/fstream.hpp>
 
-struct lua_State;
-
 namespace osrm
 {
 namespace extractor
 {
 
+class ScriptingEnvironment;
+
 namespace lookup
 {
 // Set to 1 byte alignment
@@ -83,21 +83,21 @@ class EdgeBasedGraphFactory
     EdgeBasedGraphFactory(const EdgeBasedGraphFactory &) = delete;
     EdgeBasedGraphFactory &operator=(const EdgeBasedGraphFactory &) = delete;
 
-    explicit EdgeBasedGraphFactory(
-        std::shared_ptr<util::NodeBasedDynamicGraph> node_based_graph,
-        const CompressedEdgeContainer &compressed_edge_container,
-        const std::unordered_set<NodeID> &barrier_nodes,
-        const std::unordered_set<NodeID> &traffic_lights,
-        std::shared_ptr<const RestrictionMap> restriction_map,
-        const std::vector<QueryNode> &node_info_list,
-        ProfileProperties profile_properties,
-        const util::NameTable &name_table,
-        const std::vector<std::uint32_t> &turn_lane_offsets,
-        const std::vector<guidance::TurnLaneType::Mask> &turn_lane_masks);
-
-    void Run(const std::string &original_edge_data_filename,
+    explicit EdgeBasedGraphFactory(std::shared_ptr<util::NodeBasedDynamicGraph> node_based_graph,
+                                   const CompressedEdgeContainer &compressed_edge_container,
+                                   const std::unordered_set<NodeID> &barrier_nodes,
+                                   const std::unordered_set<NodeID> &traffic_lights,
+                                   std::shared_ptr<const RestrictionMap> restriction_map,
+                                   const std::vector<QueryNode> &node_info_list,
+                                   ProfileProperties profile_properties,
+                                   const util::NameTable &name_table,
+                                   std::vector<std::uint32_t> &turn_lane_offsets,
+                                   std::vector<guidance::TurnLaneType::Mask> &turn_lane_masks,
+                                   guidance::LaneDescriptionMap &lane_description_map);
+
+    void Run(ScriptingEnvironment &scripting_environment,
+             const std::string &original_edge_data_filename,
              const std::string &turn_lane_data_filename,
-             lua_State *lua_state,
              const std::string &edge_segment_lookup_filename,
              const std::string &edge_penalty_filename,
              const bool generate_edge_lookup);
@@ -127,8 +127,6 @@ class EdgeBasedGraphFactory
                                           const NodeID w,
                                           const double angle) const;
 
-    std::int32_t GetTurnPenalty(double angle, lua_State *lua_state) const;
-
   private:
     using EdgeData = util::NodeBasedDynamicGraph::EdgeData;
 
@@ -156,15 +154,16 @@ class EdgeBasedGraphFactory
     ProfileProperties profile_properties;
 
     const util::NameTable &name_table;
-    const std::vector<std::uint32_t> &turn_lane_offsets;
-    const std::vector<guidance::TurnLaneType::Mask> &turn_lane_masks;
+    std::vector<std::uint32_t> &turn_lane_offsets;
+    std::vector<guidance::TurnLaneType::Mask> &turn_lane_masks;
+    guidance::LaneDescriptionMap &lane_description_map;
 
     void CompressGeometry();
     unsigned RenumberEdges();
     void GenerateEdgeExpandedNodes();
-    void GenerateEdgeExpandedEdges(const std::string &original_edge_data_filename,
+    void GenerateEdgeExpandedEdges(ScriptingEnvironment &scripting_environment,
+                                   const std::string &original_edge_data_filename,
                                    const std::string &turn_lane_data_filename,
-                                   lua_State *lua_state,
                                    const std::string &edge_segment_lookup_filename,
                                    const std::string &edge_fixed_penalties_filename,
                                    const bool generate_edge_lookup);
diff --git a/include/extractor/extraction_containers.hpp b/include/extractor/extraction_containers.hpp
index ccd7104..5f3ede1 100644
--- a/include/extractor/extraction_containers.hpp
+++ b/include/extractor/extraction_containers.hpp
@@ -34,16 +34,12 @@ class ExtractionContainers
 #endif
     void PrepareNodes();
     void PrepareRestrictions();
-    void PrepareEdges(lua_State *segment_state);
+    void PrepareEdges(ScriptingEnvironment &scripting_environment);
 
     void WriteNodes(std::ofstream &file_out_stream) const;
     void WriteRestrictions(const std::string &restrictions_file_name) const;
     void WriteEdges(std::ofstream &file_out_stream) const;
     void WriteCharData(const std::string &file_name);
-    void
-    WriteTurnLaneMasks(const std::string &file_name,
-                       const stxxl::vector<std::uint32_t> &turn_lane_offsets,
-                       const stxxl::vector<guidance::TurnLaneType::Mask> &turn_lane_masks) const;
 
   public:
     using STXXLNodeIDVector = stxxl::vector<OSMNodeID>;
@@ -60,8 +56,6 @@ class ExtractionContainers
     STXXLNameCharData name_char_data;
     STXXLNameOffsets name_offsets;
     // an adjacency array containing all turn lane masks
-    stxxl::vector<std::uint32_t> turn_lane_offsets;
-    stxxl::vector<guidance::TurnLaneType::Mask> turn_lane_masks;
     STXXLRestrictionsVector restrictions_list;
     STXXLWayIDStartEndVector way_start_end_id_list;
     std::unordered_map<OSMNodeID, NodeID> external_to_internal_node_id_map;
@@ -69,11 +63,10 @@ class ExtractionContainers
 
     ExtractionContainers();
 
-    void PrepareData(const std::string &output_file_name,
+    void PrepareData(ScriptingEnvironment &scripting_environment,
+                     const std::string &output_file_name,
                      const std::string &restrictions_file_name,
-                     const std::string &names_file_name,
-                     const std::string &turn_lane_file_name,
-                     lua_State *segment_state);
+                     const std::string &names_file_name);
 };
 }
 }
diff --git a/include/extractor/extraction_helper_functions.hpp b/include/extractor/extraction_helper_functions.hpp
index 9fb5477..f3240de 100644
--- a/include/extractor/extraction_helper_functions.hpp
+++ b/include/extractor/extraction_helper_functions.hpp
@@ -106,6 +106,12 @@ trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t co
 {
     return extractor::guidance::trimLaneString(std::move(lane_string), count_left, count_right);
 }
+
+inline std::string applyAccessTokens(const std::string &lane_string,
+                                     const std::string &access_tokens)
+{
+    return extractor::guidance::applyAccessTokens(lane_string, access_tokens);
+}
 }
 }
 
diff --git a/include/extractor/extraction_way.hpp b/include/extractor/extraction_way.hpp
index 8ae948a..8024f6b 100644
--- a/include/extractor/extraction_way.hpp
+++ b/include/extractor/extraction_way.hpp
@@ -1,6 +1,7 @@
 #ifndef EXTRACTION_WAY_HPP
 #define EXTRACTION_WAY_HPP
 
+#include "extractor/guidance/road_classification.hpp"
 #include "extractor/travel_mode.hpp"
 #include "util/guidance/turn_lanes.hpp"
 #include "util/typedefs.hpp"
@@ -32,12 +33,14 @@ struct ExtractionWay
         is_startpoint = true;
         is_access_restricted = false;
         name.clear();
+        ref.clear();
         pronunciation.clear();
         destinations.clear();
         forward_travel_mode = TRAVEL_MODE_INACCESSIBLE;
         backward_travel_mode = TRAVEL_MODE_INACCESSIBLE;
         turn_lanes_forward.clear();
         turn_lanes_backward.clear();
+        road_classification = guidance::RoadClassification();
     }
 
     // These accessors exists because it's not possible to take the address of a bitfield,
@@ -51,6 +54,7 @@ struct ExtractionWay
     double backward_speed;
     double duration;
     std::string name;
+    std::string ref;
     std::string pronunciation;
     std::string destinations;
     std::string turn_lanes_forward;
@@ -60,6 +64,7 @@ struct ExtractionWay
     bool is_startpoint;
     TravelMode forward_travel_mode : 4;
     TravelMode backward_travel_mode : 4;
+    guidance::RoadClassification road_classification;
 };
 }
 }
diff --git a/include/extractor/extractor.hpp b/include/extractor/extractor.hpp
index fa355c8..09d1c9b 100644
--- a/include/extractor/extractor.hpp
+++ b/include/extractor/extractor.hpp
@@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "util/guidance/bearing_class.hpp"
 #include "util/guidance/entry_class.hpp"
+#include "util/guidance/turn_lanes.hpp"
 
 #include "util/typedefs.hpp"
 
@@ -43,20 +44,20 @@ namespace osrm
 namespace extractor
 {
 
+class ScriptingEnvironment;
 struct ProfileProperties;
 
 class Extractor
 {
   public:
     Extractor(ExtractorConfig extractor_config) : config(std::move(extractor_config)) {}
-    int run();
+    int run(ScriptingEnvironment &scripting_environment);
 
   private:
     ExtractorConfig config;
 
     std::pair<std::size_t, EdgeID>
-    BuildEdgeExpandedGraph(lua_State *lua_state,
-                           const ProfileProperties &profile_properties,
+    BuildEdgeExpandedGraph(ScriptingEnvironment &scripting_environment,
                            std::vector<QueryNode> &internal_to_external_node_map,
                            std::vector<EdgeBasedNode> &node_based_edge_list,
                            std::vector<bool> &node_is_startpoint,
@@ -87,6 +88,16 @@ class Extractor
         const std::vector<std::uint32_t> &node_based_intersection_classes,
         const std::vector<util::guidance::BearingClass> &bearing_classes,
         const std::vector<util::guidance::EntryClass> &entry_classes) const;
+
+    void WriteTurnLaneData(const std::string &turn_lane_file) const;
+
+    // globals persisting during the extraction process and the graph generation process
+
+    // during turn lane analysis, we might have to combine lanes for roads that are modelled as two
+    // but are more or less experienced as one. This can be due to solid lines in between lanes, for
+    // example, that genereate a small separation between them. As a result, we might have to
+    // augment the turn lane map during processing, further adding more types.
+    guidance::LaneDescriptionMap turn_lane_map;
 };
 }
 }
diff --git a/include/extractor/extractor_callbacks.hpp b/include/extractor/extractor_callbacks.hpp
index 2adfb7d..e5cb86c 100644
--- a/include/extractor/extractor_callbacks.hpp
+++ b/include/extractor/extractor_callbacks.hpp
@@ -1,8 +1,8 @@
 #ifndef EXTRACTOR_CALLBACKS_HPP
 #define EXTRACTOR_CALLBACKS_HPP
 
-#include "util/typedefs.hpp"
 #include "extractor/guidance/turn_lane_types.hpp"
+#include "util/typedefs.hpp"
 
 #include <boost/functional/hash.hpp>
 #include <boost/optional/optional_fwd.hpp>
@@ -16,6 +16,24 @@ class Node;
 class Way;
 }
 
+namespace std
+{
+template <> struct hash<std::tuple<std::string, std::string, std::string, std::string>>
+{
+    std::size_t
+    operator()(const std::tuple<std::string, std::string, std::string, std::string> &mk) const
+        noexcept
+    {
+        std::size_t seed = 0;
+        boost::hash_combine(seed, std::get<0>(mk));
+        boost::hash_combine(seed, std::get<1>(mk));
+        boost::hash_combine(seed, std::get<2>(mk));
+        boost::hash_combine(seed, std::get<3>(mk));
+        return seed;
+    }
+};
+}
+
 namespace osrm
 {
 namespace extractor
@@ -27,7 +45,7 @@ struct ExtractionNode;
 struct ExtractionWay;
 
 /**
- * This class is uses by the extractor with the results of the
+ * This class is used by the extractor with the results of the
  * osmium based parsing and the customization through the lua profile.
  *
  * It mediates between the multi-threaded extraction process and the external memory containers.
@@ -36,11 +54,12 @@ struct ExtractionWay;
 class ExtractorCallbacks
 {
   private:
-    // used to deduplicate street names and street destinations: actually maps to name ids
-    using MapKey = std::pair<std::string, std::string>;
+    // used to deduplicate street names, refs, destinations, pronunciation: actually maps to name
+    // ids
+    using MapKey = std::tuple<std::string, std::string, std::string, std::string>;
     using MapVal = unsigned;
-    std::unordered_map<MapKey, MapVal, boost::hash<MapKey>> string_map;
-    std::unordered_map<guidance::TurnLaneDescription,LaneDescriptionID,guidance::TurnLaneDescription_hash> lane_description_map;
+    std::unordered_map<MapKey, MapVal> string_map;
+    guidance::LaneDescriptionMap lane_description_map;
     ExtractionContainers &external_memory;
 
   public:
@@ -57,6 +76,9 @@ class ExtractorCallbacks
 
     // warning: caller needs to take care of synchronization!
     void ProcessWay(const osmium::Way &current_way, const ExtractionWay &result_way);
+
+    // destroys the internal laneDescriptionMap
+    guidance::LaneDescriptionMap &&moveOutLaneDescriptionMap();
 };
 }
 }
diff --git a/include/extractor/extractor_config.hpp b/include/extractor/extractor_config.hpp
index d0b5fd2..5e15e76 100644
--- a/include/extractor/extractor_config.hpp
+++ b/include/extractor/extractor_config.hpp
@@ -77,7 +77,6 @@ struct ExtractorConfig
         intersection_class_data_output_path = basepath + ".osrm.icd";
     }
 
-    boost::filesystem::path config_file_path;
     boost::filesystem::path input_path;
     boost::filesystem::path profile_path;
 
diff --git a/include/extractor/guidance/classification_data.hpp b/include/extractor/guidance/classification_data.hpp
deleted file mode 100644
index 0454041..0000000
--- a/include/extractor/guidance/classification_data.hpp
+++ /dev/null
@@ -1,84 +0,0 @@
-#ifndef OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
-#define OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
-
-#include <cstdint>
-
-#include <string>
-
-// Forward Declaration to allow usage of external osmium::Way
-namespace osmium
-{
-class Way;
-}
-
-namespace osrm
-{
-namespace extractor
-{
-namespace guidance
-{
-
-enum class FunctionalRoadClass : std::uint8_t
-{
-    UNKNOWN = 0,
-    MOTORWAY,
-    MOTORWAY_LINK,
-    TRUNK,
-    TRUNK_LINK,
-    PRIMARY,
-    PRIMARY_LINK,
-    SECONDARY,
-    SECONDARY_LINK,
-    TERTIARY,
-    TERTIARY_LINK,
-    UNCLASSIFIED,
-    RESIDENTIAL,
-    SERVICE,
-    LIVING_STREET,
-    LOW_PRIORITY_ROAD // a road simply included for connectivity. Should be avoided at all cost
-};
-
-FunctionalRoadClass functionalRoadClassFromTag(std::string const &tag);
-
-inline bool isRampClass(const FunctionalRoadClass road_class)
-{
-    // Primary Roads and down are usually too small to announce their links as ramps
-    return road_class == FunctionalRoadClass::MOTORWAY_LINK ||
-           road_class == FunctionalRoadClass::TRUNK_LINK;
-}
-
-// Links are usually smaller than ramps, but are sometimes tagged
-// as MOTORWAY_LINK if they exit/enter a motorway/trunk road.
-inline bool isLinkClass(const FunctionalRoadClass road_class)
-{
-    return road_class == FunctionalRoadClass::MOTORWAY_LINK ||
-           road_class == FunctionalRoadClass::TRUNK_LINK ||
-           road_class == FunctionalRoadClass::PRIMARY_LINK ||
-           road_class == FunctionalRoadClass::SECONDARY_LINK ||
-           road_class == FunctionalRoadClass::TERTIARY_LINK;
-}
-
-// check wheter a class is a motorway like class
-inline bool isMotorwayClass(const FunctionalRoadClass road_class)
-{
-    return road_class == FunctionalRoadClass::MOTORWAY || road_class == FunctionalRoadClass::TRUNK;
-}
-
-// TODO augment this with all data required for guidance generation
-struct RoadClassificationData
-{
-    FunctionalRoadClass road_class = FunctionalRoadClass::UNKNOWN;
-
-    void augment(const osmium::Way &way);
-};
-
-inline bool operator==(const RoadClassificationData lhs, const RoadClassificationData rhs)
-{
-    return lhs.road_class == rhs.road_class;
-}
-
-} // namespace guidance
-} // namespace extractor
-} // namespace osrm
-
-#endif // OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
diff --git a/include/extractor/guidance/constants.hpp b/include/extractor/guidance/constants.hpp
index 54729ba..520f447 100644
--- a/include/extractor/guidance/constants.hpp
+++ b/include/extractor/guidance/constants.hpp
@@ -18,15 +18,24 @@ const double constexpr MAXIMAL_ALLOWED_NO_TURN_DEVIATION = 3.;
 const double constexpr NARROW_TURN_ANGLE = 40.;
 const double constexpr GROUP_ANGLE = 60;
 // angle difference that can be classified as straight, if its the only narrow turn
-const double constexpr FUZZY_ANGLE_DIFFERENCE = 20.;
+const double constexpr FUZZY_ANGLE_DIFFERENCE = 25.;
 const double constexpr DISTINCTION_RATIO = 2;
 
-const double constexpr MAX_ROUNDABOUT_INTERSECTION_RADIUS = 5;
-const double constexpr MAX_ROUNDABOUT_RADIUS = 15; // 30 m diameter as final distinction
+// Named roundabouts with radii larger then than this are seen as rotary
+const double constexpr MAX_ROUNDABOUT_RADIUS = 15;
+// Unnamed small roundabouts that look like intersections are announced as turns,
+// guard against data issues or such roundabout intersections getting too large.
+const double constexpr MAX_ROUNDABOUT_INTERSECTION_RADIUS = 25;
+
 const double constexpr INCREASES_BY_FOURTY_PERCENT = 1.4;
 
 const int constexpr MAX_SLIPROAD_THRESHOLD = 250;
 
+// Road priorities give an idea of how obvious a turn is. If two priorities differ greatly (e.g.
+// service road over a primary road, the better priority can be seen as obvious due to its road
+// category).
+const double constexpr PRIORITY_DISTINCTION_FACTOR = 2.0;
+
 } // namespace guidance
 } // namespace extractor
 } // namespace osrm
diff --git a/include/extractor/guidance/intersection_generator.hpp b/include/extractor/guidance/intersection_generator.hpp
index 6dcba08..a2b5b61 100644
--- a/include/extractor/guidance/intersection_generator.hpp
+++ b/include/extractor/guidance/intersection_generator.hpp
@@ -5,6 +5,7 @@
 #include "extractor/guidance/intersection.hpp"
 #include "extractor/query_node.hpp"
 #include "extractor/restriction_map.hpp"
+#include "util/attributes.hpp"
 #include "util/name_table.hpp"
 #include "util/node_based_graph.hpp"
 #include "util/typedefs.hpp"
@@ -34,6 +35,17 @@ class IntersectionGenerator
 
     Intersection operator()(const NodeID nid, const EdgeID via_eid) const;
 
+    // Graph Compression cannot compress every setting. For example any barrier/traffic light cannot
+    // be compressed. As a result, a simple road of the form `a ----- b` might end up as having an
+    // intermediate intersection, if there is a traffic light in between. If we want to look farther
+    // down a road, finding the next actual decision requires the look at multiple intersections.
+    // Here we follow the road until we either reach a dead end or find the next intersection with
+    // more than a single next road.
+    Intersection GetActualNextIntersection(const NodeID starting_node,
+                                           const EdgeID via_edge,
+                                           NodeID *resulting_from_node,
+                                           EdgeID *resulting_via_edge) const;
+
   private:
     const util::NodeBasedDynamicGraph &node_based_graph;
     const RestrictionMap &restriction_map;
@@ -46,7 +58,16 @@ class IntersectionGenerator
     // node reached
     // from `from_node` via `via_eid`
     // The resulting candidates have to be analysed for their actual instructions later on.
-    Intersection getConnectedRoads(const NodeID from_node, const EdgeID via_eid) const;
+    OSRM_ATTR_WARN_UNUSED
+    Intersection GetConnectedRoads(const NodeID from_node, const EdgeID via_eid) const;
+
+    // check if two indices in an intersection can be seen as a single road in the perceived
+    // intersection representation See below for an example. Utility function for
+    // MergeSegregatedRoads
+    bool CanMerge(const NodeID intersection_node,
+                  const Intersection &intersection,
+                  std::size_t first_index,
+                  std::size_t second_index) const;
 
     // Merge segregated roads to omit invalid turns in favor of treating segregated roads as
     // one.
@@ -59,7 +80,24 @@ class IntersectionGenerator
     //
     // The treatment results in a straight turn angle of 180º rather than a turn angle of approx
     // 160
-    Intersection mergeSegregatedRoads(Intersection intersection) const;
+    OSRM_ATTR_WARN_UNUSED
+    Intersection MergeSegregatedRoads(const NodeID intersection_node,
+                                      Intersection intersection) const;
+
+    // The counterpiece to mergeSegregatedRoads. While we can adjust roads that split up at the
+    // intersection itself, it can also happen that intersections are connected to joining roads.
+    //
+    //     *                           *
+    //     *        is converted to    *
+    //   v   a ---                     a ---
+    //   v   ^                         +
+    //   v   ^                         +
+    //       b
+    //
+    // for the local view of b at a.
+    OSRM_ATTR_WARN_UNUSED
+    Intersection AdjustForJoiningRoads(const NodeID node_at_intersection,
+                                       Intersection intersection) const;
 };
 
 } // namespace guidance
diff --git a/include/extractor/guidance/intersection_handler.hpp b/include/extractor/guidance/intersection_handler.hpp
index e657181..19b79ab 100644
--- a/include/extractor/guidance/intersection_handler.hpp
+++ b/include/extractor/guidance/intersection_handler.hpp
@@ -2,6 +2,7 @@
 #define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HANDLER_HPP_
 
 #include "extractor/guidance/intersection.hpp"
+#include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/query_node.hpp"
 #include "extractor/suffix_table.hpp"
 
@@ -28,8 +29,10 @@ class IntersectionHandler
     IntersectionHandler(const util::NodeBasedDynamicGraph &node_based_graph,
                         const std::vector<QueryNode> &node_info_list,
                         const util::NameTable &name_table,
-                        const SuffixTable &street_name_suffix_table);
-    virtual ~IntersectionHandler();
+                        const SuffixTable &street_name_suffix_table,
+                        const IntersectionGenerator &intersection_generator);
+
+    virtual ~IntersectionHandler() = default;
 
     // check whether the handler can actually handle the intersection
     virtual bool
@@ -44,6 +47,7 @@ class IntersectionHandler
     const std::vector<QueryNode> &node_info_list;
     const util::NameTable &name_table;
     const SuffixTable &street_name_suffix_table;
+    const IntersectionGenerator &intersection_generator;
 
     // counts the number on allowed entry roads
     std::size_t countValid(const Intersection &intersection) const;
@@ -51,7 +55,16 @@ class IntersectionHandler
     // Decide on a basic turn types
     TurnType::Enum findBasicTurnType(const EdgeID via_edge, const ConnectedRoad &candidate) const;
 
-    // Get the Instruction for an obvious turn
+    // Find the most obvious turn to follow. The function returns an index into the intersection
+    // determining whether there is a road that can be seen as obvious turn in the presence of many
+    // other possible turns. The function will consider road categories and other inputs like the
+    // turn angles.
+    std::size_t findObviousTurn(const EdgeID via_edge, const Intersection &intersection) const;
+
+    // Obvious turns can still take multiple forms. This function looks at the turn onto a road
+    // candidate when coming from a via_edge and determines the best instruction to emit.
+    // `through_street` indicates if the street turned onto is a through sreet (think mergees and
+    // similar)
     TurnInstruction getInstructionForObvious(const std::size_t number_of_candidates,
                                              const EdgeID via_edge,
                                              const bool through_street,
diff --git a/include/extractor/guidance/intersection_scenario_three_way.hpp b/include/extractor/guidance/intersection_scenario_three_way.hpp
index d611f91..70bfc22 100644
--- a/include/extractor/guidance/intersection_scenario_three_way.hpp
+++ b/include/extractor/guidance/intersection_scenario_three_way.hpp
@@ -10,11 +10,6 @@ namespace extractor
 namespace guidance
 {
 
-// possible fork
-bool isFork(const ConnectedRoad &uturn,
-            const ConnectedRoad &possible_right_fork,
-            const ConnectedRoad &possible_left_fork);
-
 // Ending in a T-Intersection
 bool isEndOfRoad(const ConnectedRoad &uturn,
                  const ConnectedRoad &possible_right_turn,
diff --git a/include/extractor/guidance/motorway_handler.hpp b/include/extractor/guidance/motorway_handler.hpp
index e8830aa..789545b 100644
--- a/include/extractor/guidance/motorway_handler.hpp
+++ b/include/extractor/guidance/motorway_handler.hpp
@@ -2,9 +2,11 @@
 #define OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_
 
 #include "extractor/guidance/intersection.hpp"
+#include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/guidance/intersection_handler.hpp"
 #include "extractor/query_node.hpp"
 
+#include "util/attributes.hpp"
 #include "util/name_table.hpp"
 #include "util/node_based_graph.hpp"
 
@@ -25,8 +27,10 @@ class MotorwayHandler : public IntersectionHandler
     MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
                     const std::vector<QueryNode> &node_info_list,
                     const util::NameTable &name_table,
-                    const SuffixTable &street_name_suffix_table);
-    ~MotorwayHandler() override final;
+                    const SuffixTable &street_name_suffix_table,
+                    const IntersectionGenerator &intersection_generator);
+
+    ~MotorwayHandler() override final = default;
 
     // check whether the handler can actually handle the intersection
     bool canProcess(const NodeID nid,
@@ -39,11 +43,17 @@ class MotorwayHandler : public IntersectionHandler
                             Intersection intersection) const override final;
 
   private:
+    OSRM_ATTR_WARN_UNUSED
     Intersection handleSliproads(const NodeID intersection_node_id,
                                  Intersection intersection) const;
+
+    OSRM_ATTR_WARN_UNUSED
     Intersection fromMotorway(const EdgeID via_edge, Intersection intersection) const;
+
+    OSRM_ATTR_WARN_UNUSED
     Intersection fromRamp(const EdgeID via_edge, Intersection intersection) const;
 
+    OSRM_ATTR_WARN_UNUSED
     Intersection fallback(Intersection intersection) const;
 };
 
diff --git a/include/extractor/guidance/road_classification.hpp b/include/extractor/guidance/road_classification.hpp
new file mode 100644
index 0000000..2b74348
--- /dev/null
+++ b/include/extractor/guidance/road_classification.hpp
@@ -0,0 +1,127 @@
+#ifndef OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
+#define OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
+
+#include <cmath>
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+
+#include <osmium/osm.hpp>
+
+namespace osrm
+{
+namespace extractor
+{
+namespace guidance
+{
+
+// Priorities are used to distinguish between how likely a turn is in comparison to a different
+// road. The priorities here are used to distinguish between obvious turns (e.g. following a primary
+// road next to a residential one is obvious). The decision what is obvious is described in the
+// guidance constants.
+namespace RoadPriorityClass
+{
+typedef std::uint8_t Enum;
+// Top priority Road
+const constexpr Enum MOTORWAY = 0;
+// Second highest priority
+const constexpr Enum TRUNK = 2;
+// Main roads
+const constexpr Enum PRIMARY = 4;
+const constexpr Enum SECONDARY = 6;
+const constexpr Enum TERTIARY = 8;
+// Residential Categories
+const constexpr Enum MAIN_RESIDENTIAL = 10;
+const constexpr Enum SIDE_RESIDENTIAL = 11;
+// Link Category
+const constexpr Enum LINK_ROAD = 14;
+// Bike Accessible
+const constexpr Enum BIKE_PATH = 16;
+// Walk Accessible
+const constexpr Enum FOOT_PATH = 18;
+// Link types are usually not considered in forks, unless amongst each other.
+// a road simply offered for connectivity. Will be ignored in forks/other decisions. Always
+// considered non-obvious to continue on
+const constexpr Enum CONNECTIVITY = 31;
+} // namespace Road Class
+
+#pragma pack(push, 1)
+class RoadClassification
+{
+    // a class that behaves like a motorway (separated directions)
+    std::uint8_t motorway_class : 1;
+    // all types of link classes
+    std::uint8_t link_class : 1;
+    // a low priority class is a pure connectivity way. It can be ignored in multiple decisions
+    // (e.g. fork on a primary vs service will not happen)
+    std::uint8_t may_be_ignored : 1;
+    // the road priority is used as an indicator for forks. If the roads are of similar priority
+    // (difference <=1), we can see the road as a fork. Else one of the road classes is seen as
+    // obvious choice
+    RoadPriorityClass::Enum road_priority_class : 5;
+
+  public:
+    // default construction
+    RoadClassification()
+        : motorway_class(0), link_class(0), may_be_ignored(1),
+          road_priority_class(RoadPriorityClass::CONNECTIVITY)
+    {
+    }
+
+    RoadClassification(bool motorway_class,
+                       bool link_class,
+                       bool may_be_ignored,
+                       RoadPriorityClass::Enum road_priority_class)
+        : motorway_class(motorway_class), link_class(link_class), may_be_ignored(may_be_ignored),
+          road_priority_class(road_priority_class)
+    {
+    }
+
+    bool IsMotorwayClass() const { return (0 != motorway_class) && (0 == link_class); }
+    void SetMotorwayFlag(const bool new_value) { motorway_class = new_value; }
+
+    bool IsRampClass() const { return (0 != motorway_class) && (0 != link_class); }
+
+    bool IsLinkClass() const { return (0 != link_class); }
+    void SetLinkClass(const bool new_value) { link_class = new_value; }
+
+    bool IsLowPriorityRoadClass() const { return (0 != may_be_ignored); }
+    void SetLowPriorityFlag(const bool new_value) { may_be_ignored = new_value; }
+
+    std::uint32_t GetPriority() const { return static_cast<std::uint32_t>(road_priority_class); }
+
+    RoadPriorityClass::Enum GetClass() const { return road_priority_class; }
+    void SetClass(const RoadPriorityClass::Enum new_value) { road_priority_class = new_value; }
+
+    bool operator==(const RoadClassification &other) const
+    {
+        return motorway_class == other.motorway_class && link_class == other.link_class &&
+               may_be_ignored == other.may_be_ignored &&
+               road_priority_class == other.road_priority_class;
+    }
+
+    bool operator!=(const RoadClassification &other) const { return !(*this == other); }
+
+    std::string ToString() const
+    {
+        return std::string() + (motorway_class ? "motorway" : "normal") +
+               (link_class ? "_link" : "") + (may_be_ignored ? " ignorable " : " important ") +
+               std::to_string(road_priority_class);
+    }
+};
+#pragma pack(pop)
+
+static_assert(
+    sizeof(RoadClassification) == 1,
+    "Road Classification should fit a byte. Increasing this has a severe impact on memory.");
+
+inline bool canBeSeenAsFork(const RoadClassification first, const RoadClassification second)
+{
+    return std::abs(static_cast<int>(first.GetPriority()) -
+                    static_cast<int>(second.GetPriority())) <= 1;
+}
+} // namespace guidance
+} // namespace extractor
+} // namespace osrm
+
+#endif // OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
diff --git a/include/extractor/guidance/roundabout_handler.hpp b/include/extractor/guidance/roundabout_handler.hpp
index 055982a..9aee769 100644
--- a/include/extractor/guidance/roundabout_handler.hpp
+++ b/include/extractor/guidance/roundabout_handler.hpp
@@ -3,8 +3,10 @@
 
 #include "extractor/compressed_edge_container.hpp"
 #include "extractor/guidance/intersection.hpp"
+#include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/guidance/intersection_handler.hpp"
 #include "extractor/guidance/roundabout_type.hpp"
+#include "extractor/profile_properties.hpp"
 #include "extractor/query_node.hpp"
 
 #include "util/name_table.hpp"
@@ -42,9 +44,11 @@ class RoundaboutHandler : public IntersectionHandler
                       const std::vector<QueryNode> &node_info_list,
                       const CompressedEdgeContainer &compressed_edge_container,
                       const util::NameTable &name_table,
-                      const SuffixTable &street_name_suffix_table);
+                      const SuffixTable &street_name_suffix_table,
+                      const ProfileProperties &profile_properties,
+                      const IntersectionGenerator &intersection_generator);
 
-    ~RoundaboutHandler() override final;
+    ~RoundaboutHandler() override final = default;
 
     // check whether the handler can actually handle the intersection
     bool canProcess(const NodeID from_nid,
@@ -81,6 +85,7 @@ class RoundaboutHandler : public IntersectionHandler
     bool qualifiesAsRoundaboutIntersection(const std::set<NodeID> &roundabout_nodes) const;
 
     const CompressedEdgeContainer &compressed_edge_container;
+    const ProfileProperties &profile_properties;
 };
 
 } // namespace guidance
diff --git a/include/extractor/guidance/motorway_handler.hpp b/include/extractor/guidance/sliproad_handler.hpp
similarity index 53%
copy from include/extractor/guidance/motorway_handler.hpp
copy to include/extractor/guidance/sliproad_handler.hpp
index e8830aa..462b35d 100644
--- a/include/extractor/guidance/motorway_handler.hpp
+++ b/include/extractor/guidance/sliproad_handler.hpp
@@ -1,13 +1,16 @@
-#ifndef OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_
-#define OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_
+#ifndef OSRM_EXTRACTOR_GUIDANCE_SLIPROAD_HANDLER_HPP_
+#define OSRM_EXTRACTOR_GUIDANCE_SLIPROAD_HANDLER_HPP_
 
 #include "extractor/guidance/intersection.hpp"
+#include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/guidance/intersection_handler.hpp"
 #include "extractor/query_node.hpp"
 
 #include "util/name_table.hpp"
 #include "util/node_based_graph.hpp"
 
+#include <cstddef>
+#include <utility>
 #include <vector>
 
 namespace osrm
@@ -19,36 +22,30 @@ namespace guidance
 
 // Intersection handlers deal with all issues related to intersections.
 // They assign appropriate turn operations to the TurnOperations.
-class MotorwayHandler : public IntersectionHandler
+class SliproadHandler : public IntersectionHandler
 {
   public:
-    MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
+    SliproadHandler(const IntersectionGenerator &intersection_generator,
+                    const util::NodeBasedDynamicGraph &node_based_graph,
                     const std::vector<QueryNode> &node_info_list,
                     const util::NameTable &name_table,
                     const SuffixTable &street_name_suffix_table);
-    ~MotorwayHandler() override final;
+
+    ~SliproadHandler() override final = default;
 
     // check whether the handler can actually handle the intersection
-    bool canProcess(const NodeID nid,
-                    const EdgeID via_eid,
-                    const Intersection &intersection) const override final;
+    bool canProcess(const NodeID /*nid*/,
+                    const EdgeID /*via_eid*/,
+                    const Intersection & /*intersection*/) const override final;
 
     // process the intersection
     Intersection operator()(const NodeID nid,
                             const EdgeID via_eid,
                             Intersection intersection) const override final;
-
-  private:
-    Intersection handleSliproads(const NodeID intersection_node_id,
-                                 Intersection intersection) const;
-    Intersection fromMotorway(const EdgeID via_edge, Intersection intersection) const;
-    Intersection fromRamp(const EdgeID via_edge, Intersection intersection) const;
-
-    Intersection fallback(Intersection intersection) const;
 };
 
 } // namespace guidance
 } // namespace extractor
 } // namespace osrm
 
-#endif /*OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_*/
+#endif /*OSRM_EXTRACTOR_GUIDANCE_SLIPROAD_HANDLER_HPP_*/
diff --git a/include/extractor/guidance/toolkit.hpp b/include/extractor/guidance/toolkit.hpp
index 611cf17..b505590 100644
--- a/include/extractor/guidance/toolkit.hpp
+++ b/include/extractor/guidance/toolkit.hpp
@@ -1,6 +1,7 @@
 #ifndef OSRM_GUIDANCE_TOOLKIT_HPP_
 #define OSRM_GUIDANCE_TOOLKIT_HPP_
 
+#include "util/attributes.hpp"
 #include "util/bearing.hpp"
 #include "util/coordinate.hpp"
 #include "util/coordinate_calculation.hpp"
@@ -10,9 +11,7 @@
 
 #include "extractor/compressed_edge_container.hpp"
 #include "extractor/query_node.hpp"
-#include "extractor/suffix_table.hpp"
 
-#include "extractor/guidance/classification_data.hpp"
 #include "extractor/guidance/discrete_angle.hpp"
 #include "extractor/guidance/intersection.hpp"
 #include "extractor/guidance/turn_instruction.hpp"
@@ -26,8 +25,8 @@
 #include <utility>
 
 #include <boost/algorithm/string.hpp>
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/functional/hash.hpp>
+#include <boost/tokenizer.hpp>
 
 namespace osrm
 {
@@ -46,12 +45,6 @@ using util::guidance::leavesRoundabout;
 namespace detail
 {
 const constexpr double DESIRED_SEGMENT_LENGTH = 10.0;
-const constexpr bool shiftable_ccw[] = {false, true, true, false, false, true, true, false};
-const constexpr bool shiftable_cw[] = {false, false, true, true, false, false, true, true};
-const constexpr std::uint8_t modifier_bounds[detail::num_direction_modifiers] = {
-    0, 36, 93, 121, 136, 163, 220, 255};
-
-const constexpr double discrete_angle_step_size = 360. / 24;
 
 template <typename IteratorType>
 util::Coordinate
@@ -156,295 +149,10 @@ getRepresentativeCoordinate(const NodeID from_node,
     }
 }
 
-// shift an instruction around the degree circle in CCW order
-inline DirectionModifier::Enum forcedShiftCCW(const DirectionModifier::Enum modifier)
-{
-    return static_cast<DirectionModifier::Enum>((static_cast<std::uint32_t>(modifier) + 1) %
-                                                detail::num_direction_modifiers);
-}
-
-inline DirectionModifier::Enum shiftCCW(const DirectionModifier::Enum modifier)
-{
-    if (detail::shiftable_ccw[static_cast<int>(modifier)])
-        return forcedShiftCCW(modifier);
-    else
-        return modifier;
-}
-
-// shift an instruction around the degree circle in CW order
-inline DirectionModifier::Enum forcedShiftCW(const DirectionModifier::Enum modifier)
-{
-    return static_cast<DirectionModifier::Enum>(
-        (static_cast<std::uint32_t>(modifier) + detail::num_direction_modifiers - 1) %
-        detail::num_direction_modifiers);
-}
-
-inline DirectionModifier::Enum shiftCW(const DirectionModifier::Enum modifier)
-{
-    if (detail::shiftable_cw[static_cast<int>(modifier)])
-        return forcedShiftCW(modifier);
-    else
-        return modifier;
-}
-
-inline bool isBasic(const TurnType::Enum type)
-{
-    return type == TurnType::Turn || type == TurnType::EndOfRoad;
-}
-
-inline bool isUturn(const TurnInstruction instruction)
-{
-    return isBasic(instruction.type) && instruction.direction_modifier == DirectionModifier::UTurn;
-}
-
-inline bool resolve(TurnInstruction &to_resolve, const TurnInstruction neighbor, bool resolve_cw)
-{
-    const auto shifted_turn = resolve_cw ? shiftCW(to_resolve.direction_modifier)
-                                         : shiftCCW(to_resolve.direction_modifier);
-    if (shifted_turn == neighbor.direction_modifier ||
-        shifted_turn == to_resolve.direction_modifier)
-        return false;
-
-    to_resolve.direction_modifier = shifted_turn;
-    return true;
-}
-
-inline bool resolveTransitive(TurnInstruction &first,
-                              TurnInstruction &second,
-                              const TurnInstruction third,
-                              bool resolve_cw)
-{
-    if (resolve(second, third, resolve_cw))
-    {
-        first.direction_modifier =
-            resolve_cw ? shiftCW(first.direction_modifier) : shiftCCW(first.direction_modifier);
-        return true;
-    }
-    return false;
-}
-
-inline bool isSlightTurn(const TurnInstruction turn)
-{
-    return (isBasic(turn.type) || turn.type == TurnType::NoTurn) &&
-           (turn.direction_modifier == DirectionModifier::Straight ||
-            turn.direction_modifier == DirectionModifier::SlightRight ||
-            turn.direction_modifier == DirectionModifier::SlightLeft);
-}
-
-inline bool isSlightModifier(const DirectionModifier::Enum direction_modifier)
-{
-    return (direction_modifier == DirectionModifier::Straight ||
-            direction_modifier == DirectionModifier::SlightRight ||
-            direction_modifier == DirectionModifier::SlightLeft);
-}
-
-inline bool isSharpTurn(const TurnInstruction turn)
-{
-    return isBasic(turn.type) && (turn.direction_modifier == DirectionModifier::SharpLeft ||
-                                  turn.direction_modifier == DirectionModifier::SharpRight);
-}
-
-inline bool isStraight(const TurnInstruction turn)
-{
-    return (isBasic(turn.type) || turn.type == TurnType::NoTurn) &&
-           turn.direction_modifier == DirectionModifier::Straight;
-}
-
-inline bool isConflict(const TurnInstruction first, const TurnInstruction second)
-{
-    return (first.type == second.type && first.direction_modifier == second.direction_modifier) ||
-           (isStraight(first) && isStraight(second));
-}
-
-inline DiscreteAngle discretizeAngle(const double angle)
-{
-    BOOST_ASSERT(angle >= 0. && angle <= 360.);
-    return DiscreteAngle(static_cast<std::uint8_t>(
-        (angle + 0.5 * detail::discrete_angle_step_size) / detail::discrete_angle_step_size));
-}
-
-inline double angleFromDiscreteAngle(const DiscreteAngle angle)
-{
-    return static_cast<double>(angle) * detail::discrete_angle_step_size +
-           0.5 * detail::discrete_angle_step_size;
-}
-
-inline double getAngularPenalty(const double angle, DirectionModifier::Enum modifier)
-{
-    // these are not aligned with getTurnDirection but represent an ideal center
-    const double center[] = {0, 45, 90, 135, 180, 225, 270, 315};
-    return angularDeviation(center[static_cast<int>(modifier)], angle);
-}
-
-inline double getTurnConfidence(const double angle, TurnInstruction instruction)
-{
-
-    // special handling of U-Turns and Roundabout
-    if (!isBasic(instruction.type) || instruction.direction_modifier == DirectionModifier::UTurn)
-        return 1.0;
-
-    const double deviations[] = {0, 45, 50, 30, 20, 30, 50, 45};
-    const double difference = getAngularPenalty(angle, instruction.direction_modifier);
-    const double max_deviation = deviations[static_cast<int>(instruction.direction_modifier)];
-    return 1.0 - (difference / max_deviation) * (difference / max_deviation);
-}
-
-inline bool canBeSuppressed(const TurnType::Enum type)
-{
-    if (type == TurnType::Turn)
-        return true;
-    return false;
-}
-
-inline bool isLowPriorityRoadClass(const FunctionalRoadClass road_class)
-{
-    return road_class == FunctionalRoadClass::LOW_PRIORITY_ROAD ||
-           road_class == FunctionalRoadClass::SERVICE;
-}
-
-inline bool isDistinct(const DirectionModifier::Enum first, const DirectionModifier::Enum second)
-{
-    if ((first + 1) % detail::num_direction_modifiers == second)
-        return false;
-
-    if ((second + 1) % detail::num_direction_modifiers == first)
-        return false;
-
-    return true;
-}
-
-inline std::pair<std::string, std::string> getPrefixAndSuffix(const std::string &data)
-{
-    const auto suffix_pos = data.find_last_of(' ');
-    if (suffix_pos == std::string::npos)
-        return {};
-
-    const auto prefix_pos = data.find_first_of(' ');
-    auto result = std::make_pair(data.substr(0, prefix_pos), data.substr(suffix_pos + 1));
-    boost::to_lower(result.first);
-    boost::to_lower(result.second);
-    return result;
-}
-
-inline bool requiresNameAnnounced(const std::string &from,
-                                  const std::string &to,
-                                  const SuffixTable &suffix_table)
-{
-    // first is empty and the second is not
-    if (from.empty() && !to.empty())
-        return true;
-
-    // FIXME, handle in profile to begin with?
-    // this uses the encoding of references in the profile, which is very BAD
-    // Input for this function should be a struct separating streetname, suffix (e.g. road,
-    // boulevard, North, West ...), and a list of references
-    std::string from_name;
-    std::string from_ref;
-    std::string to_name;
-    std::string to_ref;
-
-    // Split from the format "{name} ({ref})" -> name, ref
-    auto split = [](const std::string &name, std::string &out_name, std::string &out_ref) {
-        const auto ref_begin = name.find_first_of('(');
-        if (ref_begin != std::string::npos)
-        {
-            if (ref_begin != 0)
-                out_name = name.substr(0, ref_begin - 1);
-            const auto ref_end = name.find_first_of(')');
-            out_ref = name.substr(ref_begin + 1, ref_end - ref_begin - 1);
-        }
-        else
-        {
-            out_name = name;
-        }
-    };
-
-    split(from, from_name, from_ref);
-    split(to, to_name, to_ref);
-
-    // check similarity of names
-    const auto names_are_empty = from_name.empty() && to_name.empty();
-    const auto name_is_contained =
-        boost::starts_with(from_name, to_name) || boost::starts_with(to_name, from_name);
-
-    const auto checkForPrefixOrSuffixChange =
-        [](const std::string &first, const std::string &second, const SuffixTable &suffix_table) {
-
-            const auto first_prefix_and_suffixes = getPrefixAndSuffix(first);
-            const auto second_prefix_and_suffixes = getPrefixAndSuffix(second);
-            // reverse strings, get suffices and reverse them to get prefixes
-            const auto checkTable = [&](const std::string str) {
-                return str.empty() || suffix_table.isSuffix(str);
-            };
-
-            const bool is_prefix_change = [&]() -> bool {
-                if (!checkTable(first_prefix_and_suffixes.first))
-                    return false;
-                if (!checkTable(first_prefix_and_suffixes.first))
-                    return false;
-                return !first.compare(first_prefix_and_suffixes.first.length(),
-                                      std::string::npos,
-                                      second,
-                                      second_prefix_and_suffixes.first.length(),
-                                      std::string::npos);
-            }();
-
-            const bool is_suffix_change = [&]() -> bool {
-                if (!checkTable(first_prefix_and_suffixes.second))
-                    return false;
-                if (!checkTable(first_prefix_and_suffixes.second))
-                    return false;
-                return !first.compare(0,
-                                      first.length() - first_prefix_and_suffixes.second.length(),
-                                      second,
-                                      0,
-                                      second.length() - second_prefix_and_suffixes.second.length());
-            }();
-
-            return is_prefix_change || is_suffix_change;
-        };
-
-    const auto is_suffix_change = checkForPrefixOrSuffixChange(from_name, to_name, suffix_table);
-    const auto names_are_equal = from_name == to_name || name_is_contained || is_suffix_change;
-    const auto name_is_removed = !from_name.empty() && to_name.empty();
-    // references are contained in one another
-    const auto refs_are_empty = from_ref.empty() && to_ref.empty();
-    const auto ref_is_contained =
-        from_ref.empty() || to_ref.empty() ||
-        (from_ref.find(to_ref) != std::string::npos || to_ref.find(from_ref) != std::string::npos);
-    const auto ref_is_removed = !from_ref.empty() && to_ref.empty();
-
-    const auto obvious_change =
-        (names_are_empty && refs_are_empty) || (names_are_equal && ref_is_contained) ||
-        (names_are_equal && refs_are_empty) || (ref_is_contained && name_is_removed) ||
-        (names_are_equal && ref_is_removed) || is_suffix_change;
-
-    return !obvious_change;
-}
-
-inline int getPriority(const FunctionalRoadClass road_class)
-{
-    // The road priorities indicate which roads can bee seen as more or less equal.
-    // They are used in Fork-Discovery. Possibly should be moved to profiles post v5?
-    // A fork can happen between road types that are at most 1 priority apart from each other
-    const constexpr int road_priority[] = {
-        10, 0, 10, 2, 10, 4, 10, 6, 10, 8, 10, 11, 10, 12, 10, 14};
-    return road_priority[static_cast<int>(road_class)];
-}
-
-inline bool canBeSeenAsFork(const FunctionalRoadClass first, const FunctionalRoadClass second)
-{
-    // forks require similar road categories
-    // Based on the priorities assigned above, we can set forks only if the road priorities match
-    // closely.
-    // Potentially we could include features like number of lanes here and others?
-    // Should also be moved to profiles
-    return std::abs(getPriority(first) - getPriority(second)) <= 1;
-}
-
 // To simplify handling of Left/Right hand turns, we can mirror turns and write an intersection
 // handler only for one side. The mirror function turns a left-hand turn in a equivalent right-hand
 // turn and vice versa.
+OSRM_ATTR_WARN_UNUSED
 inline ConnectedRoad mirror(ConnectedRoad road)
 {
     const constexpr DirectionModifier::Enum mirrored_modifiers[] = {DirectionModifier::UTurn,
@@ -481,8 +189,11 @@ inline bool hasRoundaboutType(const TurnInstruction instruction)
                                                     TurnType::EnterRoundaboutIntersectionAtExit,
                                                     TurnType::ExitRoundaboutIntersection,
                                                     TurnType::StayOnRoundabout};
-    const auto valid_end = valid_types + 13;
-    return std::find(valid_types, valid_end, instruction.type) != valid_end;
+
+    const auto *first = valid_types;
+    const auto *last = first + sizeof(valid_types) / sizeof(valid_types[0]);
+
+    return std::find(first, last, instruction.type) != last;
 }
 
 // Public service vehicle lanes and similar can introduce additional lanes into the lane string that
@@ -494,6 +205,7 @@ inline bool hasRoundaboutType(const TurnInstruction instruction)
 // will be corrected to left|throught, since the final lane is not drivable.
 // This is in contrast to a situation with lanes:psv:forward=0 (or not set) where left|through|
 // represents left|through|through
+OSRM_ATTR_WARN_UNUSED
 inline std::string
 trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t count_right)
 {
@@ -503,7 +215,7 @@ trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t co
         for (std::int32_t i = 0; i < count_left; ++i)
             // this is adjusted for our fake pipe. The moment cucumber can handle multiple escaped
             // pipes, the '&' part can be removed
-            if (lane_string[i] != '|' && lane_string[i] != '&')
+            if (lane_string[i] != '|')
             {
                 sane = false;
                 break;
@@ -521,7 +233,7 @@ trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t co
              itr != lane_string.rend() && itr != lane_string.rbegin() + count_right;
              ++itr)
         {
-            if (*itr != '|' && *itr != '&')
+            if (*itr != '|')
             {
                 sane = false;
                 break;
@@ -533,6 +245,46 @@ trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t co
     return lane_string;
 }
 
+// https://github.com/Project-OSRM/osrm-backend/issues/2638
+// It can happen that some lanes are not drivable by car. Here we handle this tagging scheme
+// (vehicle:lanes) to filter out not-allowed roads
+// lanes=3
+// turn:lanes=left|through|through|right
+// vehicle:lanes=yes|yes|no|yes
+// bicycle:lanes=yes|no|designated|yes
+OSRM_ATTR_WARN_UNUSED
+inline std::string applyAccessTokens(std::string lane_string, const std::string &access_tokens)
+{
+    typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
+    boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
+    tokenizer tokens(lane_string, sep);
+    tokenizer access(access_tokens, sep);
+
+    // strings don't match, don't do anything
+    if (std::distance(std::begin(tokens), std::end(tokens)) !=
+        std::distance(std::begin(access), std::end(access)))
+        return lane_string;
+
+    std::string result_string = "";
+    const static std::string yes = "yes";
+
+    for (auto token_itr = std::begin(tokens), access_itr = std::begin(access);
+         token_itr != std::end(tokens);
+         ++token_itr, ++access_itr)
+    {
+        if (*access_itr == yes)
+        {
+            // we have to add this in front, because the next token could be invalid. Doing this on
+            // non-empty strings makes sure that the token string will be valid in the end
+            if (!result_string.empty())
+                result_string += '|';
+
+            result_string += *token_itr;
+        }
+    }
+    return result_string;
+}
+
 } // namespace guidance
 } // namespace extractor
 } // namespace osrm
diff --git a/include/extractor/guidance/turn_analysis.hpp b/include/extractor/guidance/turn_analysis.hpp
index 5642ea5..730c7e9 100644
--- a/include/extractor/guidance/turn_analysis.hpp
+++ b/include/extractor/guidance/turn_analysis.hpp
@@ -6,6 +6,7 @@
 #include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/guidance/motorway_handler.hpp"
 #include "extractor/guidance/roundabout_handler.hpp"
+#include "extractor/guidance/sliproad_handler.hpp"
 #include "extractor/guidance/toolkit.hpp"
 #include "extractor/guidance/turn_classification.hpp"
 #include "extractor/guidance/turn_handler.hpp"
@@ -41,7 +42,8 @@ class TurnAnalysis
                  const std::unordered_set<NodeID> &barrier_nodes,
                  const CompressedEdgeContainer &compressed_edge_container,
                  const util::NameTable &name_table,
-                 const SuffixTable &street_name_suffix_table);
+                 const SuffixTable &street_name_suffix_table,
+                 const ProfileProperties &profile_properties);
 
     // the entry into the turn analysis
     Intersection getIntersection(const NodeID from_node, const EdgeID via_eid) const;
@@ -59,13 +61,11 @@ class TurnAnalysis
     const RoundaboutHandler roundabout_handler;
     const MotorwayHandler motorway_handler;
     const TurnHandler turn_handler;
+    const SliproadHandler sliproad_handler;
 
     // Utility function, setting basic turn types. Prepares for normal turn handling.
     Intersection
     setTurnTypes(const NodeID from, const EdgeID via_edge, Intersection intersection) const;
-
-    Intersection handleSliproads(const NodeID intersection_node_id,
-                                 Intersection intersection) const;
 }; // class TurnAnalysis
 
 } // namespace guidance
diff --git a/include/extractor/guidance/turn_handler.hpp b/include/extractor/guidance/turn_handler.hpp
index e49e7e0..69feae1 100644
--- a/include/extractor/guidance/turn_handler.hpp
+++ b/include/extractor/guidance/turn_handler.hpp
@@ -2,9 +2,11 @@
 #define OSRM_EXTRACTOR_GUIDANCE_TURN_HANDLER_HPP_
 
 #include "extractor/guidance/intersection.hpp"
+#include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/guidance/intersection_handler.hpp"
 #include "extractor/query_node.hpp"
 
+#include "util/attributes.hpp"
 #include "util/name_table.hpp"
 #include "util/node_based_graph.hpp"
 
@@ -27,8 +29,10 @@ class TurnHandler : public IntersectionHandler
     TurnHandler(const util::NodeBasedDynamicGraph &node_based_graph,
                 const std::vector<QueryNode> &node_info_list,
                 const util::NameTable &name_table,
-                const SuffixTable &street_name_suffix_table);
-    ~TurnHandler() override final;
+                const SuffixTable &street_name_suffix_table,
+                const IntersectionGenerator &intersection_generator);
+
+    ~TurnHandler() override final = default;
 
     // check whether the handler can actually handle the intersection
     bool canProcess(const NodeID nid,
@@ -41,28 +45,38 @@ class TurnHandler : public IntersectionHandler
                             Intersection intersection) const override final;
 
   private:
+    bool isObviousOfTwo(const EdgeID via_edge,
+                        const ConnectedRoad &road,
+                        const ConnectedRoad &other) const;
     // Dead end.
+    OSRM_ATTR_WARN_UNUSED
     Intersection handleOneWayTurn(Intersection intersection) const;
 
     // Mode Changes, new names...
+    OSRM_ATTR_WARN_UNUSED
     Intersection handleTwoWayTurn(const EdgeID via_edge, Intersection intersection) const;
 
     // Forks, T intersections and similar
+    OSRM_ATTR_WARN_UNUSED
     Intersection handleThreeWayTurn(const EdgeID via_edge, Intersection intersection) const;
 
     // Handling of turns larger then degree three
+    OSRM_ATTR_WARN_UNUSED
     Intersection handleComplexTurn(const EdgeID via_edge, Intersection intersection) const;
 
     void
     handleDistinctConflict(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const;
 
     // Classification
-    std::size_t findObviousTurn(const EdgeID via_edge, const Intersection &intersection) const;
-    std::pair<std::size_t, std::size_t> findFork(const Intersection &intersection) const;
+    std::pair<std::size_t, std::size_t> findFork(const EdgeID via_edge,
+                                                 const Intersection &intersection) const;
 
+    OSRM_ATTR_WARN_UNUSED
     Intersection assignLeftTurns(const EdgeID via_edge,
                                  Intersection intersection,
                                  const std::size_t starting_at) const;
+
+    OSRM_ATTR_WARN_UNUSED
     Intersection assignRightTurns(const EdgeID via_edge,
                                   Intersection intersection,
                                   const std::size_t up_to) const;
diff --git a/include/extractor/guidance/turn_instruction.hpp b/include/extractor/guidance/turn_instruction.hpp
index c85c0d2..a6a1ead 100644
--- a/include/extractor/guidance/turn_instruction.hpp
+++ b/include/extractor/guidance/turn_instruction.hpp
@@ -16,12 +16,6 @@ namespace extractor
 namespace guidance
 {
 
-namespace detail
-{
-// inclusive bounds for turn modifiers
-const constexpr uint8_t num_direction_modifiers = 8;
-} // detail
-
 // direction modifiers based on angle
 namespace DirectionModifier
 {
@@ -34,6 +28,7 @@ const constexpr Enum Straight = 4;
 const constexpr Enum SlightLeft = 5;
 const constexpr Enum Left = 6;
 const constexpr Enum SharpLeft = 7;
+const constexpr Enum MaxDirectionModifier = 8;
 }
 
 namespace TurnType
@@ -70,6 +65,7 @@ const constexpr Enum ExitRoundaboutIntersection = 24; // Exiting a small Roundab
 const constexpr Enum StayOnRoundabout = 25; // Continue on Either a small or a large Roundabout
 const constexpr Enum Sliproad =
     26; // Something that looks like a ramp, but is actually just a small sliproad
+const constexpr Enum MaxTurnType = 27; // Special value for static asserts
 }
 
 // turn angle in 1.40625 degree -> 128 == 180 degree
diff --git a/include/extractor/guidance/turn_lane_augmentation.hpp b/include/extractor/guidance/turn_lane_augmentation.hpp
index 81de8f9..1865939 100644
--- a/include/extractor/guidance/turn_lane_augmentation.hpp
+++ b/include/extractor/guidance/turn_lane_augmentation.hpp
@@ -3,6 +3,7 @@
 
 #include "extractor/guidance/intersection.hpp"
 #include "extractor/guidance/turn_lane_data.hpp"
+#include "util/attributes.hpp"
 
 namespace osrm
 {
@@ -13,6 +14,7 @@ namespace guidance
 namespace lanes
 {
 
+OSRM_ATTR_WARN_UNUSED
 LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data,
                                            const Intersection &intersection);
 
diff --git a/include/extractor/guidance/turn_lane_data.hpp b/include/extractor/guidance/turn_lane_data.hpp
index f1a7d7e..113cda1 100644
--- a/include/extractor/guidance/turn_lane_data.hpp
+++ b/include/extractor/guidance/turn_lane_data.hpp
@@ -1,8 +1,9 @@
 #ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_DATA_HPP_
 #define OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_DATA_HPP_
 
-#include "util/typedefs.hpp"
 #include "extractor/guidance/turn_lane_types.hpp"
+#include "util/attributes.hpp"
+#include "util/typedefs.hpp"
 #include <string>
 #include <vector>
 
@@ -21,12 +22,17 @@ struct TurnLaneData
     LaneID from;
     LaneID to;
 
+    // a temporary data entry that does not need to be assigned to an entry.
+    // This is the case in situations that use partition and require the entry to perform the
+    // one-to-one mapping.
+    bool suppress_assignment;
     bool operator<(const TurnLaneData &other) const;
 };
 typedef std::vector<TurnLaneData> LaneDataVector;
 
 // convertes a string given in the OSM format into a TurnLaneData vector
-LaneDataVector laneDataFromDescription(const TurnLaneDescription &turn_lane_description);
+OSRM_ATTR_WARN_UNUSED
+LaneDataVector laneDataFromDescription(TurnLaneDescription turn_lane_description);
 
 // Locate A Tag in a lane data vector (if multiple tags are set, the first one found is returned)
 LaneDataVector::const_iterator findTag(const TurnLaneType::Mask tag, const LaneDataVector &data);
@@ -34,7 +40,6 @@ LaneDataVector::iterator findTag(const TurnLaneType::Mask tag, LaneDataVector &d
 
 // Returns true if any of the queried tags is contained
 bool hasTag(const TurnLaneType::Mask tag, const LaneDataVector &data);
-
 } // namespace lane_data_generation
 
 } // namespace guidance
diff --git a/include/extractor/guidance/turn_lane_handler.hpp b/include/extractor/guidance/turn_lane_handler.hpp
index 2fb5416..12064e4 100644
--- a/include/extractor/guidance/turn_lane_handler.hpp
+++ b/include/extractor/guidance/turn_lane_handler.hpp
@@ -8,11 +8,14 @@
 #include "extractor/guidance/turn_lane_types.hpp"
 #include "extractor/query_node.hpp"
 
+#include "util/attributes.hpp"
 #include "util/guidance/turn_lanes.hpp"
 #include "util/name_table.hpp"
 #include "util/node_based_graph.hpp"
 #include "util/typedefs.hpp"
 
+#include <atomic>
+#include <cstddef>
 #include <cstdint>
 #include <map>
 #include <string>
@@ -26,57 +29,118 @@ namespace extractor
 namespace guidance
 {
 
-// Given an Intersection, the graph to access the data and  the turn lanes, the turn lane matcher
+// Given an Intersection, the graph to access the data and the turn lanes, the turn lane matcher
 // assigns appropriate turn tupels to the different turns.
 namespace lanes
 {
+
+namespace
+{
+typedef enum TurnLaneScenario {
+    SIMPLE,             // a straightforward assignment
+    PARTITION_LOCAL,    // an assignment that requires partitioning, using local turns
+    SIMPLE_PREVIOUS,    // an assignemtnn using the turns specified at the previous road (e.g.
+                        // traffic light, lanes not drawn up to the intersection)
+    PARTITION_PREVIOUS, // a set of lanes on a turn with a traffic island. The lanes for the
+                        // turn end at the previous turn (parts of it remain valid without being
+                        // shown again)
+    SLIPROAD, // Sliproads are simple assignments that, for better visual representation should
+              // include turns from other roads in their listings
+    MERGE,    // Merging Lanes
+    NONE,     // not a turn lane scenario at all
+    INVALID,  // some error might have occurred
+    UNKNOWN,  // UNKNOWN describes all cases that we are currently not able to handle
+    NUM_SCENARIOS
+} TurnLaneScenario;
+
+const constexpr char *scenario_names[] = {"Simple",
+                                          "Partition Local",
+                                          "Simple Previous",
+                                          "Partition Previous",
+                                          "Sliproad",
+                                          "Merge",
+                                          "None",
+                                          "Invalid",
+                                          "Unknown"};
+} // namespace
+
 class TurnLaneHandler
 {
   public:
     typedef std::vector<TurnLaneData> LaneDataVector;
 
     TurnLaneHandler(const util::NodeBasedDynamicGraph &node_based_graph,
-                    const std::vector<std::uint32_t> &turn_lane_offsets,
-                    const std::vector<TurnLaneType::Mask> &turn_lane_masks,
+                    std::vector<std::uint32_t> &turn_lane_offsets,
+                    std::vector<TurnLaneType::Mask> &turn_lane_masks,
+                    LaneDescriptionMap &lane_description_map,
                     const std::vector<QueryNode> &node_info_list,
-                    const TurnAnalysis &turn_analysis);
+                    const TurnAnalysis &turn_analysis,
+                    LaneDataIdMap &id_map);
 
-    Intersection assignTurnLanes(const NodeID at,
-                                 const EdgeID via_edge,
-                                 Intersection intersection,
-                                 LaneDataIdMap &id_map) const;
+    ~TurnLaneHandler();
+
+    OSRM_ATTR_WARN_UNUSED
+    Intersection assignTurnLanes(const NodeID at, const EdgeID via_edge, Intersection intersection);
 
   private:
+    mutable std::atomic<std::size_t> count_handled;
+    mutable std::atomic<std::size_t> count_called;
     // we need to be able to look at previous intersections to, in some cases, find the correct turn
     // lanes for a turn
     const util::NodeBasedDynamicGraph &node_based_graph;
-    const std::vector<std::uint32_t> &turn_lane_offsets;
-    const std::vector<TurnLaneType::Mask> &turn_lane_masks;
+    std::vector<std::uint32_t> &turn_lane_offsets;
+    std::vector<TurnLaneType::Mask> &turn_lane_masks;
+    LaneDescriptionMap &lane_description_map;
     const std::vector<QueryNode> &node_info_list;
     const TurnAnalysis &turn_analysis;
+    LaneDataIdMap &id_map;
+
+    // Find out which scenario we have to handle
+    TurnLaneScenario deduceScenario(const NodeID at,
+                                    const EdgeID via_edge,
+                                    const Intersection &intersection,
+                                    // Output Parameters to reduce repeated creation
+                                    LaneDescriptionID &lane_description_id,
+                                    LaneDataVector &lane_data,
+                                    NodeID &previous_node,
+                                    EdgeID &previous_id,
+                                    Intersection &previous_intersection,
+                                    LaneDataVector &previous_lane_data,
+                                    LaneDescriptionID &previous_description_id);
 
     // check whether we can handle an intersection
     bool isSimpleIntersection(const LaneDataVector &turn_lane_data,
                               const Intersection &intersection) const;
 
     // in case of a simple intersection, assign the lane entries
+    OSRM_ATTR_WARN_UNUSED
     Intersection simpleMatchTuplesToTurns(Intersection intersection,
                                           const LaneDataVector &lane_data,
-                                          const LaneDescriptionID lane_string_id,
-                                          LaneDataIdMap &id_map) const;
+                                          const LaneDescriptionID lane_string_id);
 
     // partition lane data into lane data relevant at current turn and at next turn
+    OSRM_ATTR_WARN_UNUSED
     std::pair<TurnLaneHandler::LaneDataVector, TurnLaneHandler::LaneDataVector> partitionLaneData(
         const NodeID at, LaneDataVector turn_lane_data, const Intersection &intersection) const;
 
-    // if the current intersections turn string is empty, we check whether there is an incoming
-    // intersection whose turns might be related to this current intersection
-    Intersection handleTurnAtPreviousIntersection(const NodeID at,
-                                                  const EdgeID via_edge,
-                                                  Intersection intersection,
-                                                  LaneDataIdMap &id_map) const;
+    // Sliproad turns have a separated lane to the right/left of other depicted lanes. These lanes
+    // are not necessarily separated clearly from the rest of the way. As a result, we combine both
+    // lane entries for our output, while performing the matching with the separated lanes only.
+    OSRM_ATTR_WARN_UNUSED
+    Intersection handleSliproadTurn(Intersection intersection,
+                                    const LaneDescriptionID lane_description_id,
+                                    LaneDataVector lane_data,
+                                    const Intersection &previous_intersection);
+
+    // get the lane data for an intersection
+    void extractLaneData(const EdgeID via_edge,
+                         LaneDescriptionID &lane_description_id,
+                         LaneDataVector &lane_data) const;
 };
 
+static_assert(sizeof(scenario_names) / sizeof(*scenario_names) == TurnLaneScenario::NUM_SCENARIOS,
+              "Number of scenarios needs to match the number of scenario names.");
+
 } // namespace lanes
 } // namespace guidance
 } // namespace extractor
diff --git a/include/extractor/guidance/turn_lane_matcher.hpp b/include/extractor/guidance/turn_lane_matcher.hpp
index 8dfcfe8..45c9f37 100644
--- a/include/extractor/guidance/turn_lane_matcher.hpp
+++ b/include/extractor/guidance/turn_lane_matcher.hpp
@@ -6,6 +6,7 @@
 #include "extractor/guidance/turn_instruction.hpp"
 #include "extractor/guidance/turn_lane_data.hpp"
 
+#include "util/attributes.hpp"
 #include "util/guidance/turn_lanes.hpp"
 #include "util/node_based_graph.hpp"
 
@@ -21,22 +22,27 @@ namespace lanes
 {
 
 // Translate Turn Lane Tags into a matching modifier
-DirectionModifier::Enum getMatchingModifier(const TurnLaneType::Mask &tag);
+DirectionModifier::Enum getMatchingModifier(const TurnLaneType::Mask tag);
 
 // check whether a match of a given tag and a turn instruction can be seen as valid
-bool isValidMatch(const TurnLaneType::Mask &tag, const TurnInstruction instruction);
+bool isValidMatch(const TurnLaneType::Mask tag, const TurnInstruction instruction);
 
 // localisation of the best possible match for a tag
-typename Intersection::const_iterator findBestMatch(const TurnLaneType::Mask &tag,
+typename Intersection::const_iterator findBestMatch(const TurnLaneType::Mask tag,
                                                     const Intersection &intersection);
-typename Intersection::const_iterator
-findBestMatchForReverse(const TurnLaneType::Mask &leftmost_tag, const Intersection &intersection);
+
+// the quality of a matching to decide between first/second possibility on segregated intersections
+double getMatchingQuality(const TurnLaneType::Mask tag, const ConnectedRoad &road);
+
+typename Intersection::const_iterator findBestMatchForReverse(const TurnLaneType::Mask leftmost_tag,
+                                                              const Intersection &intersection);
 
 // a match is trivial if all turns can be associated with their best match in a valid way and the
 // matches occur in order
 bool canMatchTrivially(const Intersection &intersection, const LaneDataVector &lane_data);
 
 // perform a trivial match on the turn lanes
+OSRM_ATTR_WARN_UNUSED
 Intersection triviallyMatchLanesToTurns(Intersection intersection,
                                         const LaneDataVector &lane_data,
                                         const util::NodeBasedDynamicGraph &node_based_graph,
diff --git a/include/extractor/guidance/turn_lane_types.hpp b/include/extractor/guidance/turn_lane_types.hpp
index 3a06a3b..dbd4c5b 100644
--- a/include/extractor/guidance/turn_lane_types.hpp
+++ b/include/extractor/guidance/turn_lane_types.hpp
@@ -5,14 +5,15 @@
 #include <cstddef>
 #include <cstdint>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <boost/assert.hpp>
-#include <boost/functional/hash_fwd.hpp>
+#include <boost/functional/hash.hpp>
 
+#include "util/json_container.hpp"
 #include "util/simple_logger.hpp"
 #include "util/typedefs.hpp"
-#include "util/json_container.hpp"
 
 namespace osrm
 {
@@ -89,12 +90,16 @@ struct TurnLaneDescription_hash
     std::size_t operator()(const TurnLaneDescription &lane_description) const
     {
         std::size_t seed = 0;
-        for (auto val : lane_description)
-            boost::hash_combine(seed, val);
+        boost::hash_range(seed, lane_description.begin(), lane_description.end());
         return seed;
     }
 };
 
+typedef std::unordered_map<guidance::TurnLaneDescription,
+                           LaneDescriptionID,
+                           guidance::TurnLaneDescription_hash>
+    LaneDescriptionMap;
+
 } // guidance
 } // extractor
 } // osrm
diff --git a/include/extractor/internal_extractor_edge.hpp b/include/extractor/internal_extractor_edge.hpp
index 376e587..a915bc2 100644
--- a/include/extractor/internal_extractor_edge.hpp
+++ b/include/extractor/internal_extractor_edge.hpp
@@ -7,7 +7,7 @@
 
 #include <boost/assert.hpp>
 
-#include "extractor/guidance/classification_data.hpp"
+#include "extractor/guidance/road_classification.hpp"
 #include "osrm/coordinate.hpp"
 #include <utility>
 
@@ -51,7 +51,7 @@ struct InternalExtractorEdge
                  TRAVEL_MODE_INACCESSIBLE,
                  false,
                  guidance::TurnLaneType::empty,
-                 guidance::RoadClassificationData())
+                 guidance::RoadClassification())
     {
     }
 
@@ -67,7 +67,7 @@ struct InternalExtractorEdge
                                    TravelMode travel_mode,
                                    bool is_split,
                                    LaneDescriptionID lane_description,
-                                   guidance::RoadClassificationData road_classification)
+                                   guidance::RoadClassification road_classification)
         : result(source,
                  target,
                  name_id,
@@ -107,7 +107,7 @@ struct InternalExtractorEdge
                                      TRAVEL_MODE_INACCESSIBLE,
                                      false,
                                      INVALID_LANE_DESCRIPTIONID,
-                                     guidance::RoadClassificationData());
+                                     guidance::RoadClassification());
     }
     static InternalExtractorEdge max_osm_value()
     {
@@ -123,7 +123,7 @@ struct InternalExtractorEdge
                                      TRAVEL_MODE_INACCESSIBLE,
                                      false,
                                      INVALID_LANE_DESCRIPTIONID,
-                                     guidance::RoadClassificationData());
+                                     guidance::RoadClassification());
     }
 
     static InternalExtractorEdge min_internal_value()
@@ -141,7 +141,6 @@ struct InternalExtractorEdge
         return v;
     }
 };
-
 }
 }
 
diff --git a/include/extractor/node_based_edge.hpp b/include/extractor/node_based_edge.hpp
index 1009776..f1a658b 100644
--- a/include/extractor/node_based_edge.hpp
+++ b/include/extractor/node_based_edge.hpp
@@ -4,7 +4,7 @@
 #include "extractor/travel_mode.hpp"
 #include "util/typedefs.hpp"
 
-#include "extractor/guidance/classification_data.hpp"
+#include "extractor/guidance/road_classification.hpp"
 
 namespace osrm
 {
@@ -27,7 +27,7 @@ struct NodeBasedEdge
                   TravelMode travel_mode,
                   bool is_split,
                   const LaneDescriptionID lane_description_id,
-                  guidance::RoadClassificationData road_classification);
+                  guidance::RoadClassification road_classification);
 
     bool operator<(const NodeBasedEdge &other) const;
 
@@ -43,7 +43,7 @@ struct NodeBasedEdge
     bool is_split : 1;
     TravelMode travel_mode : 4;
     LaneDescriptionID lane_description_id;
-    guidance::RoadClassificationData road_classification;
+    guidance::RoadClassification road_classification;
 };
 
 struct NodeBasedEdgeWithOSM : NodeBasedEdge
@@ -60,7 +60,7 @@ struct NodeBasedEdgeWithOSM : NodeBasedEdge
                          TravelMode travel_mode,
                          bool is_split,
                          const LaneDescriptionID lane_description_id,
-                         guidance::RoadClassificationData road_classification);
+                         guidance::RoadClassification road_classification);
 
     OSMNodeID osm_source_id;
     OSMNodeID osm_target_id;
@@ -87,7 +87,7 @@ inline NodeBasedEdge::NodeBasedEdge(NodeID source,
                                     TravelMode travel_mode,
                                     bool is_split,
                                     const LaneDescriptionID lane_description_id,
-                                    guidance::RoadClassificationData road_classification)
+                                    guidance::RoadClassification road_classification)
     : source(source), target(target), name_id(name_id), weight(weight), forward(forward),
       backward(backward), roundabout(roundabout), access_restricted(access_restricted),
       startpoint(startpoint), is_split(is_split), travel_mode(travel_mode),
@@ -112,20 +112,19 @@ inline bool NodeBasedEdge::operator<(const NodeBasedEdge &other) const
     return source < other.source;
 }
 
-inline NodeBasedEdgeWithOSM::NodeBasedEdgeWithOSM(
-    OSMNodeID source,
-    OSMNodeID target,
-    NodeID name_id,
-    EdgeWeight weight,
-    bool forward,
-    bool backward,
-    bool roundabout,
-    bool access_restricted,
-    bool startpoint,
-    TravelMode travel_mode,
-    bool is_split,
-    const LaneDescriptionID lane_description_id,
-    guidance::RoadClassificationData road_classification)
+inline NodeBasedEdgeWithOSM::NodeBasedEdgeWithOSM(OSMNodeID source,
+                                                  OSMNodeID target,
+                                                  NodeID name_id,
+                                                  EdgeWeight weight,
+                                                  bool forward,
+                                                  bool backward,
+                                                  bool roundabout,
+                                                  bool access_restricted,
+                                                  bool startpoint,
+                                                  TravelMode travel_mode,
+                                                  bool is_split,
+                                                  const LaneDescriptionID lane_description_id,
+                                                  guidance::RoadClassification road_classification)
     : NodeBasedEdge(SPECIAL_NODEID,
                     SPECIAL_NODEID,
                     name_id,
diff --git a/include/extractor/profile_properties.hpp b/include/extractor/profile_properties.hpp
index 0d07885..58a191d 100644
--- a/include/extractor/profile_properties.hpp
+++ b/include/extractor/profile_properties.hpp
@@ -12,7 +12,7 @@ struct ProfileProperties
 {
     ProfileProperties()
         : traffic_signal_penalty(0), u_turn_penalty(0), continue_straight_at_waypoint(true),
-          use_turn_restrictions(false)
+          use_turn_restrictions(false), left_hand_driving(false)
     {
     }
 
@@ -36,6 +36,7 @@ struct ProfileProperties
     int u_turn_penalty;
     bool continue_straight_at_waypoint;
     bool use_turn_restrictions;
+    bool left_hand_driving;
 };
 }
 }
diff --git a/include/extractor/query_node.hpp b/include/extractor/query_node.hpp
index f66d9c5..53c7ada 100644
--- a/include/extractor/query_node.hpp
+++ b/include/extractor/query_node.hpp
@@ -5,8 +5,8 @@
 
 #include "util/coordinate.hpp"
 
-#include <limits>
 #include <cstdint>
+#include <limits>
 
 namespace osrm
 {
@@ -15,8 +15,8 @@ namespace extractor
 
 struct QueryNode
 {
-    using key_type = OSMNodeID; // type of NodeID
-    using value_type = std::int32_t;     // type of lat,lons
+    using key_type = OSMNodeID;      // type of NodeID
+    using value_type = std::int32_t; // type of lat,lons
 
     explicit QueryNode(const util::FixedLongitude lon_,
                        const util::FixedLatitude lat_,
diff --git a/include/extractor/restriction_parser.hpp b/include/extractor/restriction_parser.hpp
index 77926c9..c5eede2 100644
--- a/include/extractor/restriction_parser.hpp
+++ b/include/extractor/restriction_parser.hpp
@@ -8,7 +8,6 @@
 #include <string>
 #include <vector>
 
-struct lua_State;
 namespace osmium
 {
 class Relation;
@@ -19,7 +18,7 @@ namespace osrm
 namespace extractor
 {
 
-struct ProfileProperties;
+class ScriptingEnvironment;
 
 /**
  * Parses the relations that represents turn restrictions.
@@ -42,11 +41,10 @@ struct ProfileProperties;
 class RestrictionParser
 {
   public:
-    RestrictionParser(lua_State *lua_state, const ProfileProperties &properties);
+    RestrictionParser(ScriptingEnvironment &scripting_environment);
     boost::optional<InputRestrictionContainer> TryParse(const osmium::Relation &relation) const;
 
   private:
-    void ReadRestrictionExceptions(lua_State *lua_state);
     bool ShouldIgnoreRestriction(const std::string &except_tag_string) const;
 
     std::vector<std::string> restriction_exceptions;
diff --git a/include/extractor/scripting_environment.hpp b/include/extractor/scripting_environment.hpp
index 16c8da1..a450b31 100644
--- a/include/extractor/scripting_environment.hpp
+++ b/include/extractor/scripting_environment.hpp
@@ -1,52 +1,70 @@
 #ifndef SCRIPTING_ENVIRONMENT_HPP
 #define SCRIPTING_ENVIRONMENT_HPP
 
+#include "extractor/guidance/turn_lane_types.hpp"
+#include "extractor/internal_extractor_edge.hpp"
 #include "extractor/profile_properties.hpp"
-#include "extractor/raster_source.hpp"
+#include "extractor/restriction.hpp"
 
-#include "util/lua_util.hpp"
+#include <osmium/memory/buffer.hpp>
+
+#include <boost/optional/optional.hpp>
+
+#include <tbb/concurrent_vector.h>
 
-#include <memory>
-#include <mutex>
 #include <string>
-#include <tbb/enumerable_thread_specific.h>
+#include <vector>
 
-struct lua_State;
+namespace osmium
+{
+class Node;
+class Way;
+}
 
 namespace osrm
 {
+
+namespace util
+{
+struct Coordinate;
+}
+
 namespace extractor
 {
 
+class RestrictionParser;
+struct ExtractionNode;
+struct ExtractionWay;
+
 /**
- * Creates a lua context and binds osmium way, node and relation objects and
- * ExtractionWay and ExtractionNode to lua objects.
- *
- * Each thread has its own lua state which is implemented with thread specific
- * storage from TBB.
+ * Abstract class that handles processing osmium ways, nodes and relation objects by applying
+ * user supplied profiles.
  */
 class ScriptingEnvironment
 {
   public:
-    struct Context
-    {
-        ProfileProperties properties;
-        SourceContainer sources;
-        util::LuaState state;
-    };
-
-    explicit ScriptingEnvironment(const std::string &file_name);
-
+    ScriptingEnvironment() = default;
     ScriptingEnvironment(const ScriptingEnvironment &) = delete;
     ScriptingEnvironment &operator=(const ScriptingEnvironment &) = delete;
+    virtual ~ScriptingEnvironment() = default;
 
-    Context &GetContex();
+    virtual const ProfileProperties &GetProfileProperties() = 0;
 
-  private:
-    void InitContext(Context &context);
-    std::mutex init_mutex;
-    std::string file_name;
-    tbb::enumerable_thread_specific<std::unique_ptr<Context>> script_contexts;
+    virtual std::vector<std::string> GetNameSuffixList() = 0;
+    virtual std::vector<std::string> GetExceptions() = 0;
+    virtual void SetupSources() = 0;
+    virtual int32_t GetTurnPenalty(double angle) = 0;
+    virtual void ProcessSegment(const osrm::util::Coordinate &source,
+                                const osrm::util::Coordinate &target,
+                                double distance,
+                                InternalExtractorEdge::WeightData &weight) = 0;
+    virtual void
+    ProcessElements(const std::vector<osmium::memory::Buffer::const_iterator> &osm_elements,
+                    const RestrictionParser &restriction_parser,
+                    tbb::concurrent_vector<std::pair<std::size_t, ExtractionNode>> &resulting_nodes,
+                    tbb::concurrent_vector<std::pair<std::size_t, ExtractionWay>> &resulting_ways,
+                    tbb::concurrent_vector<boost::optional<InputRestrictionContainer>>
+                        &resulting_restrictions) = 0;
 };
 }
 }
diff --git a/include/extractor/scripting_environment_lua.hpp b/include/extractor/scripting_environment_lua.hpp
new file mode 100644
index 0000000..c55a4b6
--- /dev/null
+++ b/include/extractor/scripting_environment_lua.hpp
@@ -0,0 +1,80 @@
+#ifndef SCRIPTING_ENVIRONMENT_LUA_HPP
+#define SCRIPTING_ENVIRONMENT_LUA_HPP
+
+#include "extractor/scripting_environment.hpp"
+
+#include "extractor/raster_source.hpp"
+
+#include "util/lua_util.hpp"
+
+#include <tbb/enumerable_thread_specific.h>
+
+#include <memory>
+#include <mutex>
+#include <string>
+
+struct lua_State;
+
+namespace osrm
+{
+namespace extractor
+{
+
+struct LuaScriptingContext final
+{
+    void processNode(const osmium::Node &, ExtractionNode &result);
+    void processWay(const osmium::Way &, ExtractionWay &result);
+
+    ProfileProperties properties;
+    SourceContainer sources;
+    util::LuaState state;
+
+    bool has_turn_penalty_function;
+    bool has_node_function;
+    bool has_way_function;
+    bool has_segment_function;
+};
+
+/**
+ * Creates a lua context and binds osmium way, node and relation objects and
+ * ExtractionWay and ExtractionNode to lua objects.
+ *
+ * Each thread has its own lua state which is implemented with thread specific
+ * storage from TBB.
+ */
+class LuaScriptingEnvironment final : public ScriptingEnvironment
+{
+  public:
+    explicit LuaScriptingEnvironment(const std::string &file_name);
+    ~LuaScriptingEnvironment() override = default;
+
+    const ProfileProperties &GetProfileProperties() override;
+
+    LuaScriptingContext &GetLuaContext();
+
+    std::vector<std::string> GetNameSuffixList() override;
+    std::vector<std::string> GetExceptions() override;
+    void SetupSources() override;
+    int32_t GetTurnPenalty(double angle) override;
+    void ProcessSegment(const osrm::util::Coordinate &source,
+                        const osrm::util::Coordinate &target,
+                        double distance,
+                        InternalExtractorEdge::WeightData &weight) override;
+    void
+    ProcessElements(const std::vector<osmium::memory::Buffer::const_iterator> &osm_elements,
+                    const RestrictionParser &restriction_parser,
+                    tbb::concurrent_vector<std::pair<std::size_t, ExtractionNode>> &resulting_nodes,
+                    tbb::concurrent_vector<std::pair<std::size_t, ExtractionWay>> &resulting_ways,
+                    tbb::concurrent_vector<boost::optional<InputRestrictionContainer>>
+                        &resulting_restrictions) override;
+
+  private:
+    void InitContext(LuaScriptingContext &context);
+    std::mutex init_mutex;
+    std::string file_name;
+    tbb::enumerable_thread_specific<std::unique_ptr<LuaScriptingContext>> script_contexts;
+};
+}
+}
+
+#endif /* SCRIPTING_ENVIRONMENT_LUA_HPP */
diff --git a/include/extractor/suffix_table.hpp b/include/extractor/suffix_table.hpp
index af5f016..7785a5e 100644
--- a/include/extractor/suffix_table.hpp
+++ b/include/extractor/suffix_table.hpp
@@ -4,19 +4,20 @@
 #include <string>
 #include <unordered_set>
 
-struct lua_State;
-
 namespace osrm
 {
 namespace extractor
 {
+
+class ScriptingEnvironment;
+
 // A table containing suffixes.
 // At the moment, it is only a front for an unordered set. At some point we might want to make it
 // country dependent and have it behave accordingly
 class SuffixTable final
 {
   public:
-    SuffixTable(lua_State *lua_state);
+    SuffixTable(ScriptingEnvironment &scripting_environment);
 
     // check whether a string is part of the know suffix list
     bool isSuffix(const std::string &possible_suffix) const;
@@ -24,6 +25,7 @@ class SuffixTable final
   private:
     std::unordered_set<std::string> suffix_set;
 };
+
 } /* namespace extractor */
 } /* namespace osrm */
 
diff --git a/include/engine/engine_config.hpp b/include/osrm/exception.hpp
similarity index 58%
copy from include/engine/engine_config.hpp
copy to include/osrm/exception.hpp
index eb62719..42548f5 100644
--- a/include/engine/engine_config.hpp
+++ b/include/osrm/exception.hpp
@@ -25,49 +25,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 */
 
-#ifndef ENGINE_CONFIG_HPP
-#define ENGINE_CONFIG_HPP
+#ifndef GLOBAL_EXCEPTION_HPP
+#define GLOBAL_EXCEPTION_HPP
 
-#include "storage/storage_config.hpp"
-
-#include <boost/filesystem/path.hpp>
-
-#include <string>
+#include "util/exception.hpp"
 
 namespace osrm
 {
-
-namespace engine
-{
-
-/**
- * Configures an OSRM instance.
- *
- * You can customize the storage OSRM uses for auxiliary files specifying a storage config.
- *
- * You can further set service constraints.
- * These are the maximum number of allowed locations (-1 for unlimited) for the services:
- *  - Trip
- *  - Route
- *  - Table
- *  - Match
- *
- * In addition, shared memory can be used for datasets loaded with osrm-datastore.
- *
- * \see OSRM, StorageConfig
- */
-struct EngineConfig final
-{
-    bool IsValid() const;
-
-    storage::StorageConfig storage_config;
-    int max_locations_trip = -1;
-    int max_locations_viaroute = -1;
-    int max_locations_distance_table = -1;
-    int max_locations_map_matching = -1;
-    bool use_shared_memory = true;
-};
-}
+using util::exception;
 }
 
-#endif // SERVER_CONFIG_HPP
+#endif
diff --git a/include/osrm/osrm.hpp b/include/osrm/osrm.hpp
index 1de8610..0bbc175 100644
--- a/include/osrm/osrm.hpp
+++ b/include/osrm/osrm.hpp
@@ -83,7 +83,7 @@ class OSRM final
      * \return Status indicating success for the query or failure
      * \see Status, RouteParameters and json::Object
      */
-    Status Route(const RouteParameters &parameters, json::Object &result);
+    Status Route(const RouteParameters &parameters, json::Object &result) const;
 
     /**
      * Distance tables for coordinates.
@@ -92,7 +92,7 @@ class OSRM final
      * \return Status indicating success for the query or failure
      * \see Status, TableParameters and json::Object
      */
-    Status Table(const TableParameters &parameters, json::Object &result);
+    Status Table(const TableParameters &parameters, json::Object &result) const;
 
     /**
      * Nearest street segment for coordinate.
@@ -101,7 +101,7 @@ class OSRM final
      * \return Status indicating success for the query or failure
      * \see Status, NearestParameters and json::Object
      */
-    Status Nearest(const NearestParameters &parameters, json::Object &result);
+    Status Nearest(const NearestParameters &parameters, json::Object &result) const;
 
     /**
      * Trip: shortest round trip between coordinates.
@@ -110,7 +110,7 @@ class OSRM final
      * \return Status indicating success for the query or failure
      * \see Status, TripParameters and json::Object
      */
-    Status Trip(const TripParameters &parameters, json::Object &result);
+    Status Trip(const TripParameters &parameters, json::Object &result) const;
 
     /**
      * Match: snaps noisy coordinate traces to the road network
@@ -119,7 +119,7 @@ class OSRM final
      * \return Status indicating success for the query or failure
      * \see Status, MatchParameters and json::Object
      */
-    Status Match(const MatchParameters &parameters, json::Object &result);
+    Status Match(const MatchParameters &parameters, json::Object &result) const;
 
     /**
      * Tile: vector tiles with internal graph representation
@@ -128,7 +128,7 @@ class OSRM final
      * \return Status indicating success for the query or failure
      * \see Status, TileParameters and json::Object
      */
-    Status Tile(const TileParameters &parameters, std::string &result);
+    Status Tile(const TileParameters &parameters, std::string &result) const;
 
   private:
     std::unique_ptr<engine::Engine> engine_;
diff --git a/include/util/attributes.hpp b/include/util/attributes.hpp
new file mode 100644
index 0000000..667d933
--- /dev/null
+++ b/include/util/attributes.hpp
@@ -0,0 +1,13 @@
+#ifndef OSRM_ATTRIBUTES_HPP_
+#define OSRM_ATTRIBUTES_HPP_
+
+// OSRM_ATTR_WARN_UNUSED - caller has to use function's return value
+// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
+
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define OSRM_ATTR_WARN_UNUSED __attribute__((warn_unused_result))
+#else
+#define OSRM_ATTR_WARN_UNUSED
+#endif
+
+#endif
diff --git a/include/util/bearing.hpp b/include/util/bearing.hpp
index 9300691..de308ad 100644
--- a/include/util/bearing.hpp
+++ b/include/util/bearing.hpp
@@ -67,7 +67,7 @@ inline bool CheckInBounds(const int A, const int B, const int range)
 
     if (range >= 180)
         return true;
-    if (range <= 0)
+    if (range < 0)
         return false;
 
     // Map both bearings into positive modulo 360 space
diff --git a/include/util/debug.hpp b/include/util/debug.hpp
index 4d6ba1f..28cfde2 100644
--- a/include/util/debug.hpp
+++ b/include/util/debug.hpp
@@ -5,6 +5,7 @@
 #include "extractor/guidance/turn_lane_data.hpp"
 #include "extractor/query_node.hpp"
 #include "engine/guidance/route_step.hpp"
+#include "util/node_based_graph.hpp"
 #include "util/typedefs.hpp"
 
 #include <iomanip>
@@ -22,16 +23,14 @@ inline void print(const engine::guidance::RouteStep &step)
     std::cout << static_cast<int>(step.maneuver.instruction.type) << " "
               << static_cast<int>(step.maneuver.instruction.direction_modifier) << "  "
               << static_cast<int>(step.maneuver.waypoint_type) << " "
-              << " Lanes: (" << static_cast<int>(step.maneuver.lanes.lanes_in_turn) << ", "
-              << static_cast<int>(step.maneuver.lanes.first_lane_from_the_right) << ")"
               << " Duration: " << step.duration << " Distance: " << step.distance
               << " Geometry: " << step.geometry_begin << " " << step.geometry_end
-              << " exit: " << step.maneuver.exit << " Intersections: " << step.intersections.size()
-              << " [";
+              << "\n\tIntersections: " << step.intersections.size() << " [";
 
     for (const auto &intersection : step.intersections)
     {
-        std::cout << "(bearings:";
+        std::cout << "(Lanes: " << static_cast<int>(intersection.lanes.lanes_in_turn) << " "
+                  << static_cast<int>(intersection.lanes.first_lane_from_the_right) << " bearings:";
         for (auto bearing : intersection.bearings)
             std::cout << " " << bearing;
         std::cout << ", entry: ";
@@ -54,6 +53,28 @@ inline void print(const std::vector<engine::guidance::RouteStep> &steps)
     }
 }
 
+inline void print(const extractor::guidance::Intersection &intersection)
+{
+    std::cout << "  Intersection:\n";
+    for (const auto &road : intersection)
+        std::cout << "\t" << toString(road) << "\n";
+    std::cout << std::flush;
+}
+
+inline void print(const NodeBasedDynamicGraph &node_based_graph,
+                  const extractor::guidance::Intersection &intersection)
+{
+    std::cout << "  Intersection:\n";
+    for (const auto &road : intersection)
+    {
+        std::cout << "\t" << toString(road) << "\n";
+        std::cout << "\t\t"
+                  << node_based_graph.GetEdgeData(road.turn.eid).road_classification.ToString()
+                  << "\n";
+    }
+    std::cout << std::flush;
+}
+
 inline void print(const extractor::guidance::lanes::LaneDataVector &turn_lane_data)
 {
     std::cout << " Tags:\n";
@@ -61,7 +82,9 @@ inline void print(const extractor::guidance::lanes::LaneDataVector &turn_lane_da
         std::cout << "\t" << entry.tag << "("
                   << extractor::guidance::TurnLaneType::toString(entry.tag)
                   << ") from: " << static_cast<int>(entry.from)
-                  << " to: " << static_cast<int>(entry.to) << "\n";
+                  << " to: " << static_cast<int>(entry.to)
+                  << " Can Be Suppresssed: " << (entry.suppress_assignment ? "true" : "false")
+                  << "\n";
     std::cout << std::flush;
 }
 
@@ -76,10 +99,7 @@ printTurnAssignmentData(const NodeID at,
     std::cout << std::setprecision(12) << toFloating(coordinate.lat) << " "
               << toFloating(coordinate.lon) << "\n";
 
-    std::cout << "  Intersection:\n";
-    for (const auto &road : intersection)
-        std::cout << "\t" << toString(road) << "\n";
-
+    print(intersection);
     // flushes as well
     print(turn_lane_data);
 }
diff --git a/include/util/exception.hpp b/include/util/exception.hpp
index 41efb0b..b180995 100644
--- a/include/util/exception.hpp
+++ b/include/util/exception.hpp
@@ -1,3 +1,30 @@
+/*
+
+Copyright (c) 2016, Project OSRM contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
 #ifndef OSRM_EXCEPTION_HPP
 #define OSRM_EXCEPTION_HPP
 
diff --git a/include/util/guidance/toolkit.hpp b/include/util/guidance/toolkit.hpp
index 12a3977..d6480f0 100644
--- a/include/util/guidance/toolkit.hpp
+++ b/include/util/guidance/toolkit.hpp
@@ -6,13 +6,19 @@
 #include "extractor/guidance/turn_instruction.hpp"
 #include "engine/guidance/route_step.hpp"
 #include "engine/phantom_node.hpp"
+#include "util/attributes.hpp"
 #include "util/guidance/bearing_class.hpp"
 #include "util/guidance/entry_class.hpp"
 #include "util/simple_logger.hpp"
 
 #include <algorithm>
+#include <string>
 #include <vector>
 
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/functional/hash.hpp>
+
 namespace osrm
 {
 namespace util
@@ -50,6 +56,7 @@ inline extractor::guidance::DirectionModifier::Enum getTurnDirection(const doubl
 }
 
 // swaps left <-> right modifier types
+OSRM_ATTR_WARN_UNUSED
 inline extractor::guidance::DirectionModifier::Enum
 mirrorDirectionModifier(const extractor::guidance::DirectionModifier::Enum modifier)
 {
@@ -111,7 +118,7 @@ inline bool entersRoundabout(const extractor::guidance::TurnInstruction instruct
             instruction.type == extractor::guidance::TurnType::EnterRoundaboutIntersectionAtExit ||
             instruction.type == extractor::guidance::TurnType::EnterAndExitRoundabout ||
             instruction.type == extractor::guidance::TurnType::EnterAndExitRotary ||
-            instruction.type == extractor::guidance::TurnType::EnterAndExitRotary);
+            instruction.type == extractor::guidance::TurnType::EnterAndExitRoundaboutIntersection);
 }
 
 inline bool leavesRoundabout(const extractor::guidance::TurnInstruction instruction)
@@ -129,6 +136,128 @@ inline bool staysOnRoundabout(const extractor::guidance::TurnInstruction instruc
     return instruction.type == extractor::guidance::TurnType::StayOnRoundabout;
 }
 
+// Name Change Logic
+// Used both during Extraction as well as during Post-Processing
+
+inline std::pair<std::string, std::string> getPrefixAndSuffix(const std::string &data)
+{
+    const auto suffix_pos = data.find_last_of(' ');
+    if (suffix_pos == std::string::npos)
+        return {};
+
+    const auto prefix_pos = data.find_first_of(' ');
+    auto result = std::make_pair(data.substr(0, prefix_pos), data.substr(suffix_pos + 1));
+    boost::to_lower(result.first);
+    boost::to_lower(result.second);
+    return result;
+}
+
+// Note: there is an overload without suffix checking below.
+// (that's the reason we template the suffix table here)
+template <typename SuffixTable>
+inline bool requiresNameAnnounced(const std::string &from_name,
+                                  const std::string &from_ref,
+                                  const std::string &to_name,
+                                  const std::string &to_ref,
+                                  const SuffixTable &suffix_table)
+{
+    // first is empty and the second is not
+    if ((from_name.empty() && from_ref.empty()) && !(to_name.empty() && to_ref.empty()))
+        return true;
+
+    // FIXME, handle in profile to begin with?
+    // Input for this function should be a struct separating streetname, suffix (e.g. road,
+    // boulevard, North, West ...), and a list of references
+
+    // check similarity of names
+    const auto names_are_empty = from_name.empty() && to_name.empty();
+    const auto name_is_contained =
+        boost::starts_with(from_name, to_name) || boost::starts_with(to_name, from_name);
+
+    const auto checkForPrefixOrSuffixChange = [](
+        const std::string &first, const std::string &second, const SuffixTable &suffix_table) {
+
+        const auto first_prefix_and_suffixes = getPrefixAndSuffix(first);
+        const auto second_prefix_and_suffixes = getPrefixAndSuffix(second);
+
+        // reverse strings, get suffices and reverse them to get prefixes
+        const auto checkTable = [&](const std::string &str) {
+            return str.empty() || suffix_table.isSuffix(str);
+        };
+
+        const auto getOffset = [](const std::string &str) -> std::size_t {
+            if (str.empty())
+                return 0;
+            else
+                return str.length() + 1;
+        };
+
+        const bool is_prefix_change = [&]() -> bool {
+            if (!checkTable(first_prefix_and_suffixes.first))
+                return false;
+            if (!checkTable(second_prefix_and_suffixes.first))
+                return false;
+            return !first.compare(getOffset(first_prefix_and_suffixes.first),
+                                  std::string::npos,
+                                  second,
+                                  getOffset(second_prefix_and_suffixes.first),
+                                  std::string::npos);
+        }();
+
+        const bool is_suffix_change = [&]() -> bool {
+            if (!checkTable(first_prefix_and_suffixes.second))
+                return false;
+            if (!checkTable(second_prefix_and_suffixes.second))
+                return false;
+            return !first.compare(0,
+                                  first.length() - getOffset(first_prefix_and_suffixes.second),
+                                  second,
+                                  0,
+                                  second.length() - getOffset(second_prefix_and_suffixes.second));
+        }();
+
+        return is_prefix_change || is_suffix_change;
+    };
+
+    const auto is_suffix_change = checkForPrefixOrSuffixChange(from_name, to_name, suffix_table);
+    const auto names_are_equal = from_name == to_name || name_is_contained || is_suffix_change;
+    const auto name_is_removed = !from_name.empty() && to_name.empty();
+    // references are contained in one another
+    const auto refs_are_empty = from_ref.empty() && to_ref.empty();
+    const auto ref_is_contained =
+        from_ref.empty() || to_ref.empty() ||
+        (from_ref.find(to_ref) != std::string::npos || to_ref.find(from_ref) != std::string::npos);
+    const auto ref_is_removed = !from_ref.empty() && to_ref.empty();
+
+    const auto obvious_change =
+        (names_are_empty && refs_are_empty) || (names_are_equal && ref_is_contained) ||
+        (names_are_equal && refs_are_empty) || (ref_is_contained && name_is_removed) ||
+        (names_are_equal && ref_is_removed) || is_suffix_change;
+
+    const auto needs_announce =
+        // " (Ref)" -> "Name "
+        (from_name.empty() && !from_ref.empty() && !to_name.empty() && to_ref.empty());
+
+    return !obvious_change || needs_announce;
+}
+
+// Overload without suffix checking
+inline bool requiresNameAnnounced(const std::string &from_name,
+                                  const std::string &from_ref,
+                                  const std::string &to_name,
+                                  const std::string &to_ref)
+{
+    // Dummy since we need to provide a SuffixTable but do not have the data for it.
+    // (Guidance Post-Processing does not keep the suffix table around at the moment)
+    struct NopSuffixTable final
+    {
+        NopSuffixTable(){}
+        bool isSuffix(const std::string &) const { return false; }
+    } static const table;
+
+    return requiresNameAnnounced(from_name, from_ref, to_name, to_ref, table);
+}
+
 } // namespace guidance
 } // namespace util
 } // namespace osrm
diff --git a/include/util/io.hpp b/include/util/io.hpp
index cb4473d..e208f7c 100644
--- a/include/util/io.hpp
+++ b/include/util/io.hpp
@@ -151,7 +151,7 @@ bool serializeVector(std::ofstream &out_stream, const stxxl::vector<simple_type>
 template <typename simple_type>
 bool deserializeAdjacencyArray(const std::string &filename,
                                std::vector<std::uint32_t> &offsets,
-                               std::vector<simple_type>& data)
+                               std::vector<simple_type> &data)
 {
     std::ifstream in_stream(filename, std::ios::binary);
 
diff --git a/include/util/name_table.hpp b/include/util/name_table.hpp
index 318cffc..2f45a71 100644
--- a/include/util/name_table.hpp
+++ b/include/util/name_table.hpp
@@ -23,7 +23,13 @@ class NameTable
 
   public:
     NameTable(const std::string &filename);
+
+    // This class provides a limited view over all the string data we serialize out.
+    // The following functions are a subset of what is available.
+    // See the data facades for they provide full access to this serialized string data.
+    // (at time of writing this: get{Name,Ref,Pronunciation,Destinations}ForID(name_id);)
     std::string GetNameForID(const unsigned name_id) const;
+    std::string GetRefForID(const unsigned name_id) const;
 };
 } // namespace util
 } // namespace osrm
diff --git a/include/util/node_based_graph.hpp b/include/util/node_based_graph.hpp
index 957b83c..c5981c6 100644
--- a/include/util/node_based_graph.hpp
+++ b/include/util/node_based_graph.hpp
@@ -1,7 +1,7 @@
 #ifndef NODE_BASED_GRAPH_HPP
 #define NODE_BASED_GRAPH_HPP
 
-#include "extractor/guidance/classification_data.hpp"
+#include "extractor/guidance/road_classification.hpp"
 #include "extractor/node_based_edge.hpp"
 #include "util/dynamic_graph.hpp"
 #include "util/graph_utils.hpp"
@@ -49,16 +49,20 @@ struct NodeBasedEdgeData
     bool startpoint : 1;
     extractor::TravelMode travel_mode : 4;
     LaneDescriptionID lane_description_id;
-    extractor::guidance::RoadClassificationData road_classification;
+    extractor::guidance::RoadClassification road_classification;
 
     bool IsCompatibleTo(const NodeBasedEdgeData &other) const
     {
-        return (name_id == other.name_id) && (reversed == other.reversed) &&
-               (roundabout == other.roundabout) && (startpoint == other.startpoint) &&
-               (access_restricted == other.access_restricted) &&
+        return (reversed == other.reversed) && (roundabout == other.roundabout) &&
+               (startpoint == other.startpoint) && (access_restricted == other.access_restricted) &&
                (travel_mode == other.travel_mode) &&
                (road_classification == other.road_classification);
     }
+
+    bool CanCombineWith(const NodeBasedEdgeData &other) const
+    {
+        return (name_id == other.name_id) && IsCompatibleTo(other);
+    }
 };
 
 using NodeBasedDynamicGraph = DynamicGraph<NodeBasedEdgeData>;
diff --git a/include/util/static_rtree.hpp b/include/util/static_rtree.hpp
index c955151..1dedffc 100644
--- a/include/util/static_rtree.hpp
+++ b/include/util/static_rtree.hpp
@@ -17,6 +17,7 @@
 #include <boost/assert.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
+#include <boost/format.hpp>
 #include <boost/iostreams/device/mapped_file.hpp>
 
 #include <tbb/parallel_for.h>
@@ -192,9 +193,9 @@ class StaticRTree
 
                     Coordinate current_centroid = coordinate_calculation::centroid(
                         m_coordinate_list[current_element.u], m_coordinate_list[current_element.v]);
-                    current_centroid.lat =
-                        FixedLatitude{static_cast<std::int32_t>(COORDINATE_PRECISION *
-                                      web_mercator::latToY(toFloating(current_centroid.lat)))};
+                    current_centroid.lat = FixedLatitude{static_cast<std::int32_t>(
+                        COORDINATE_PRECISION *
+                        web_mercator::latToY(toFloating(current_centroid.lat)))};
 
                     current_wrapper.m_hilbert_value = hilbertCode(current_centroid);
                 }
@@ -385,7 +386,7 @@ class StaticRTree
             std::size_t num_leaves = m_leaves_region.size() / sizeof(LeafNode);
             m_leaves.reset(reinterpret_cast<const LeafNode *>(m_leaves_region.data()), num_leaves);
         }
-        catch (std::exception &exc)
+        catch (const std::exception &exc)
         {
             throw exception(boost::str(boost::format("Leaf file %1% mapping failed: %2%") %
                                        leaf_file % exc.what()));
diff --git a/include/util/strong_typedef.hpp b/include/util/strong_typedef.hpp
index 0d45a2d..02b1d62 100644
--- a/include/util/strong_typedef.hpp
+++ b/include/util/strong_typedef.hpp
@@ -40,32 +40,32 @@ namespace osrm
  * etc.  Also clarifies what this random "int" value is
  * being used for.
  */
-#define OSRM_STRONG_TYPEDEF(From, To)                                                                    \
-    struct To final                                                                                      \
-    {                                                                                                    \
-        static_assert(std::is_arithmetic<From>(), "");                                                   \
-        From __value;                                                                                    \
-        friend std::ostream &operator<<(std::ostream &stream, const To &inst);                           \
-                                                                                                         \
-        explicit operator From &() { return __value; }                                                   \
-        explicit operator From() const { return __value; }                                               \
-        To operator+(const To rhs_) const { return To{__value + static_cast<const From>(rhs_)}; }        \
-        To operator-(const To rhs_) const { return To{__value - static_cast<const From>(rhs_)}; }        \
-        To operator*(const To rhs_) const { return To{__value * static_cast<const From>(rhs_)}; }        \
-        To operator/(const To rhs_) const { return To{__value / static_cast<const From>(rhs_)}; }        \
-        bool operator<(const To z_) const { return __value < static_cast<const From>(z_); }              \
-        bool operator>(const To z_) const { return __value > static_cast<const From>(z_); }              \
-        bool operator<=(const To z_) const { return __value <= static_cast<const From>(z_); }            \
-        bool operator>=(const To z_) const { return __value >= static_cast<const From>(z_); }            \
-        bool operator==(const To z_) const { return __value == static_cast<const From>(z_); }            \
-        bool operator!=(const To z_) const { return __value != static_cast<const From>(z_); }            \
-    };                                                                                                   \
-    static_assert(std::is_trivial<To>(), #To " is not a trivial type");                                \
-    static_assert(std::is_standard_layout<To>(), #To " is not a standart layout");                     \
-    static_assert(std::is_pod<To>(), #To " is not a POD layout");                                      \
-    inline std::ostream &operator<<(std::ostream &stream, const To &inst)                                \
-    {                                                                                                    \
-        return stream << inst.__value;                                                                   \
+#define OSRM_STRONG_TYPEDEF(From, To)                                                              \
+    struct To final                                                                                \
+    {                                                                                              \
+        static_assert(std::is_arithmetic<From>(), "");                                             \
+        From __value;                                                                              \
+        friend std::ostream &operator<<(std::ostream &stream, const To &inst);                     \
+                                                                                                   \
+        explicit operator From &() { return __value; }                                             \
+        explicit operator From() const { return __value; }                                         \
+        To operator+(const To rhs_) const { return To{__value + static_cast<const From>(rhs_)}; }  \
+        To operator-(const To rhs_) const { return To{__value - static_cast<const From>(rhs_)}; }  \
+        To operator*(const To rhs_) const { return To{__value * static_cast<const From>(rhs_)}; }  \
+        To operator/(const To rhs_) const { return To{__value / static_cast<const From>(rhs_)}; }  \
+        bool operator<(const To z_) const { return __value < static_cast<const From>(z_); }        \
+        bool operator>(const To z_) const { return __value > static_cast<const From>(z_); }        \
+        bool operator<=(const To z_) const { return __value <= static_cast<const From>(z_); }      \
+        bool operator>=(const To z_) const { return __value >= static_cast<const From>(z_); }      \
+        bool operator==(const To z_) const { return __value == static_cast<const From>(z_); }      \
+        bool operator!=(const To z_) const { return __value != static_cast<const From>(z_); }      \
+    };                                                                                             \
+    static_assert(std::is_trivial<To>(), #To " is not a trivial type");                            \
+    static_assert(std::is_standard_layout<To>(), #To " is not a standart layout");                 \
+    static_assert(std::is_pod<To>(), #To " is not a POD layout");                                  \
+    inline std::ostream &operator<<(std::ostream &stream, const To &inst)                          \
+    {                                                                                              \
+        return stream << inst.__value;                                                             \
     }
 
 #define OSRM_STRONG_TYPEDEF_HASHABLE(From, To)                                                     \
diff --git a/include/util/typedefs.hpp b/include/util/typedefs.hpp
index 2ffb577..36797ee 100644
--- a/include/util/typedefs.hpp
+++ b/include/util/typedefs.hpp
@@ -64,7 +64,8 @@ static const LaneID INVALID_LANEID = std::numeric_limits<LaneID>::max();
 using LaneDataID = std::uint16_t;
 static const LaneDataID INVALID_LANE_DATAID = std::numeric_limits<LaneDataID>::max();
 using LaneDescriptionID = std::uint16_t;
-static const LaneDescriptionID INVALID_LANE_DESCRIPTIONID = std::numeric_limits<LaneDescriptionID>::max();
+static const LaneDescriptionID INVALID_LANE_DESCRIPTIONID =
+    std::numeric_limits<LaneDescriptionID>::max();
 
 using BearingClassID = std::uint32_t;
 static const BearingClassID INVALID_BEARING_CLASSID = std::numeric_limits<BearingClassID>::max();
@@ -82,6 +83,8 @@ static const NameID EMPTY_NAMEID = 0;
 static const unsigned INVALID_COMPONENTID = 0;
 static const EdgeWeight INVALID_EDGE_WEIGHT = std::numeric_limits<EdgeWeight>::max();
 
+using DatasourceID = std::uint8_t;
+
 struct SegmentID
 {
     SegmentID(const NodeID id_, const bool enabled_) : id{id_}, enabled{enabled_}
diff --git a/package.json b/package.json
index 87d21ce..c78842f 100644
--- a/package.json
+++ b/package.json
@@ -7,9 +7,11 @@
     "chalk": "^1.1.3",
     "cucumber": "^1.2.1",
     "d3-queue": "^2.0.3",
+    "mkdirp": "^0.5.1",
     "node-timeout": "0.0.4",
     "polyline": "^0.2.0",
     "request": "^2.69.0",
+    "rimraf": "^2.5.4",
     "xmlbuilder": "^4.2.1"
   },
   "bin": {
diff --git a/profiles/bicycle.lua b/profiles/bicycle.lua
index be97de3..7bcc65e 100644
--- a/profiles/bicycle.lua
+++ b/profiles/bicycle.lua
@@ -2,6 +2,7 @@
 
 local find_access_tag = require("lib/access").find_access_tag
 local limit = require("lib/maxspeed").limit
+local set_classification = require("lib/guidance").set_classification
 
 -- Begin of globals
 barrier_whitelist = { [""] = true, ["cycle_barrier"] = true, ["bollard"] = true, ["entrance"] = true, ["cattle_grid"] = true, ["border_control"] = true, ["toll_booth"] = true, ["sally_port"] = true, ["gate"] = true, ["no"] = true, ["block"] = true }
@@ -98,7 +99,7 @@ properties.continue_straight_at_waypoint = false
 
 local obey_oneway               = true
 local ignore_areas              = true
-local turn_penalty              = 60
+local turn_penalty              = 6
 local turn_bias                 = 1.4
 -- reduce the driving speed by 30% for unsafe roads
 -- local safety_penalty            = 0.7
@@ -210,19 +211,20 @@ function way_function (way, result)
   local bicycle = way:get_value_by_key("bicycle")
 
   -- name
-  if ref and "" ~= ref and name and "" ~= name then
-    result.name = name .. " (" .. ref .. ")"
-  elseif ref and "" ~= ref then
-    result.name = ref
-  elseif name and "" ~= name then
+  if name and "" ~= name then
     result.name = name
   -- TODO find a better solution for encoding way type
   elseif fallback_names and highway then
     -- if no name exists, use way type
-    -- this encoding scheme is excepted to be a temporary solution
+    -- this encoding scheme is expected to be a temporary solution
     result.name = "{highway:"..highway.."}"
   end
 
+  -- ref
+  if ref and "" ~= ref then
+    result.ref = ref
+  end
+
   -- roundabout handling
   if junction and "roundabout" == junction then
     result.roundabout = true
@@ -392,13 +394,17 @@ function way_function (way, result)
     result.backward_speed = math.min(surface_speeds[surface], result.backward_speed)
   end
 
+  -- set the road classification based on guidance globals configuration
+  set_classification(highway,result)
+
   -- maxspeed
   limit( result, maxspeed, maxspeed_forward, maxspeed_backward )
 end
 
 function turn_function (angle)
   -- compute turn penalty as angle^2, with a left/right bias
-  k = turn_penalty/(90.0*90.0)
+  -- multiplying by 10 converts to deci-seconds see issue #1318
+  k = 10*turn_penalty/(90.0*90.0)
   if angle>=0 then
     return angle*angle*k/turn_bias
   else
diff --git a/profiles/car.lua b/profiles/car.lua
index c138f86..3fd8720 100644
--- a/profiles/car.lua
+++ b/profiles/car.lua
@@ -1,7 +1,8 @@
 -- Car profile
-
 local find_access_tag = require("lib/access").find_access_tag
 local get_destination = require("lib/destination").get_destination
+local set_classification = require("lib/guidance").set_classification
+local get_turn_lanes = require("lib/guidance").get_turn_lanes
 
 -- Begin of globals
 barrier_whitelist = { ["cattle_grid"] = true, ["border_control"] = true, ["checkpoint"] = true, ["toll_booth"] = true, ["sally_port"] = true, ["gate"] = true, ["lift_gate"] = true, ["no"] = true, ["entrance"] = true }
@@ -10,6 +11,7 @@ access_tag_blacklist = { ["no"] = true, ["private"] = true, ["agricultural"] = t
 access_tag_restricted = { ["destination"] = true, ["delivery"] = true }
 access_tags_hierarchy = { "motorcar", "motor_vehicle", "vehicle", "access" }
 service_tag_restricted = { ["parking_aisle"] = true }
+service_tag_forbidden = { ["emergency_access"] = true }
 restriction_exception_tags = { "motorcar", "motor_vehicle", "vehicle" }
 
 -- A list of suffixes to suppress in name change instructions
@@ -37,6 +39,11 @@ speed_profile = {
   ["default"] = 10
 }
 
+-- service speeds
+service_speeds = {
+  ["alley"] = 5,
+  ["parking_aisle"] = 5
+}
 
 -- surface/trackype/smoothness
 -- values were estimated from looking at the photos at the relevant wiki pages
@@ -129,6 +136,8 @@ maxspeed_table = {
   ["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
 }
 
@@ -137,16 +146,19 @@ properties.u_turn_penalty                  = 20
 properties.traffic_signal_penalty          = 2
 properties.use_turn_restrictions           = true
 properties.continue_straight_at_waypoint   = true
+properties.left_hand_driving               = false
 
 local side_road_speed_multiplier = 0.8
 
-local turn_penalty               = 10
+local turn_penalty               = 7.5
 -- Note: this biases right-side driving.  Should be
 -- inverted for left-driving countries.
-local turn_bias                  = 1.2
+local turn_bias                  = properties.left_hand_driving and 1/1.075 or 1.075
 
 local obey_oneway                = true
 local ignore_areas               = true
+local ignore_hov_ways            = true
+local ignore_toll_ways           = false
 
 local abs = math.abs
 local min = math.min
@@ -166,62 +178,6 @@ function get_exceptions(vector)
   end
 end
 
--- returns forward,backward psv lane count
-local function getPSVCounts(way)
-    local psv = way:get_value_by_key("lanes:psv")
-    local psv_forward = way:get_value_by_key("lanes:psv:forward");
-    local psv_backward = way:get_value_by_key("lanes:psv:backward");
-
-    local fw = 0;
-    local bw = 0;
-    if( psv and psv ~= "" ) then
-        fw = tonumber(psv)
-        if( fw == nil ) then
-            fw = 0
-        end
-    end
-    if( psv_forward and psv_forward ~= "" ) then
-        fw = tonumber(psv_forward)
-        if( fw == nil ) then
-            fw = 0
-        end
-    end
-    if( psv_backward and psv_backward ~= "" ) then
-        bw = tonumber(psv_backward);
-        if( bw == nil ) then
-            bw = 0
-        end
-    end
-    return fw, bw
-end
-
--- this is broken for left-sided driving. It needs to switch left and right in case of left-sided driving
-local function getTurnLanes(way)
-    local fw_psv = 0
-    local bw_psv = 0
-    fw_psv, bw_psv = getPSVCounts(way)
-
-    local turn_lanes = way:get_value_by_key("turn:lanes")
-    local turn_lanes_fw = way:get_value_by_key("turn:lanes:forward")
-    local turn_lanes_bw = way:get_value_by_key("turn:lanes:backward")
-
-    if( fw_psv ~= 0 or bw_psv ~= 0 ) then
-        if  turn_lanes and turn_lanes ~= "" then
-            turn_lanes = trimLaneString(turn_lanes, bw_psv, fw_psv )
-        end
-        if  turn_lanes_fw and turn_lanes_fw ~= ""  then
-            turn_lanes_fw = trimLaneString(turn_lanes_fw, bw_psv, fw_psv )
-        end
-        --backwards turn lanes need to treat bw_psv as fw_psv and vice versa
-        if  turn_lanes_bw and turn_lanes_bw ~= ""  then
-            turn_lanes_bw = trimLaneString(turn_lanes_bw, fw_psv, bw_psv )
-        end
-    end
-
-    return turn_lanes, turn_lanes_fw, turn_lanes_bw
-end
-
-
 local function parse_maxspeed(source)
   if not source then
     return 0
@@ -282,12 +238,76 @@ function way_function (way, result)
     return
   end
 
+  -- default to driving mode, may get overwritten below
+  result.forward_mode = mode.driving
+  result.backward_mode = mode.driving
+
   -- we dont route over areas
   local area = way:get_value_by_key("area")
   if ignore_areas and area and "yes" == area then
     return
   end
 
+  -- respect user-preference for HOV-only ways
+  if ignore_hov_ways then
+    local hov = way:get_value_by_key("hov")
+    if hov and "designated" == hov then
+      return
+    end
+
+    -- also respect user-preference for HOV-only ways when all lanes are HOV-designated
+    local function has_all_designated_hov_lanes(lanes)
+      local all = true
+      for lane in lanes:gmatch("(%w+)") do
+        if lane and lane ~= "designated" then
+          all = false
+          break
+        end
+      end
+      return all
+    end
+
+    local hov_lanes = way:get_value_by_key("hov:lanes")
+    local hov_lanes_forward = way:get_value_by_key("hov:lanes:forward")
+    local hov_lanes_backward = way:get_value_by_key("hov:lanes:backward")
+
+    local hov_all_designated = hov_lanes and hov_lanes ~= ""
+                               and has_all_designated_hov_lanes(hov_lanes)
+
+    local hov_all_designated_forward = hov_lanes_forward and hov_lanes_forward ~= ""
+                                       and has_all_designated_hov_lanes(hov_lanes_forward)
+
+    local hov_all_designated_backward = hov_lanes_backward and hov_lanes_backward ~= ""
+                                        and has_all_designated_hov_lanes(hov_lanes_backward)
+
+    -- forward/backward lane depend on a way's direction
+    local oneway = way:get_value_by_key("oneway")
+    local reverse = oneway and oneway == "-1"
+
+    if hov_all_designated or hov_all_designated_forward then
+      if reverse then
+        result.backward_mode = mode.inaccessible
+      else
+        result.forward_mode = mode.inaccessible
+      end
+    end
+
+    if hov_all_designated_backward then
+      if reverse then
+        result.forward_mode = mode.inaccessible
+      else
+        result.backward_mode = mode.inaccessible
+      end
+    end
+
+  end -- hov handling
+
+  -- respect user-preference for toll=yes ways
+  local toll = way:get_value_by_key("toll")
+  if ignore_toll_ways and toll and "yes" == toll then
+    return
+  end
+
   -- check if oneway tag is unsupported
   local oneway = way:get_value_by_key("oneway")
   if oneway and "reversible" == oneway then
@@ -310,9 +330,6 @@ function way_function (way, result)
     return
   end
 
-  result.forward_mode = mode.driving
-  result.backward_mode = mode.driving
-
   -- handling ferries and piers
   local route_speed = speed_profile[route]
   if (route_speed and route_speed > 0) then
@@ -340,7 +357,7 @@ function way_function (way, result)
     result.backward_speed = bridge_speed
   end
 
-  -- leave early of this way is not accessible
+  -- leave early if this way is not accessible
   if "" == highway then
     return
   end
@@ -402,6 +419,9 @@ function way_function (way, result)
     result.backward_speed = math.min(smoothness_speeds[smoothness], result.backward_speed)
   end
 
+  -- set the road classification based on guidance globals configuration
+  set_classification(highway,result)
+
   -- parse the remaining tags
   local name = way:get_value_by_key("name")
   local pronunciation = way:get_value_by_key("name:pronunciation")
@@ -416,14 +436,14 @@ function way_function (way, result)
   local has_name = name and "" ~= name
   local has_pronunciation = pronunciation and "" ~= pronunciation
 
-  if has_name and has_ref then
-    result.name = name .. " (" .. ref .. ")"
-  elseif has_ref then
-    result.name = ref
-  elseif has_name then
+  if has_name then
     result.name = name
   end
 
+  if has_ref then
+    result.ref = ref
+  end
+
   if has_pronunciation then
     result.pronunciation = pronunciation
   end
@@ -432,7 +452,7 @@ function way_function (way, result)
   local turn_lanes_forward = ""
   local turn_lanes_backward = ""
 
-  turn_lanes, turn_lanes_forward, turn_lanes_backward = getTurnLanes(way)
+  turn_lanes, turn_lanes_forward, turn_lanes_backward = get_turn_lanes(way)
   if  turn_lanes and turn_lanes ~= "" then
     result.turn_lanes_forward = turn_lanes;
     result.turn_lanes_backward = turn_lanes;
@@ -456,9 +476,18 @@ function way_function (way, result)
     result.is_access_restricted = true
   end
 
-  -- Set access restriction flag if service is allowed under certain restrictions only
-  if service and service ~= "" and service_tag_restricted[service] then
-    result.is_access_restricted = true
+  if service and service ~= "" then
+    -- Set access restriction flag if service is allowed under certain restrictions only
+    if service_tag_restricted[service] then
+      result.is_access_restricted = true
+    end
+
+    -- Set don't allow access to certain service roads
+    if service_tag_forbidden[service] then
+      result.forward_mode = mode.inaccessible
+      result.backward_mode = mode.inaccessible
+      return
+    end
   end
 
   -- Set direction according to tags on way
@@ -476,10 +505,6 @@ function way_function (way, result)
       local destination = get_destination(way)
       local has_destination = destination and "" ~= destination
 
-      if has_destination and has_name and not has_ref then
-        result.name = name .. " (" .. destination .. ")"
-      end
-
       result.destinations = destination
     end
   end
@@ -540,7 +565,9 @@ function way_function (way, result)
   if result.forward_speed > 0 then
     local scaled_speed = result.forward_speed*speed_reduction + 11
     local penalized_speed = math.huge
-    if width <= 3 or (lanes <= 1 and is_bidirectional) then
+    if service and service ~= "" and service_speeds[service] then
+      penalized_speed = service_speeds[service]
+    elseif width <= 3 or (lanes <= 1 and is_bidirectional) then
       penalized_speed = result.forward_speed / 2
     end
     result.forward_speed = math.min(penalized_speed, scaled_speed)
@@ -549,7 +576,9 @@ function way_function (way, result)
   if result.backward_speed > 0 then
     local scaled_speed = result.backward_speed*speed_reduction + 11
     local penalized_speed = math.huge
-    if width <= 3 or (lanes <= 1 and is_bidirectional) then
+    if service and service ~= "" and service_speeds[service]then
+      penalized_speed = service_speeds[service]
+    elseif width <= 3 or (lanes <= 1 and is_bidirectional) then
       penalized_speed = result.backward_speed / 2
     end
     result.backward_speed = math.min(penalized_speed, scaled_speed)
@@ -560,11 +589,13 @@ function way_function (way, result)
 end
 
 function turn_function (angle)
-  ---- compute turn penalty as angle^2, with a left/right bias
-  k = turn_penalty/(90.0*90.0)
+  -- 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.
+  -- multiplying by 10 converts to deci-seconds see issue #1318
   if angle>=0 then
-    return angle*angle*k/turn_bias
+    return 10 * turn_penalty / (1 + 2.718 ^ - ((13 / turn_bias) * angle/180 - 6.5*turn_bias))
   else
-    return angle*angle*k*turn_bias
+    return 10 * turn_penalty / (1 + 2.718 ^  - ((13 * turn_bias) * - angle/180 - 6.5/turn_bias))
   end
 end
diff --git a/profiles/foot.lua b/profiles/foot.lua
index 002f06d..25ed19f 100644
--- a/profiles/foot.lua
+++ b/profiles/foot.lua
@@ -150,16 +150,15 @@ function way_function (way, result)
   local surface = way:get_value_by_key("surface")
 
    -- name
-  if ref and "" ~= ref and name and "" ~= name then
-    result.name = name .. " (" .. ref .. ")"
-    elseif ref and "" ~= ref then
-      result.name = ref
-  elseif name and "" ~= name then
+  if name and "" ~= name then
     result.name = name
   elseif highway and fallback_names then
     result.name = "{highway:"..highway.."}"  -- if no name exists, use way type
                                             -- this encoding scheme is excepted to be a temporary solution
   end
+  if ref and "" ~= ref then
+    result.ref = ref
+  end
 
     -- roundabouts
   if "roundabout" == junction then
diff --git a/profiles/lhs.lua b/profiles/lhs.lua
new file mode 100644
index 0000000..84f1924
--- /dev/null
+++ b/profiles/lhs.lua
@@ -0,0 +1,20 @@
+-- Testbot, with turn penalty
+-- Used for testing turn penalties
+
+require 'testbot'
+
+properties.left_hand_driving = true
+
+local turn_penalty           = 50
+local turn_bias              = properties.left_hand_driving and 1/1.2 or 1.2
+
+function turn_function (angle)
+  ---- compute turn penalty as angle^2, with a left/right bias
+  -- multiplying by 10 converts to deci-seconds see issue #1318
+  k = 10*turn_penalty/(90.0*90.0)
+  if angle>=0 then
+    return angle*angle*k/turn_bias
+  else
+    return angle*angle*k*turn_bias
+  end
+end
diff --git a/profiles/lib/guidance.lua b/profiles/lib/guidance.lua
new file mode 100644
index 0000000..882567d
--- /dev/null
+++ b/profiles/lib/guidance.lua
@@ -0,0 +1,127 @@
+local Guidance = {}
+
+-- Guidance: Default Mapping from roads to types/priorities
+highway_classes = { ["motorway"] = road_priority_class.motorway,
+                    ["motorway_link"] = road_priority_class.link_road,
+                    ["trunk"] = road_priority_class.trunk,
+                    ["trunk_link"] = road_priority_class.link_road,
+                    ["primary"] = road_priority_class.primary,
+                    ["primary_link"] = road_priority_class.link_road,
+                    ["secondary"] = road_priority_class.secondary,
+                    ["secondary_link"] = road_priority_class.link_road,
+                    ["tertiary"] = road_priority_class.tertiary,
+                    ["tertiary_link"] = road_priority_class.link_road,
+                    ["unclassified"] = road_priority_class.side_residential,
+                    ["residential"] = road_priority_class.side_residential,
+                    ["service"] = road_priority_class.connectivity,
+                    ["living_street"] = road_priority_class.main_residential,
+                    ["track"] = road_priority_class.bike_path,
+                    ["path"] = road_priority_class.bike_path,
+                    ["footway"] = road_priority_class.foot_path,
+                    ["pedestrian"] = road_priority_class.foot_path,
+                    ["steps"] = road_priority_class.foot_path}
+
+default_highway_class = road_priority_class.connectivity;
+
+motorway_types = { ["motorway"] = true, ["motorway_link"] = true, ["trunk"] = true, ["trunk_link"] = true }
+
+-- these road types are set with a car in mind. For bicycle/walk we probably need different ones
+road_types = { ["motorway"] = true,
+               ["motorway_link"] = true,
+               ["trunk"] = true,
+               ["trunk_link"] = true,
+               ["primary"] = true,
+               ["primary_link"] = true,
+               ["secondary"] = true,
+               ["secondary_link"] = true,
+               ["tertiary"] = true,
+               ["tertiary_link"] = true,
+               ["unclassified"] = true,
+               ["residential"] = true,
+               ["living_street"] = true }
+
+link_types = { ["motorway_link"] = true, ["trunk_link"] = true, ["primary_link"] = true, ["secondary_link"] = true, ["tertiary_link"] = true }
+
+function Guidance.set_classification (highway, result)
+    if motorway_types[highway] then
+        result.road_classification.motorway_class = true;
+    end
+    if link_types[highway] then
+        result.road_classification.link_class = true;
+    end
+    if highway_classes[highway] ~= nil then
+        result.road_classification.road_priority_class = highway_classes[highway]
+    else
+        result.road_classification.road_priority_class = default_highway_class
+    end
+    if road_types[highway] then
+        result.road_classification.may_be_ignored = false;
+    else
+        result.road_classification.may_be_ignored = true;
+    end
+end
+
+-- returns forward,backward psv lane count
+local function get_psv_counts(way)
+    local psv = way:get_value_by_key("lanes:psv")
+    local psv_forward = way:get_value_by_key("lanes:psv:forward");
+    local psv_backward = way:get_value_by_key("lanes:psv:backward");
+
+    local fw = 0;
+    local bw = 0;
+    if( psv and psv ~= "" ) then
+        fw = tonumber(psv)
+        if( fw == nil ) then
+            fw = 0
+        end
+    end 
+    if( psv_forward and psv_forward ~= "" ) then
+        fw = tonumber(psv_forward)
+        if( fw == nil ) then
+            fw = 0
+        end
+    end 
+    if( psv_backward and psv_backward ~= "" ) then
+        bw = tonumber(psv_backward);
+        if( bw == nil ) then
+            bw = 0
+        end
+    end
+    return fw, bw
+end
+
+-- trims lane string with regard to supported lanes
+local function process_lanes(turn_lane,vehicle_lane,first_count,second_count)
+    if turn_lane and turn_lane ~= "" then
+        if vehicle_lane and vehicle_lane ~= "" then
+            turn_lane = applyAccessTokens(turn_lane,vehicle_lane)
+        elseif first_count ~= 0 or second_count ~= 0 then
+            turn_lane = trimLaneString(turn_lane, first_count, second_count)
+        end
+    end
+    return turn_lane;
+end
+
+-- this is broken for left-sided driving. It needs to switch left and right in case of left-sided driving
+function Guidance.get_turn_lanes(way)
+    local fw_psv = 0
+    local bw_psv = 0
+    fw_psv, bw_psv = get_psv_counts(way)
+
+    local turn_lanes = way:get_value_by_key("turn:lanes")
+    local turn_lanes_fw = way:get_value_by_key("turn:lanes:forward")
+    local turn_lanes_bw = way:get_value_by_key("turn:lanes:backward")
+
+    local vehicle_lanes = way:get_value_by_key("vehicle:lanes");
+    local vehicle_lanes_fw = way:get_value_by_key("vehicle:lanes:forward");
+    local vehicle_lanes_bw = way:get_value_by_key("vehicle:lanes:backward");
+
+    turn_lanes = process_lanes(turn_lanes,vehicle_lanes,bw_psv,fw_psv)
+    turn_lanes_fw = process_lanes(turn_lanes_fw,vehicle_lanes_fw,bw_psv,fw_psv)
+    --backwards turn lanes need to treat bw_psv as fw_psv and vice versa
+    turn_lanes_bw = process_lanes(turn_lanes_bw,vehicle_lanes_bw,fw_psv,bw_psv)
+
+    return turn_lanes, turn_lanes_fw, turn_lanes_bw
+end
+
+return Guidance
diff --git a/profiles/rasterbot.lua b/profiles/rasterbot.lua
index 03ff8f2..bc2b2b2 100644
--- a/profiles/rasterbot.lua
+++ b/profiles/rasterbot.lua
@@ -21,8 +21,12 @@ function way_function (way, result)
 end
 
 function source_function ()
+  local path = os.getenv('OSRM_RASTER_SOURCE')
+  if not path then
+    path = "rastersource.asc"
+  end
   raster_source = sources:load(
-    "../test/rastersource.asc",
+    path,
     0,    -- lon_min
     0.1,  -- lon_max
     0,    -- lat_min
diff --git a/profiles/rasterbotinterp.lua b/profiles/rasterbotinterp.lua
index 8266b07..f81e6e2 100644
--- a/profiles/rasterbotinterp.lua
+++ b/profiles/rasterbotinterp.lua
@@ -21,8 +21,12 @@ function way_function (way, result)
 end
 
 function source_function ()
+  local path = os.getenv('OSRM_RASTER_SOURCE')
+  if not path then
+    path = "rastersource.asc"
+  end
   raster_source = sources:load(
-    "../test/rastersource.asc",
+    path,
     0,    -- lon_min
     0.1,  -- lon_max
     0,    -- lat_min
diff --git a/profiles/rhs.lua b/profiles/rhs.lua
new file mode 100644
index 0000000..022a144
--- /dev/null
+++ b/profiles/rhs.lua
@@ -0,0 +1,20 @@
+-- Testbot, with turn penalty
+-- Used for testing turn penalties
+
+require 'testbot'
+
+properties.left_hand_driving = false
+
+local turn_penalty           = 50
+local turn_bias              = properties.left_hand_driving and 1/1.2 or 1.2
+
+function turn_function (angle)
+  ---- compute turn penalty as angle^2, with a left/right bias
+  -- multiplying by 10 converts to deci-seconds see issue #1318
+  k = 10*turn_penalty/(90.0*90.0)
+  if angle>=0 then
+    return angle*angle*k/turn_bias
+  else
+    return angle*angle*k*turn_bias
+  end
+end
diff --git a/profiles/turnbot.lua b/profiles/turnbot.lua
index 1a96966..7629515 100644
--- a/profiles/turnbot.lua
+++ b/profiles/turnbot.lua
@@ -4,5 +4,6 @@
 require 'testbot'
 
 function turn_function (angle)
-    return 200*math.abs(angle)/180 -- penalty 
+    -- multiplying by 10 converts to deci-seconds see issue #1318
+    return 10*20*math.abs(angle)/180
 end
diff --git a/scripts/format.sh b/scripts/format.sh
index 561db87..f95f873 100755
--- a/scripts/format.sh
+++ b/scripts/format.sh
@@ -7,8 +7,33 @@
 
 set -eu -o pipefail
 
+# Get CPU count
+OS=$(uname)
+NPROC=1
+if [[ $OS = "Linux" ]] ; then
+    NPROC=$(nproc)
+elif [[ ${OS} = "Darwin" ]] ; then
+    NPROC=$(sysctl -n hw.physicalcpu)
+fi
+
+# Discover clang-format
+if type clang-format-3.8 2> /dev/null ; then
+    CLANG_FORMAT=clang-format-3.8
+elif type clang-format 2> /dev/null ; then
+    # Clang format found, but need to check version
+    CLANG_FORMAT=clang-format
+    V=$(clang-format --version)
+    if [[ $V != *3.8* ]] ; then
+        echo "clang-format is not 3.8 (returned ${V})"
+        exit 1
+    fi
+else
+    echo "No appropriate clang-format found (expected clang-format-3.8, or clang-format)"
+    exit 1
+fi
+
 find src include unit_tests example -type f -name '*.hpp' -o -name '*.cpp' \
-  | xargs -I{} -P $(nproc) clang-format -i -style=file {}
+  | xargs -I{} -P ${NPROC} ${CLANG_FORMAT} -i -style=file {}
 
 
 dirty=$(git ls-files --modified)
diff --git a/scripts/gdb_printers.py b/scripts/gdb_printers.py
new file mode 100644
index 0000000..fd85ff3
--- /dev/null
+++ b/scripts/gdb_printers.py
@@ -0,0 +1,63 @@
+import gdb.printing
+
+# https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html
+# https://sourceware.org/gdb/onlinedocs/gdb/Writing-a-Pretty_002dPrinter.html
+
+COORDINATE_PRECISION = 1e6
+
+class CoordinatePrinter:
+    """Print a CoordinatePrinter object."""
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        lon, lat = int(self.val['lon']['__value']), int(self.val['lat']['__value'])
+        return '{{{}, {}}}'.format(float(lon) / COORDINATE_PRECISION, float(lat) / COORDINATE_PRECISION)
+
+class TurnInstructionPrinter:
+    """Print a TurnInstruction object."""
+
+    modifiers = {0:'UTurn', 1:'SharpRight', 2:'Right', 3:'SlightRight',
+                 4:'Straight', 5:'SlightLeft', 6:'Left', 7:'SharpLeft'}
+    types = {0:'Invalid', 1:'NewName', 2:'Continue', 3:'Turn', 4:'Merge', 5:'OnRamp',
+             6:'OffRamp', 7:'Fork', 8:'EndOfRoad', 9:'Notification', 10:'EnterRoundabout',
+             11:'EnterAndExitRoundabout', 12:'EnterRotary', 13:'EnterAndExitRotary',
+             14:'EnterRoundaboutIntersection', 15:'EnterAndExitRoundaboutIntersection',
+             16:'UseLane', 17:'NoTurn', 18:'Suppressed', 19:'EnterRoundaboutAtExit',
+             20:'ExitRoundabout', 21:'EnterRotaryAtExit', 22:'ExitRotary',
+             23:'EnterRoundaboutIntersectionAtExit', 24:'ExitRoundaboutIntersection',
+             25:'StayOnRoundabout', 26:'Sliproad'}
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        t, m = int(self.val['type']), int(self.val['direction_modifier'])
+        m = '%s (%d)' % (self.modifiers[m], m) if m in self.modifiers else str(m)
+        t = '%s (%d)' % (self.types[t], t) if t in self.types else str(t)
+        return '{{type = {}, direction_modifier = {}}}'.format(t, m)
+
+class TurnLaneDataPrinter:
+    """Print a TurnLaneData object."""
+
+    mask = {0:'Empty', 1:'None', 2:'Straight', 4:'SharpLeft', 8:'Left', 16:'SlightLeft',
+            32:'SlightRight', 64:'Right', 128:'SharpRight', 256:'UTurn', 512:'MergeToLeft',
+            1024:'MergeToRight'}
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        tg = int(self.val['tag'])
+        fr, to = int(self.val['from']), int(self.val['to'])
+        return '{{tag = {}, from = {}, to = {}}}'.format(self.mask[tg] if tg in self.mask else tg, fr, to)
+
+def build_pretty_printer():
+    pp = gdb.printing.RegexpCollectionPrettyPrinter('OSRM')
+    pp.add_printer('TurnInstruction', '::TurnInstruction$', TurnInstructionPrinter)
+    pp.add_printer('Coordinate', '::Coordinate$', CoordinatePrinter)
+    pp.add_printer('TurnLaneData', '::TurnLaneData$', TurnLaneDataPrinter)
+    return pp
+
+#gdb.pretty_printers = [filter(lambda x: x.name != 'OSRM', gdb.pretty_printers)]
+gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer())
diff --git a/src/contractor/contractor.cpp b/src/contractor/contractor.cpp
index e77a6b0..ca45a63 100644
--- a/src/contractor/contractor.cpp
+++ b/src/contractor/contractor.cpp
@@ -251,19 +251,19 @@ parse_segment_lookup_from_csv_files(const std::vector<std::string> &segment_spee
             const auto last = end(line);
 
             // The ulong_long -> uint64_t will likely break on 32bit platforms
-            const auto ok = parse(it,
-                                  last,                                              //
-                                  (ulong_long >> ',' >> ulong_long >> ',' >> uint_), //
-                                  from_node_id,
-                                  to_node_id,
-                                  speed); //
+            const auto ok =
+                parse(it,
+                      last,                                                                  //
+                      (ulong_long >> ',' >> ulong_long >> ',' >> uint_ >> *(',' >> *char_)), //
+                      from_node_id,
+                      to_node_id,
+                      speed); //
 
             if (!ok || it != last)
                 throw util::exception{"Segment speed file " + filename + " malformed"};
 
-            SegmentSpeedSource val{
-                {OSMNodeID{from_node_id}, OSMNodeID{to_node_id}},
-                {speed, static_cast<std::uint8_t>(file_id)}};
+            SegmentSpeedSource val{{OSMNodeID{from_node_id}, OSMNodeID{to_node_id}},
+                                   {speed, static_cast<std::uint8_t>(file_id)}};
 
             local.push_back(std::move(val));
         }
@@ -335,14 +335,14 @@ parse_turn_penalty_lookup_from_csv_files(const std::vector<std::string> &turn_pe
             const auto last = end(line);
 
             // The ulong_long -> uint64_t will likely break on 32bit platforms
-            const auto ok =
-                parse(it,
-                      last,                                                                     //
-                      (ulong_long >> ',' >> ulong_long >> ',' >> ulong_long >> ',' >> double_), //
-                      from_node_id,
-                      via_node_id,
-                      to_node_id,
-                      penalty); //
+            const auto ok = parse(it,
+                                  last, //
+                                  (ulong_long >> ',' >> ulong_long >> ',' >> ulong_long >> ',' >>
+                                   double_ >> *(',' >> *char_)), //
+                                  from_node_id,
+                                  via_node_id,
+                                  to_node_id,
+                                  penalty); //
 
             if (!ok || it != last)
                 throw util::exception{"Turn penalty file " + filename + " malformed"};
@@ -382,7 +382,7 @@ EdgeID Contractor::LoadEdgeExpandedGraph(
         using boost::interprocess::mapped_region;
         using boost::interprocess::read_only;
 
-        const file_mapping mapping{ filename.c_str(), read_only };
+        const file_mapping mapping{filename.c_str(), read_only};
         mapped_region region{mapping, read_only};
         region.advise(mapped_region::advice_sequential);
         return region;
@@ -409,19 +409,21 @@ EdgeID Contractor::LoadEdgeExpandedGraph(
         return boost::interprocess::mapped_region();
     }();
 
-    // Set the struct packing to 1 byte word sizes.  This prevents any padding.  We only use
-    // this struct once, so any alignment penalty is trivial.  If this is *not* done, then
-    // the struct will be padded out by an extra 4 bytes, and sizeof() will mean we read
-    // too much data from the original file.
-    #pragma pack(push, r1, 1)
-    struct EdgeBasedGraphHeader {
+// Set the struct packing to 1 byte word sizes.  This prevents any padding.  We only use
+// this struct once, so any alignment penalty is trivial.  If this is *not* done, then
+// the struct will be padded out by an extra 4 bytes, and sizeof() will mean we read
+// too much data from the original file.
+#pragma pack(push, r1, 1)
+    struct EdgeBasedGraphHeader
+    {
         util::FingerPrint fingerprint;
         std::uint64_t number_of_edges;
         EdgeID max_edge_id;
     };
-    #pragma pack(pop, r1)
+#pragma pack(pop, r1)
 
-    const EdgeBasedGraphHeader graph_header = *(reinterpret_cast<const EdgeBasedGraphHeader *>(edge_based_graph_region.get_address()));
+    const EdgeBasedGraphHeader graph_header =
+        *(reinterpret_cast<const EdgeBasedGraphHeader *>(edge_based_graph_region.get_address()));
 
     const util::FingerPrint fingerprint_valid = util::FingerPrint::GetValid();
     graph_header.fingerprint.TestContractor(fingerprint_valid);
@@ -737,14 +739,17 @@ EdgeID Contractor::LoadEdgeExpandedGraph(
 
     tbb::parallel_invoke(maybe_save_geometries, save_datasource_indexes, save_datastore_names);
 
-    auto penaltyblock =
-        reinterpret_cast<const extractor::lookup::PenaltyBlock *>(edge_penalty_region.get_address());
+    auto penaltyblock = reinterpret_cast<const extractor::lookup::PenaltyBlock *>(
+        edge_penalty_region.get_address());
     auto edge_segment_byte_ptr = reinterpret_cast<const char *>(edge_segment_region.get_address());
     auto edge_based_edge_ptr = reinterpret_cast<extractor::EdgeBasedEdge *>(
-        reinterpret_cast<char *>(edge_based_graph_region.get_address()) + sizeof(EdgeBasedGraphHeader));
+        reinterpret_cast<char *>(edge_based_graph_region.get_address()) +
+        sizeof(EdgeBasedGraphHeader));
 
     const auto edge_based_edge_last = reinterpret_cast<extractor::EdgeBasedEdge *>(
-        reinterpret_cast<char *>(edge_based_graph_region.get_address()) + sizeof(EdgeBasedGraphHeader) + sizeof(extractor::EdgeBasedEdge) * graph_header.number_of_edges);
+        reinterpret_cast<char *>(edge_based_graph_region.get_address()) +
+        sizeof(EdgeBasedGraphHeader) +
+        sizeof(extractor::EdgeBasedEdge) * graph_header.number_of_edges);
 
     while (edge_based_edge_ptr != edge_based_edge_last)
     {
@@ -754,6 +759,7 @@ EdgeID Contractor::LoadEdgeExpandedGraph(
 
         if (update_edge_weights || update_turn_penalties)
         {
+            bool skip_this_edge = false;
             auto header = reinterpret_cast<const extractor::lookup::SegmentHeaderBlock *>(
                 edge_segment_byte_ptr);
             edge_segment_byte_ptr += sizeof(extractor::lookup::SegmentHeaderBlock);
@@ -777,13 +783,17 @@ EdgeID Contractor::LoadEdgeExpandedGraph(
                 {
                     if (speed_iter->speed_source.speed > 0)
                     {
-                        auto new_segment_weight = distanceAndSpeedToWeight(segmentblocks[i].segment_length, speed_iter->speed_source.speed);
+                        auto new_segment_weight = distanceAndSpeedToWeight(
+                            segmentblocks[i].segment_length, speed_iter->speed_source.speed);
                         new_weight += new_segment_weight;
                     }
                     else
                     {
-                        // This edge is blocked, we don't need to continue updating
-                        new_weight = INVALID_EDGE_WEIGHT;
+                        // If we hit a 0-speed edge, then it's effectively not traversible.
+                        // We don't want to include it in the edge_based_edge_list, so
+                        // we set a flag and `continue` the parent loop as soon as we can.
+                        // This would be a perfect place to use `goto`, but Patrick vetoed it.
+                        skip_this_edge = true;
                         break;
                     }
                 }
@@ -796,6 +806,14 @@ EdgeID Contractor::LoadEdgeExpandedGraph(
                 previous_osm_node_id = segmentblocks[i].this_osm_node_id;
             }
 
+            // We found a zero-speed edge, so we'll skip this whole edge-based-edge which
+            // effectively removes it from the routing network.
+            if (skip_this_edge)
+            {
+                penaltyblock++;
+                continue;
+            }
+
             const auto turn_iter = turn_penalty_lookup.find(
                 std::make_tuple(penaltyblock->from_id, penaltyblock->via_id, penaltyblock->to_id));
             if (turn_iter != turn_penalty_lookup.end())
diff --git a/src/engine/api/json_factory.cpp b/src/engine/api/json_factory.cpp
index 70579b7..7e30e00 100644
--- a/src/engine/api/json_factory.cpp
+++ b/src/engine/api/json_factory.cpp
@@ -50,7 +50,7 @@ const constexpr char *turn_type_names[] = {
     "roundabout",      "roundabout", "rotary",   "rotary",      "roundabout turn",
     "roundabout turn", "use lane",   "invalid",  "invalid",     "invalid",
     "invalid",         "invalid",    "invalid",  "invalid",     "invalid",
-    "invalid"};
+    "invalid",         "invalid"};
 
 const constexpr char *waypoint_type_names[] = {"invalid", "arrive", "depart"};
 
@@ -68,6 +68,8 @@ inline bool hasValidLanes(const guidance::Intersection &intersection)
 
 std::string instructionTypeToString(const TurnType::Enum type)
 {
+    static_assert(sizeof(turn_type_names) / sizeof(turn_type_names[0]) >= TurnType::MaxTurnType,
+                  "Some turn types has not string representation.");
     return turn_type_names[static_cast<std::size_t>(type)];
 }
 
@@ -83,7 +85,8 @@ util::json::Array lanesFromIntersection(const guidance::Intersection &intersecti
         util::json::Object lane;
         lane.values["indications"] = extractor::guidance::TurnLaneType::toJsonArray(lane_desc);
         if (lane_id >= intersection.lanes.first_lane_from_the_right &&
-            lane_id < intersection.lanes.first_lane_from_the_right + intersection.lanes.lanes_in_turn)
+            lane_id <
+                intersection.lanes.first_lane_from_the_right + intersection.lanes.lanes_in_turn)
             lane.values["valid"] = util::json::True();
         else
             lane.values["valid"] = util::json::False();
@@ -96,11 +99,17 @@ util::json::Array lanesFromIntersection(const guidance::Intersection &intersecti
 
 std::string instructionModifierToString(const DirectionModifier::Enum modifier)
 {
+    static_assert(sizeof(modifier_names) / sizeof(modifier_names[0]) >=
+                      DirectionModifier::MaxDirectionModifier,
+                  "Some direction modifiers has not string representation.");
     return modifier_names[static_cast<std::size_t>(modifier)];
 }
 
 std::string waypointTypeToString(const guidance::WaypointType waypoint_type)
 {
+    static_assert(sizeof(waypoint_type_names) / sizeof(waypoint_type_names[0]) >=
+                      static_cast<size_t>(guidance::WaypointType::MaxWaypointType),
+                  "Some waypoint types has not string representation.");
     return waypoint_type_names[static_cast<std::size_t>(waypoint_type)];
 }
 
@@ -226,12 +235,20 @@ util::json::Object makeRouteStep(guidance::RouteStep step, util::json::Value geo
     route_step.values["distance"] = std::round(step.distance * 10) / 10.;
     route_step.values["duration"] = std::round(step.duration * 10) / 10.;
     route_step.values["name"] = std::move(step.name);
+    if (!step.ref.empty())
+        route_step.values["ref"] = std::move(step.ref);
     if (!step.pronunciation.empty())
         route_step.values["pronunciation"] = std::move(step.pronunciation);
     if (!step.destinations.empty())
         route_step.values["destinations"] = std::move(step.destinations);
     if (!step.rotary_name.empty())
+    {
         route_step.values["rotary_name"] = std::move(step.rotary_name);
+        if (!step.rotary_pronunciation.empty())
+        {
+            route_step.values["rotary_pronunciation"] = std::move(step.rotary_pronunciation);
+        }
+    }
 
     route_step.values["mode"] = detail::modeToString(std::move(step.mode));
     route_step.values["maneuver"] = makeStepManeuver(std::move(step.maneuver));
diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp
index 22eb150..df0db57 100644
--- a/src/engine/engine.cpp
+++ b/src/engine/engine.cpp
@@ -124,7 +124,7 @@ namespace osrm
 namespace engine
 {
 
-Engine::Engine(EngineConfig &config)
+Engine::Engine(const EngineConfig &config)
 {
     if (config.use_shared_memory)
     {
@@ -146,7 +146,7 @@ Engine::Engine(EngineConfig &config)
 
     route_plugin = create<ViaRoutePlugin>(*query_data_facade, config.max_locations_viaroute);
     table_plugin = create<TablePlugin>(*query_data_facade, config.max_locations_distance_table);
-    nearest_plugin = create<NearestPlugin>(*query_data_facade);
+    nearest_plugin = create<NearestPlugin>(*query_data_facade, config.max_results_nearest);
     trip_plugin = create<TripPlugin>(*query_data_facade, config.max_locations_trip);
     match_plugin = create<MatchPlugin>(*query_data_facade, config.max_locations_map_matching);
     tile_plugin = create<TilePlugin>(*query_data_facade);
@@ -157,32 +157,32 @@ Engine::~Engine() = default;
 Engine::Engine(Engine &&) noexcept = default;
 Engine &Engine::operator=(Engine &&) noexcept = default;
 
-Status Engine::Route(const api::RouteParameters &params, util::json::Object &result)
+Status Engine::Route(const api::RouteParameters &params, util::json::Object &result) const
 {
     return RunQuery(lock, *query_data_facade, params, *route_plugin, result);
 }
 
-Status Engine::Table(const api::TableParameters &params, util::json::Object &result)
+Status Engine::Table(const api::TableParameters &params, util::json::Object &result) const
 {
     return RunQuery(lock, *query_data_facade, params, *table_plugin, result);
 }
 
-Status Engine::Nearest(const api::NearestParameters &params, util::json::Object &result)
+Status Engine::Nearest(const api::NearestParameters &params, util::json::Object &result) const
 {
     return RunQuery(lock, *query_data_facade, params, *nearest_plugin, result);
 }
 
-Status Engine::Trip(const api::TripParameters &params, util::json::Object &result)
+Status Engine::Trip(const api::TripParameters &params, util::json::Object &result) const
 {
     return RunQuery(lock, *query_data_facade, params, *trip_plugin, result);
 }
 
-Status Engine::Match(const api::MatchParameters &params, util::json::Object &result)
+Status Engine::Match(const api::MatchParameters &params, util::json::Object &result) const
 {
     return RunQuery(lock, *query_data_facade, params, *match_plugin, result);
 }
 
-Status Engine::Tile(const api::TileParameters &params, std::string &result)
+Status Engine::Tile(const api::TileParameters &params, std::string &result) const
 {
     return RunQuery(lock, *query_data_facade, params, *tile_plugin, result);
 }
diff --git a/src/engine/engine_config.cpp b/src/engine/engine_config.cpp
index e26a67e..9d32248 100644
--- a/src/engine/engine_config.cpp
+++ b/src/engine/engine_config.cpp
@@ -15,11 +15,15 @@ bool EngineConfig::IsValid() const
         storage_config.datasource_names_path.empty() &&
         storage_config.datasource_indexes_path.empty() && storage_config.names_data_path.empty();
 
-    const bool limits_valid =
-        (max_locations_distance_table == -1 || max_locations_distance_table > 2) &&
-        (max_locations_map_matching == -1 || max_locations_map_matching > 2) &&
-        (max_locations_trip == -1 || max_locations_trip > 2) &&
-        (max_locations_viaroute == -1 || max_locations_viaroute > 2);
+    const auto unlimited_or_more_than = [](const int v, const int limit) {
+        return v == -1 || v > limit;
+    };
+
+    const bool limits_valid = unlimited_or_more_than(max_locations_distance_table, 2) &&
+                              unlimited_or_more_than(max_locations_map_matching, 2) &&
+                              unlimited_or_more_than(max_locations_trip, 2) &&
+                              unlimited_or_more_than(max_locations_viaroute, 2) &&
+                              unlimited_or_more_than(max_results_nearest, 0);
 
     return ((use_shared_memory && all_path_are_empty) || storage_config.IsValid()) && limits_valid;
 }
diff --git a/src/engine/guidance/lane_processing.cpp b/src/engine/guidance/lane_processing.cpp
index 03bb3c4..a447887 100644
--- a/src/engine/guidance/lane_processing.cpp
+++ b/src/engine/guidance/lane_processing.cpp
@@ -3,8 +3,12 @@
 #include "util/guidance/toolkit.hpp"
 
 #include "extractor/guidance/turn_instruction.hpp"
+#include "engine/guidance/post_processing.hpp"
+#include "engine/guidance/toolkit.hpp"
 
 #include <iterator>
+#include <unordered_set>
+#include <utility>
 
 using TurnInstruction = osrm::extractor::guidance::TurnInstruction;
 namespace TurnType = osrm::extractor::guidance::TurnType;
@@ -23,47 +27,45 @@ namespace guidance
 std::vector<RouteStep> anticipateLaneChange(std::vector<RouteStep> steps,
                                             const double min_duration_needed_for_lane_change)
 {
-    // Postprocessing does not strictly guarantee for only turns
-    const auto is_turn = [](const RouteStep &step) {
-        return step.maneuver.instruction.type != TurnType::NewName &&
-               step.maneuver.instruction.type != TurnType::Notification;
+    // Lane anticipation works on contiguous ranges of quick steps that have lane information
+    const auto is_quick_has_lanes = [&](const RouteStep &step) {
+        const auto is_quick = step.duration < min_duration_needed_for_lane_change;
+        const auto has_lanes = step.intersections.front().lanes.lanes_in_turn > 0;
+        return has_lanes && is_quick;
     };
 
-    const auto is_quick = [min_duration_needed_for_lane_change](const RouteStep &step) {
-        return step.duration < min_duration_needed_for_lane_change;
-    };
-
-    const auto is_quick_turn = [&](const RouteStep &step) {
-        return is_turn(step) && is_quick(step);
-    };
-
-    // Determine range of subsequent quick turns, candidates for possible lane anticipation
     using StepIter = decltype(steps)::iterator;
     using StepIterRange = std::pair<StepIter, StepIter>;
 
-    std::vector<StepIterRange> subsequent_quick_turns;
+    std::vector<StepIterRange> quick_lanes_ranges;
 
-    const auto keep_turn_range = [&](StepIterRange range) {
+    const auto range_back_inserter = [&](StepIterRange range) {
         if (std::distance(range.first, range.second) > 1)
-            subsequent_quick_turns.push_back(std::move(range));
+            quick_lanes_ranges.push_back(std::move(range));
     };
 
-    util::group_by(begin(steps), end(steps), is_quick_turn, keep_turn_range);
+    util::group_by(begin(steps), end(steps), is_quick_has_lanes, range_back_inserter);
+
+    // The lanes for a keep straight depend on the next left/right turn. Tag them in advance.
+    std::unordered_set<const RouteStep *> is_straight_left;
+    std::unordered_set<const RouteStep *> is_straight_right;
 
     // Walk backwards over all turns, constraining possible turn lanes.
     // Later turn lanes constrain earlier ones: we have to anticipate lane changes.
-    const auto constrain_lanes = [](const StepIterRange &turns) {
+    const auto constrain_lanes = [&](const StepIterRange &turns) {
         const std::reverse_iterator<StepIter> rev_first{turns.second};
         const std::reverse_iterator<StepIter> rev_last{turns.first};
 
         // We're walking backwards over all adjacent turns:
         // the current turn lanes constrain the lanes we have to take in the previous turn.
-        util::for_each_pair(rev_first, rev_last, [](RouteStep &current, RouteStep &previous) {
+        util::for_each_pair(rev_first, rev_last, [&](RouteStep &current, RouteStep &previous) {
             const auto current_inst = current.maneuver.instruction;
             const auto current_lanes = current.intersections.front().lanes;
 
             // Constrain the previous turn's lanes
             auto &previous_lanes = previous.intersections.front().lanes;
+            const auto previous_inst = previous.maneuver.instruction;
+
             // Lane mapping (N:M) from previous lanes (N) to current lanes (M), with:
             //  N > M, N > 1   fan-in situation, constrain N lanes to min(N,M) shared lanes
             //  otherwise      nothing to constrain
@@ -73,34 +75,137 @@ std::vector<RouteStep> anticipateLaneChange(std::vector<RouteStep> steps,
             if (!lanes_to_constrain || !lanes_fan_in)
                 return;
 
-            // In case there is no lane information we work with one artificial lane
-            const auto current_adjusted_lanes = std::max(current_lanes.lanes_in_turn, LaneID{1});
+            // We do not have a mapping from lanes to lanes. All we have is the lanes in the turn
+            // and all the lanes at that situation. To perfectly handle lane anticipation in cases
+            // where lanes in the turn fan in but for example the overall lanes at that location
+            // fan out, we would have to know the asymmetric mapping of lanes. This is currently
+            // not possible at the moment. In the following we implement a heuristic instead.
+            const LaneID current_num_all_lanes =
+                current.intersections.front().lane_description.size();
+            const LaneID current_num_lanes_right_of_turn = current_lanes.first_lane_from_the_right;
+            const LaneID current_num_lanes_left_of_turn =
+                current_num_all_lanes -
+                (current_lanes.lanes_in_turn + current_num_lanes_right_of_turn);
+
+            const LaneID num_shared_lanes = std::min(current_lanes.lanes_in_turn,   //
+                                                     previous_lanes.lanes_in_turn); //
+
+            // 0/ Tag keep straight with the next turn's direction if available
+            const auto previous_is_straight =
+                !isLeftTurn(previous_inst) && !isRightTurn(previous_inst);
+
+            if (previous_is_straight)
+            {
+                if (isLeftTurn(current_inst) || is_straight_left.count(&current) > 0)
+                    is_straight_left.insert(&previous);
+                else if (isRightTurn(current_inst) || is_straight_right.count(&current) > 0)
+                    is_straight_right.insert(&previous);
+            }
 
-            const auto num_shared_lanes = std::min(current_adjusted_lanes, //
-                                                   previous_lanes.lanes_in_turn);
+            // 1/ How to anticipate left, right:
+            const auto anticipate_for_left_turn = [&] {
+                // Current turn is left turn, already keep left during previous turn.
+                // This implies constraining the rightmost lanes in previous step.
+                LaneID new_first_lane_from_the_right =
+                    previous_lanes.first_lane_from_the_right // start from rightmost lane
+                    + previous_lanes.lanes_in_turn           // one past leftmost lane
+                    - num_shared_lanes;                      // back number of new lanes
 
-            if (isRightTurn(current_inst))
-            {
+                // The leftmost target lanes might not be involved in the turn. Figure out
+                // how many lanes are to the left and not in the turn.
+                new_first_lane_from_the_right -=
+                    std::min(current_num_lanes_left_of_turn, num_shared_lanes);
+
+                previous_lanes = {num_shared_lanes, new_first_lane_from_the_right};
+            };
+
+            const auto anticipate_for_right_turn = [&] {
                 // Current turn is right turn, already keep right during the previous turn.
                 // This implies constraining the leftmost lanes in the previous turn step.
-                previous_lanes = {num_shared_lanes, previous_lanes.first_lane_from_the_right};
+                LaneID new_first_lane_from_the_right = previous_lanes.first_lane_from_the_right;
+
+                // The rightmost target lanes might not be involved in the turn. Figure out
+                // how many lanes are to the right and not in the turn.
+                new_first_lane_from_the_right +=
+                    std::min(current_num_lanes_right_of_turn, num_shared_lanes);
+
+                previous_lanes = {num_shared_lanes, new_first_lane_from_the_right};
+            };
+
+            // 2/ When to anticipate a left, right turn
+            if (isLeftTurn(current_inst))
+                anticipate_for_left_turn();
+            else if (isRightTurn(current_inst))
+                anticipate_for_right_turn();
+            else // keepStraight
+            {
+                // Heuristic: we do not have a from-lanes -> to-lanes mapping. What we use
+                // here instead in addition is the number of all lanes (not only the lanes
+                // in a turn):
+                //
+                // -v-v v-v-        straight follows
+                //  | | | |
+                // <- v v ->        keep straight here
+                //    | |
+                //  <-| |->
+                //
+                // A route from the top left to the bottom right here goes over a keep
+                // straight. If we handle all keep straights as right turns (in right-sided
+                // driving), we wrongly guide the user to the rightmost lanes in the first turn.
+                // Not only is this wrong but the opposite of what we expect.
+                //
+                // The following implements a heuristic to determine a keep straight's
+                // direction in relation to the next step. In the above example we would get:
+                //
+                // coming from right, going to left (in direction of way) -> handle as left turn
+
+                if (is_straight_left.count(&current) > 0)
+                    anticipate_for_left_turn();
+                else if (is_straight_right.count(&current) > 0)
+                    anticipate_for_right_turn();
+                else // FIXME: right-sided driving
+                    anticipate_for_right_turn();
             }
-            else if (isLeftTurn(current_inst))
+
+            // We might have constrained the previous step in a way that makes it compatible
+            // with the current step. If we did so we collapse it here and mark the current
+            // step as invalid, scheduled for later removal.
+            if (collapsable(previous, current))
             {
-                // Current turn is left turn, already keep left during previous turn.
-                // This implies constraining the rightmost lanes in the previous turn step.
-                const LaneID shared_lane_delta = previous_lanes.lanes_in_turn - num_shared_lanes;
-                const LaneID previous_adjusted_lanes =
-                    std::min(current_adjusted_lanes, shared_lane_delta);
-                const LaneID constraint_first_lane_from_the_right =
-                    previous_lanes.first_lane_from_the_right + previous_adjusted_lanes;
-
-                previous_lanes = {num_shared_lanes, constraint_first_lane_from_the_right};
+                previous = elongate(previous, current);
+                current.maneuver.instruction = TurnInstruction::NO_TURN();
             }
         });
     };
 
-    std::for_each(begin(subsequent_quick_turns), end(subsequent_quick_turns), constrain_lanes);
+    std::for_each(begin(quick_lanes_ranges), end(quick_lanes_ranges), constrain_lanes);
+
+    // Lane Anticipation might have collapsed steps after constraining lanes. Remove invalid steps.
+    steps = removeNoTurnInstructions(std::move(steps));
+
+    return steps;
+}
+
+std::vector<RouteStep> removeLanesFromRoundabouts(std::vector<RouteStep> steps)
+{
+    using namespace util::guidance;
+
+    const auto removeLanes = [](RouteStep &step) {
+        for (auto &intersection : step.intersections)
+        {
+            intersection.lane_description = {};
+            intersection.lanes = {};
+        }
+    };
+
+    for (auto &step : steps)
+    {
+        const auto inst = step.maneuver.instruction;
+
+        if (entersRoundabout(inst) || staysOnRoundabout(inst) || leavesRoundabout(inst))
+            removeLanes(step);
+    }
+
     return steps;
 }
 
diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp
index 2bf41ea..7f02b66 100644
--- a/src/engine/guidance/post_processing.cpp
+++ b/src/engine/guidance/post_processing.cpp
@@ -1,10 +1,11 @@
-#include "extractor/guidance/turn_instruction.hpp"
 #include "engine/guidance/post_processing.hpp"
+#include "extractor/guidance/turn_instruction.hpp"
 
 #include "engine/guidance/assemble_steps.hpp"
 #include "engine/guidance/lane_processing.hpp"
 #include "engine/guidance/toolkit.hpp"
 
+#include "util/attributes.hpp"
 #include "util/guidance/toolkit.hpp"
 #include "util/guidance/turn_lanes.hpp"
 
@@ -15,7 +16,6 @@
 #include <algorithm>
 #include <cmath>
 #include <cstddef>
-#include <iostream>
 #include <limits>
 #include <utility>
 
@@ -37,6 +37,25 @@ namespace
 const constexpr std::size_t MIN_END_OF_ROAD_INTERSECTIONS = std::size_t{2};
 const constexpr double MAX_COLLAPSE_DISTANCE = 30;
 
+// check if at least one of the turns is actually a maneuver
+inline bool hasManeuver(const RouteStep &first, const RouteStep &second)
+{
+    return first.maneuver.instruction.type != TurnType::Suppressed ||
+           second.maneuver.instruction.type != TurnType::Suppressed;
+}
+
+// forward all signage/name data from one step to another.
+// When we collapse a step, we might have to transfer the name, pronunciation and similar tags.
+inline void forwardStepSignage(RouteStep &destination, const RouteStep &origin)
+{
+    destination.name_id = origin.name_id;
+    destination.name = origin.name;
+    destination.pronunciation = origin.pronunciation;
+    destination.destinations = origin.destinations;
+    destination.destinations = origin.destinations;
+    destination.ref = origin.ref;
+}
+
 inline bool choiceless(const RouteStep &step, const RouteStep &previous)
 {
     // if the next turn is choiceless, we consider longer turn roads collapsable than usually
@@ -62,30 +81,8 @@ bool isCollapsableInstruction(const TurnInstruction instruction)
            (instruction.type == TurnType::Merge);
 }
 
-// A check whether two instructions can be treated as one. This is only the case for very short
-// maneuvers that can, in some form, be seen as one. The additional in_step is to find out about
-// a possible u-turn.
-bool collapsable(const RouteStep &step)
-{
-    return step.distance < MAX_COLLAPSE_DISTANCE &&
-           (step.maneuver.instruction.type == TurnType::UseLane ||
-            isCollapsableInstruction(step.maneuver.instruction));
-}
-
 bool compatible(const RouteStep &lhs, const RouteStep &rhs) { return lhs.mode == rhs.mode; }
 
-double nameSegmentLength(std::size_t at, const std::vector<RouteStep> &steps)
-{
-    double result = steps[at].distance;
-    while (at + 1 < steps.size() && steps[at + 1].name_id == steps[at].name_id)
-    {
-        ++at;
-        result += steps[at].distance;
-    }
-
-    return result;
-}
-
 // invalidate a step and set its content to nothing
 void invalidateStep(RouteStep &step) { step = getInvalidRouteStep(); }
 
@@ -120,6 +117,29 @@ double turn_angle(const double entry_bearing, const double exit_bearing)
     return angle > 360 ? angle - 360 : angle;
 }
 
+// Checks if name change happens the user wants to know about.
+// Treats e.g. "Name (Ref)" -> "Name" changes still as same name.
+bool isNoticeableNameChange(const RouteStep &lhs, const RouteStep &rhs)
+{
+    // TODO: at some point we might want to think about pronunciation here.
+    // Also rotary_name is not handled at the moment.
+    return util::guidance::requiresNameAnnounced(lhs.name, lhs.ref, rhs.name, rhs.ref);
+}
+
+double nameSegmentLength(std::size_t at, const std::vector<RouteStep> &steps)
+{
+    BOOST_ASSERT(at < steps.size());
+
+    double result = steps[at].distance;
+    while (at + 1 < steps.size() && !isNoticeableNameChange(steps[at], steps[at + 1]))
+    {
+        at += 1;
+        result += steps[at].distance;
+    }
+    return result;
+}
+
+OSRM_ATTR_WARN_UNUSED
 RouteStep forwardInto(RouteStep destination, const RouteStep &source)
 {
     // Merge a turn into a silent turn
@@ -158,7 +178,10 @@ void fixFinalRoundabout(std::vector<RouteStep> &steps)
             // remember the current name as rotary name in tha case we end in a rotary
             if (propagation_step.maneuver.instruction.type == TurnType::EnterRotary ||
                 propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit)
+            {
                 propagation_step.rotary_name = propagation_step.name;
+                propagation_step.rotary_pronunciation = propagation_step.pronunciation;
+            }
 
             else if (propagation_step.maneuver.instruction.type ==
                          TurnType::EnterRoundaboutIntersection ||
@@ -190,6 +213,8 @@ bool setUpRoundabout(RouteStep &step)
         instruction.type == TurnType::EnterRoundaboutAtExit ||
         instruction.type == TurnType::EnterRoundaboutIntersectionAtExit)
     {
+        // Here we consider an actual entry, not an exit. We simply have to count the additional
+        // exit
         step.maneuver.exit = 1;
         // prevent futher special case handling of these two.
         if (instruction.type == TurnType::EnterRotaryAtExit)
@@ -202,6 +227,7 @@ bool setUpRoundabout(RouteStep &step)
 
     if (leavesRoundabout(instruction))
     {
+        // This set-up, even though it looks the same, is actually looking at entering AND exiting
         step.maneuver.exit = 1; // count the otherwise missing exit
 
         // prevent futher special case handling of these two.
@@ -256,7 +282,10 @@ void closeOffRoundabout(const bool on_roundabout,
         };
         steps[1].maneuver.instruction.type = exitToEnter(step.maneuver.instruction.type);
         if (steps[1].maneuver.instruction.type == TurnType::EnterRotary)
+        {
             steps[1].rotary_name = steps[0].name;
+            steps[1].rotary_pronunciation = steps[0].pronunciation;
+        }
     }
 
     // Normal exit from the roundabout, or exit from a previously fixed roundabout. Propagate the
@@ -268,8 +297,7 @@ void closeOffRoundabout(const bool on_roundabout,
     // intersections are locations passed along the way
     const auto exit_intersection = steps[step_index].intersections.front();
     const auto exit_bearing = exit_intersection.bearings[exit_intersection.out];
-    const auto destination_name = step.name;
-    const auto destinatino_name_id = step.name_id;
+    const auto destination_copy = step;
     if (step_index > 1)
     {
         // The very first route-step is head, so we cannot iterate past that one
@@ -287,6 +315,7 @@ void closeOffRoundabout(const bool on_roundabout,
                     propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit)
                 {
                     propagation_step.rotary_name = propagation_step.name;
+                    propagation_step.rotary_pronunciation = propagation_step.pronunciation;
                 }
                 else if (propagation_step.maneuver.instruction.type ==
                              TurnType::EnterRoundaboutIntersection ||
@@ -303,8 +332,7 @@ void closeOffRoundabout(const bool on_roundabout,
                         ::osrm::util::guidance::getTurnDirection(angle);
                 }
 
-                propagation_step.name = destination_name;
-                propagation_step.name_id = destinatino_name_id;
+                forwardStepSignage(propagation_step, destination_copy);
                 invalidateStep(steps[propagation_index + 1]);
                 break;
             }
@@ -317,41 +345,6 @@ void closeOffRoundabout(const bool on_roundabout,
     }
 }
 
-// elongate a step by another. the data is added either at the front, or the back
-RouteStep elongate(RouteStep step, const RouteStep &by_step)
-{
-    BOOST_ASSERT(step.mode == by_step.mode);
-
-    step.duration += by_step.duration;
-    step.distance += by_step.distance;
-
-    // by_step comes after step -> we append at the end
-    if (step.geometry_end == by_step.geometry_begin + 1)
-    {
-        step.geometry_end = by_step.geometry_end;
-
-        // if we elongate in the back, we only need to copy the intersections to the beginning.
-        // the bearings remain the same, as the location of the turn doesn't change
-        step.intersections.insert(
-            step.intersections.end(), by_step.intersections.begin(), by_step.intersections.end());
-    }
-    // by_step comes before step -> we append at the front
-    else
-    {
-        BOOST_ASSERT(step.maneuver.waypoint_type == WaypointType::None &&
-                     by_step.maneuver.waypoint_type == WaypointType::None);
-        BOOST_ASSERT(by_step.geometry_end == step.geometry_begin + 1);
-        step.geometry_begin = by_step.geometry_begin;
-
-        // elongating in the front changes the location of the maneuver
-        step.maneuver = by_step.maneuver;
-
-        step.intersections.insert(
-            step.intersections.begin(), by_step.intersections.begin(), by_step.intersections.end());
-    }
-    return step;
-}
-
 void collapseTurnAt(std::vector<RouteStep> &steps,
                     const std::size_t two_back_index,
                     const std::size_t one_back_index,
@@ -360,10 +353,9 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
     BOOST_ASSERT(step_index < steps.size());
     BOOST_ASSERT(one_back_index < steps.size());
     const auto &current_step = steps[step_index];
-
     const auto &one_back_step = steps[one_back_index];
 
-    // This function assumes driving on the right hand side of the streat
+    // FIXME: this function assumes driving on the right hand side of the streat
     const auto bearingsAreReversed = [](const double bearing_in, const double bearing_out) {
         // Nearly perfectly reversed angles have a difference close to 180 degrees (straight)
         const double left_turn_angle = [&]() {
@@ -375,8 +367,12 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
     };
 
     BOOST_ASSERT(!one_back_step.intersections.empty() && !current_step.intersections.empty());
+
+    if (!hasManeuver(one_back_step, current_step))
+        return;
+
     // Very Short New Name
-    if (((collapsable(one_back_step) ||
+    if (((collapsable(one_back_step, current_step) ||
           (isCollapsableInstruction(one_back_step.maneuver.instruction) &&
            choiceless(current_step, one_back_step))) &&
          !(one_back_step.maneuver.instruction.type == TurnType::Merge)))
@@ -403,6 +399,11 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
                          DirectionModifier::Straight &&
                      one_back_step.intersections.front().bearings.size() > 2)
                 steps[step_index].maneuver.instruction.type = TurnType::Turn;
+            else if (TurnType::UseLane == current_step.maneuver.instruction.type &&
+                     current_step.maneuver.instruction.direction_modifier !=
+                         DirectionModifier::Straight &&
+                     one_back_step.intersections.front().bearings.size() > 2)
+                steps[step_index].maneuver.instruction.type = TurnType::Turn;
 
             steps[two_back_index] = elongate(std::move(steps[two_back_index]), one_back_step);
             // If the previous instruction asked to continue, the name change will have to
@@ -418,12 +419,16 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
         if (compatible(one_back_step, current_step))
         {
             steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]);
+
             if ((TurnType::Continue == one_back_step.maneuver.instruction.type ||
                  TurnType::Suppressed == one_back_step.maneuver.instruction.type) &&
-                current_step.name_id != steps[two_back_index].name_id)
+                isNoticeableNameChange(steps[two_back_index], current_step))
+            {
+
                 steps[one_back_index].maneuver.instruction.type = TurnType::Turn;
+            }
             else if (TurnType::Turn == one_back_step.maneuver.instruction.type &&
-                     current_step.name_id == steps[two_back_index].name_id)
+                     !isNoticeableNameChange(steps[two_back_index], current_step))
             {
                 steps[one_back_index].maneuver.instruction.type = TurnType::Continue;
 
@@ -437,8 +442,11 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
                 if (bearingsAreReversed(
                         util::bearing::reverseBearing(getBearing(true, one_back_step)),
                         getBearing(false, current_step)))
+                {
+                    steps[one_back_index].maneuver.instruction.type = TurnType::Continue;
                     steps[one_back_index].maneuver.instruction.direction_modifier =
                         DirectionModifier::UTurn;
+                }
             }
             else if (TurnType::Merge == one_back_step.maneuver.instruction.type &&
                      current_step.maneuver.instruction.type !=
@@ -450,8 +458,7 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
                     util::guidance::mirrorDirectionModifier(
                         steps[one_back_index].maneuver.instruction.direction_modifier);
             }
-            steps[one_back_index].name = current_step.name;
-            steps[one_back_index].name_id = current_step.name_id;
+            forwardStepSignage(steps[one_back_index], current_step);
             invalidateStep(steps[step_index]);
         }
     }
@@ -467,32 +474,36 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
     {
         BOOST_ASSERT(two_back_index < steps.size());
         // the simple case is a u-turn that changes directly into the in-name again
-        const bool direct_u_turn = steps[two_back_index].name == current_step.name;
+        const bool direct_u_turn = !isNoticeableNameChange(steps[two_back_index], current_step);
 
         // however, we might also deal with a dual-collapse scenario in which we have to
-        // additionall collapse a name-change as well
+        // additionall collapse a name-change as welll
+        const auto next_step_index = step_index + 1;
         const bool continues_with_name_change =
-            (step_index + 1 < steps.size()) &&
-            (steps[step_index + 1].maneuver.instruction.type == TurnType::UseLane ||
-             isCollapsableInstruction(steps[step_index + 1].maneuver.instruction));
+            (next_step_index < steps.size()) &&
+            (steps[next_step_index].maneuver.instruction.type == TurnType::UseLane ||
+             isCollapsableInstruction(steps[next_step_index].maneuver.instruction));
         const bool u_turn_with_name_change =
-            continues_with_name_change && steps[step_index + 1].name == steps[two_back_index].name;
+            continues_with_name_change &&
+            !isNoticeableNameChange(steps[two_back_index], steps[next_step_index]);
 
         if (direct_u_turn || u_turn_with_name_change)
         {
             steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]);
             invalidateStep(steps[step_index]);
-            if (u_turn_with_name_change)
+            if (u_turn_with_name_change &&
+                compatible(steps[one_back_index], steps[next_step_index]))
             {
                 steps[one_back_index] =
-                    elongate(std::move(steps[one_back_index]), steps[step_index + 1]);
-                invalidateStep(steps[step_index + 1]); // will be skipped due to the
-                                                       // continue statement at the
-                                                       // beginning of this function
-            }
+                    elongate(std::move(steps[one_back_index]), steps[next_step_index]);
+                invalidateStep(steps[next_step_index]); // will be skipped due to the
+                                                        // continue statement at the
+                                                        // beginning of this function
 
-            steps[one_back_index].name = steps[two_back_index].name;
-            steps[one_back_index].name_id = steps[two_back_index].name_id;
+                forwardStepSignage(steps[one_back_index], steps[two_back_index]);
+            }
+            if (direct_u_turn)
+                forwardStepSignage(steps[one_back_index], steps[two_back_index]);
             steps[one_back_index].maneuver.instruction.type = TurnType::Continue;
             steps[one_back_index].maneuver.instruction.direction_modifier =
                 DirectionModifier::UTurn;
@@ -500,51 +511,109 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
     }
 }
 
-// Works on steps including silent and invalid instructions in order to do lane anticipation for
-// roundabouts which later on get collapsed into a single multi-hop instruction.
-std::vector<RouteStep> anticipateLaneChangeForRoundabouts(std::vector<RouteStep> steps)
+// Staggered intersection are very short zig-zags of a few meters.
+// We do not want to announce these short left-rights or right-lefts:
+//
+//      * -> b      a -> *
+//      |       or       |       becomes  a   ->   b
+// a -> *                * -> b
+//
+bool isStaggeredIntersection(const RouteStep &previous, const RouteStep &current)
 {
-    using namespace util::guidance;
-
-    using StepIter = decltype(steps)::iterator;
-    using StepIterRange = std::pair<StepIter, StepIter>;
-
-    const auto anticipate_lanes_in_roundabout = [&](StepIterRange roundabout) {
-        // We do lane anticipation on the roundabout's enter and leave step only.
-        // TODO: This means, lanes _inside_ the roundabout are ignored at the moment.
+    // Base decision on distance since the zig-zag is a visual clue.
+    // If adjusted, make sure to check validity of the is_right/is_left classification below
+    const constexpr auto MAX_STAGGERED_DISTANCE = 3; // debatable, but keep short to be on safe side
+
+    const auto angle = [](const RouteStep &step) {
+        const auto &intersection = step.intersections.front();
+        const auto entry_bearing = intersection.bearings[intersection.in];
+        const auto exit_bearing = intersection.bearings[intersection.out];
+        return turn_angle(entry_bearing, exit_bearing);
+    };
 
-        auto enter = *roundabout.first;
-        const auto leave = *roundabout.second;
+    // Instead of using turn modifiers (e.g. as in isRightTurn) we want to be more strict here.
+    // We do not want to trigger e.g. on sharp uturn'ish turns or going straight "turns".
+    // Therefore we use the turn angle to derive 90 degree'ish right / left turns.
+    // This more closely resembles what we understand as Staggered Intersection.
+    // We have to be careful in cases with larger MAX_STAGGERED_DISTANCE values. If the distance
+    // gets large, sharper angles might be not obvious enough to consider them a staggered
+    // intersection. We might need to consider making the decision here dependent on the actual turn
+    // angle taken. To do so, we could scale the angle-limits by a factor depending on the distance
+    // between the turns.
+    const auto is_right = [](const double angle) { return angle > 45 && angle < 135; };
+    const auto is_left = [](const double angle) { return angle > 225 && angle < 315; };
+
+    const auto left_right = is_left(angle(previous)) && is_right(angle(current));
+    const auto right_left = is_right(angle(previous)) && is_left(angle(current));
+
+    // A RouteStep holds distance/duration from the maneuver to the subsequent step.
+    // We are only interested in the distance between the first and the second.
+    const auto is_short = previous.distance < MAX_STAGGERED_DISTANCE;
+
+    return is_short && (left_right || right_left);
+}
 
-        // Although the enter instruction may be a left/right turn, for right-sided driving the
-        // roundabout is counter-clockwise and therefore we need to always set it to a left turn.
-        // FIXME: assumes right-side driving (counter-clockwise roundabout flow)
-        const auto enter_direction = enter.maneuver.instruction.direction_modifier;
+} // namespace
 
-        if (util::guidance::isRightTurn(enter.maneuver.instruction))
-            enter.maneuver.instruction.direction_modifier =
-                mirrorDirectionModifier(enter_direction);
+// elongate a step by another. the data is added either at the front, or the back
+OSRM_ATTR_WARN_UNUSED
+RouteStep elongate(RouteStep step, const RouteStep &by_step)
+{
+    step.duration += by_step.duration;
+    step.distance += by_step.distance;
+    BOOST_ASSERT(step.mode == by_step.mode);
 
-        // a roundabout is a continuous maneuver. We don't switch lanes within a roundabout, as long
-        // as it can be avoided.
-        auto enterAndLeave =
-            anticipateLaneChange({enter, leave}, std::numeric_limits<double>::max());
+    // by_step comes after step -> we append at the end
+    if (step.geometry_end == by_step.geometry_begin + 1)
+    {
+        step.geometry_end = by_step.geometry_end;
 
-        // Undo flipping direction on a right turn in a right-sided counter-clockwise roundabout.
-        // FIXME: assumes right-side driving (counter-clockwise roundabout flow)
-        enterAndLeave[0].maneuver.instruction.direction_modifier = enter_direction;
+        // if we elongate in the back, we only need to copy the intersections to the beginning.
+        // the bearings remain the same, as the location of the turn doesn't change
+        step.intersections.insert(
+            step.intersections.end(), by_step.intersections.begin(), by_step.intersections.end());
+    }
+    // by_step comes before step -> we append at the front
+    else
+    {
+        BOOST_ASSERT(step.maneuver.waypoint_type == WaypointType::None &&
+                     by_step.maneuver.waypoint_type == WaypointType::None);
+        BOOST_ASSERT(by_step.geometry_end == step.geometry_begin + 1);
+        step.geometry_begin = by_step.geometry_begin;
 
-        std::swap(*roundabout.first, enterAndLeave[0]);
-        std::swap(*roundabout.second, enterAndLeave[1]);
-    };
+        // elongating in the front changes the location of the maneuver
+        step.maneuver = by_step.maneuver;
 
-    forEachRoundabout(begin(steps), end(steps), anticipate_lanes_in_roundabout);
-    return steps;
+        step.intersections.insert(
+            step.intersections.begin(), by_step.intersections.begin(), by_step.intersections.end());
+    }
+    return step;
 }
-} // namespace
 
 // Post processing can invalidate some instructions. For example StayOnRoundabout
 // is turned into exit counts. These instructions are removed by the following function
+
+// A check whether two instructions can be treated as one. This is only the case for very short
+// maneuvers that can, in some form, be seen as one. Lookahead of one step.
+bool collapsable(const RouteStep &step, const RouteStep &next)
+{
+    const auto is_short_step = step.distance < MAX_COLLAPSE_DISTANCE;
+    const auto instruction_can_be_collapsed = isCollapsableInstruction(step.maneuver.instruction);
+
+    const auto is_use_lane = step.maneuver.instruction.type == TurnType::UseLane;
+    const auto lanes_dont_change =
+        step.intersections.front().lanes == next.intersections.front().lanes;
+
+    if (is_short_step && instruction_can_be_collapsed)
+        return true;
+
+    // Prevent collapsing away important lane change steps
+    if (is_short_step && is_use_lane && lanes_dont_change)
+        return true;
+
+    return false;
+}
+
 std::vector<RouteStep> removeNoTurnInstructions(std::vector<RouteStep> steps)
 {
     // finally clean up the post-processed instructions.
@@ -589,11 +658,6 @@ std::vector<RouteStep> postProcess(std::vector<RouteStep> steps)
     if (steps.size() == 2)
         return steps;
 
-    // Before we invalidate and remove silent instructions, we handle roundabouts (before they're
-    // getting collapsed into a single multi-hop instruction) by back-propagating exit lane
-    // constraints already to a roundabout's enter instruction.
-    steps = anticipateLaneChangeForRoundabouts(std::move(steps));
-
     // Count Street Exits forward
     bool on_roundabout = false;
     bool has_entered_roundabout = false;
@@ -604,22 +668,23 @@ std::vector<RouteStep> postProcess(std::vector<RouteStep> steps)
     // In this case, exits are numbered from the start of the leg.
     for (std::size_t step_index = 0; step_index < steps.size(); ++step_index)
     {
+        const auto next_step_index = step_index + 1;
         auto &step = steps[step_index];
         const auto instruction = step.maneuver.instruction;
         if (entersRoundabout(instruction))
         {
             has_entered_roundabout = setUpRoundabout(step);
 
-            if (has_entered_roundabout && step_index + 1 < steps.size())
-                steps[step_index + 1].maneuver.exit = step.maneuver.exit;
+            if (has_entered_roundabout && next_step_index < steps.size())
+                steps[next_step_index].maneuver.exit = step.maneuver.exit;
         }
         else if (instruction.type == TurnType::StayOnRoundabout)
         {
             on_roundabout = true;
             // increase the exit number we require passing the exit
             step.maneuver.exit += 1;
-            if (step_index + 1 < steps.size())
-                steps[step_index + 1].maneuver.exit = step.maneuver.exit;
+            if (next_step_index < steps.size())
+                steps[next_step_index].maneuver.exit = step.maneuver.exit;
         }
         else if (leavesRoundabout(instruction))
         {
@@ -631,9 +696,9 @@ std::vector<RouteStep> postProcess(std::vector<RouteStep> steps)
             has_entered_roundabout = false;
             on_roundabout = false;
         }
-        else if (on_roundabout && step_index + 1 < steps.size())
+        else if (on_roundabout && next_step_index < steps.size())
         {
-            steps[step_index + 1].maneuver.exit = step.maneuver.exit;
+            steps[next_step_index].maneuver.exit = step.maneuver.exit;
         }
     }
 
@@ -694,6 +759,8 @@ std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps)
             if (steps[index].maneuver.instruction.type != TurnType::Suppressed &&
                 steps[index].maneuver.instruction.type != TurnType::NewName)
                 return false;
+            if (index + 1 < end_index && !compatible(steps[index], steps[index + 1]))
+                return false;
         }
         return true;
     };
@@ -702,12 +769,17 @@ std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps)
     for (std::size_t step_index = 1; step_index + 1 < steps.size(); ++step_index)
     {
         const auto &current_step = steps[step_index];
+        const auto next_step_index = step_index + 1;
         if (current_step.maneuver.instruction.type == TurnType::NoTurn)
             continue;
         const auto one_back_index = getPreviousIndex(step_index);
         BOOST_ASSERT(one_back_index < steps.size());
 
         const auto &one_back_step = steps[one_back_index];
+
+        if (!hasManeuver(one_back_step, current_step))
+            continue;
+
         // how long has a name change to be so that we announce it, even as a bridge?
         const constexpr auto name_segment_cutoff_length = 100;
         const auto isBasicNameChange = [](const RouteStep &step) {
@@ -720,42 +792,61 @@ std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps)
         // TurnType::Sliproad != TurnType::NoTurn
         if (one_back_step.maneuver.instruction.type == TurnType::Sliproad)
         {
-            // Handle possible u-turns between highways that look like slip-roads
-            if (steps[getPreviousIndex(one_back_index)].name_id == steps[step_index].name_id &&
-                steps[step_index].name_id != EMPTY_NAMEID)
+            if (current_step.maneuver.instruction.type == TurnType::Suppressed &&
+                compatible(one_back_step, current_step))
             {
-                steps[one_back_index].maneuver.instruction.type = TurnType::Continue;
+                // Traffic light on the sliproad, the road itself will be handled in the next
+                // iteration, when one-back-index again points to the sliproad.
+                steps[one_back_index] =
+                    elongate(std::move(steps[one_back_index]), steps[step_index]);
+                invalidateStep(steps[step_index]);
             }
             else
             {
-                steps[one_back_index].maneuver.instruction.type = TurnType::Turn;
-            }
-            if (compatible(one_back_step, current_step))
-            {
-                steps[one_back_index] =
-                    elongate(std::move(steps[one_back_index]), steps[step_index]);
-                steps[one_back_index].name_id = steps[step_index].name_id;
-                steps[one_back_index].name = steps[step_index].name;
-
-                const auto exit_intersection = steps[step_index].intersections.front();
-                const auto exit_bearing = exit_intersection.bearings[exit_intersection.out];
-
-                const auto entry_intersection = steps[one_back_index].intersections.front();
-                const auto entry_bearing = entry_intersection.bearings[entry_intersection.in];
+                // Handle possible u-turns between highways that look like slip-roads
+                if (compatible(one_back_step, current_step))
+                {
+                    // Turn Types in the response depend on whether we find the same road name
+                    // (sliproad indcating a u-turn) or if we are turning onto a different road, in
+                    // which case we use a turn.
+                    if (!isNoticeableNameChange(steps[getPreviousIndex(one_back_index)],
+                                                steps[step_index]))
+                        steps[one_back_index].maneuver.instruction.type = TurnType::Continue;
+                    else
+                        steps[one_back_index].maneuver.instruction.type = TurnType::Turn;
+
+                    steps[one_back_index] =
+                        elongate(std::move(steps[one_back_index]), steps[step_index]);
+
+                    forwardStepSignage(steps[one_back_index], steps[step_index]);
+                    // the turn lanes for this turn are on the sliproad itself, so we have to
+                    // remember  them
+                    steps[one_back_index].intersections.front().lanes =
+                        current_step.intersections.front().lanes;
+                    steps[one_back_index].intersections.front().lane_description =
+                        current_step.intersections.front().lane_description;
+
+                    const auto exit_intersection = steps[step_index].intersections.front();
+                    const auto exit_bearing = exit_intersection.bearings[exit_intersection.out];
+
+                    const auto entry_intersection = steps[one_back_index].intersections.front();
+                    const auto entry_bearing = entry_intersection.bearings[entry_intersection.in];
 
-                const double angle =
-                    turn_angle(util::bearing::reverseBearing(entry_bearing), exit_bearing);
-                steps[one_back_index].maneuver.instruction.direction_modifier =
-                    ::osrm::util::guidance::getTurnDirection(angle);
-                invalidateStep(steps[step_index]);
+                    const double angle =
+                        turn_angle(util::bearing::reverseBearing(entry_bearing), exit_bearing);
+                    steps[one_back_index].maneuver.instruction.direction_modifier =
+                        ::osrm::util::guidance::getTurnDirection(angle);
+                    invalidateStep(steps[step_index]);
+                }
             }
         }
         // Due to empty segments, we can get name-changes from A->A
         // These have to be handled in post-processing
         else if (isCollapsableInstruction(current_step.maneuver.instruction) &&
                  current_step.maneuver.instruction.type != TurnType::Suppressed &&
-                 steps[getPreviousNameIndex(step_index)].name == current_step.name &&
-                 canCollapseAll(getPreviousNameIndex(step_index) + 1, step_index + 1))
+                 !isNoticeableNameChange(steps[getPreviousNameIndex(step_index)], current_step) &&
+                 // canCollapseAll is also checking for compatible(step,step+1) for all indices
+                 canCollapseAll(getPreviousNameIndex(step_index) + 1, next_step_index))
         {
             BOOST_ASSERT(step_index > 0);
             const std::size_t last_available_name_index = getPreviousNameIndex(step_index);
@@ -771,14 +862,14 @@ std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps)
         // A name oszillation changes from name A shortly to name B and back to A.
         // In these cases, the name change will be suppressed.
         else if (one_back_index > 0 && compatible(current_step, one_back_step) &&
-                 isCollapsableInstruction(current_step.maneuver.instruction) &&
-                 isCollapsableInstruction(one_back_step.maneuver.instruction))
+                 ((isCollapsableInstruction(current_step.maneuver.instruction) &&
+                   isCollapsableInstruction(one_back_step.maneuver.instruction)) ||
+                  isStaggeredIntersection(one_back_step, current_step)))
         {
             const auto two_back_index = getPreviousIndex(one_back_index);
             BOOST_ASSERT(two_back_index < steps.size());
-            // valid, since one_back is collapsable:
-            const auto &coming_from_name = steps[two_back_index].name;
-            if (current_step.name == coming_from_name)
+            // valid, since one_back is collapsable or a turn and therefore not depart:
+            if (!isNoticeableNameChange(steps[two_back_index], current_step))
             {
                 if (compatible(one_back_step, steps[two_back_index]))
                 {
@@ -794,14 +885,35 @@ std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps)
             else if (nameSegmentLength(one_back_index, steps) < name_segment_cutoff_length &&
                      isBasicNameChange(one_back_step) && isBasicNameChange(current_step))
             {
-                steps[two_back_index] =
-                    elongate(std::move(steps[two_back_index]), steps[one_back_index]);
-                invalidateStep(steps[one_back_index]);
-                if (nameSegmentLength(step_index, steps) < name_segment_cutoff_length)
+                if (compatible(steps[two_back_index], steps[one_back_index]))
                 {
                     steps[two_back_index] =
-                        elongate(std::move(steps[two_back_index]), steps[step_index]);
+                        elongate(std::move(steps[two_back_index]), steps[one_back_index]);
+                    invalidateStep(steps[one_back_index]);
+                    if (nameSegmentLength(step_index, steps) < name_segment_cutoff_length)
+                    {
+                        steps[two_back_index] =
+                            elongate(std::move(steps[two_back_index]), steps[step_index]);
+                        invalidateStep(steps[step_index]);
+                    }
+                }
+            }
+            else if (step_index + 2 < steps.size() &&
+                     current_step.maneuver.instruction.type == TurnType::NewName &&
+                     steps[next_step_index].maneuver.instruction.type == TurnType::NewName &&
+                     !isNoticeableNameChange(one_back_step, steps[next_step_index]))
+            {
+                if (compatible(steps[step_index], steps[next_step_index]))
+                {
+                    // if we are crossing an intersection and go immediately after into a name
+                    // change,
+                    // we don't wan't to collapse the initial intersection.
+                    // a - b ---BRIDGE -- c
+                    steps[one_back_index] =
+                        elongate(std::move(steps[one_back_index]),
+                                 elongate(std::move(steps[step_index]), steps[next_step_index]));
                     invalidateStep(steps[step_index]);
+                    invalidateStep(steps[next_step_index]);
                 }
             }
             else if (choiceless(current_step, one_back_step) ||
@@ -820,15 +932,16 @@ std::vector<RouteStep> collapseTurns(std::vector<RouteStep> steps)
             // check for one of the multiple collapse scenarios and, if possible, collapse the turn
             const auto two_back_index = getPreviousIndex(one_back_index);
             BOOST_ASSERT(two_back_index < steps.size());
+            // all turns that are handled lower down are also compatible
             collapseTurnAt(steps, two_back_index, one_back_index, step_index);
         }
     }
 
     // handle final sliproad
     if (steps.size() >= 3 &&
-        steps[steps.size() - 2].maneuver.instruction.type == TurnType::Sliproad)
+        steps[getPreviousIndex(steps.size() - 1)].maneuver.instruction.type == TurnType::Sliproad)
     {
-        steps[steps.size() - 2].maneuver.instruction.type = TurnType::Turn;
+        steps[getPreviousIndex(steps.size() - 1)].maneuver.instruction.type = TurnType::Turn;
     }
 
     BOOST_ASSERT(steps.front().intersections.size() >= 1);
@@ -1009,8 +1122,7 @@ void trimShortSegments(std::vector<RouteStep> &steps, LegGeometry &geometry)
         // as the segment before it.  Thus, we have to copy the names
         // and travel modes from the new next_to_last step.
         auto &new_next_to_last = *(steps.end() - 2);
-        next_to_last_step.name = new_next_to_last.name;
-        next_to_last_step.name_id = new_next_to_last.name_id;
+        forwardStepSignage(next_to_last_step, new_next_to_last);
         next_to_last_step.mode = new_next_to_last.mode;
         // the geometry indices of the last step are already correct;
     }
@@ -1136,7 +1248,8 @@ std::vector<RouteStep> buildIntersections(std::vector<RouteStep> steps)
         {
             // count intersections. We cannot use exit, since intersections can follow directly
             // after a roundabout
-            steps[last_valid_instruction] = elongate(steps[last_valid_instruction], step);
+            steps[last_valid_instruction] =
+                elongate(std::move(steps[last_valid_instruction]), step);
             step.maneuver.instruction = TurnInstruction::NO_TURN();
         }
         else if (!isSilent(instruction))
@@ -1151,7 +1264,7 @@ std::vector<RouteStep> buildIntersections(std::vector<RouteStep> steps)
             // previous instruction.
             if (instruction.type == TurnType::EndOfRoad)
             {
-                BOOST_ASSERT(step_index > 0 && step_index + 1 < steps.size());
+                BOOST_ASSERT(step_index > 0);
                 const auto &previous_step = steps[last_valid_instruction];
                 if (previous_step.intersections.size() < MIN_END_OF_ROAD_INTERSECTIONS)
                     step.maneuver.instruction.type = TurnType::Turn;
@@ -1185,7 +1298,7 @@ std::vector<RouteStep> collapseUseLane(std::vector<RouteStep> steps)
         [containsTag](const util::guidance::LaneTupel lanes,
                       extractor::guidance::TurnLaneDescription lane_description) {
             // the lane description is given left to right, lanes are counted from the right.
-            // Therefore we access the lane description yousing the reverse iterator
+            // Therefore we access the lane description using the reverse iterator
             if (lanes.first_lane_from_the_right > 0 &&
                 containsTag(*(lane_description.rbegin() + (lanes.first_lane_from_the_right - 1)),
                             (extractor::guidance::TurnLaneType::straight |
@@ -1210,8 +1323,8 @@ std::vector<RouteStep> collapseUseLane(std::vector<RouteStep> steps)
                               step.intersections.front().lane_description))
         {
             const auto previous = getPreviousIndex(step_index);
-            steps[previous] = elongate(steps[previous], steps[step_index]);
-            //elongate(steps[step_index-1], steps[step_index]);
+            steps[previous] = elongate(std::move(steps[previous]), steps[step_index]);
+            // elongate(steps[step_index-1], steps[step_index]);
             invalidateStep(steps[step_index]);
         }
     }
diff --git a/src/engine/plugins/nearest.cpp b/src/engine/plugins/nearest.cpp
index 3e54d33..e89a3d2 100644
--- a/src/engine/plugins/nearest.cpp
+++ b/src/engine/plugins/nearest.cpp
@@ -8,6 +8,7 @@
 #include <string>
 
 #include <boost/assert.hpp>
+#include <boost/numeric/conversion/cast.hpp>
 
 namespace osrm
 {
@@ -16,13 +17,25 @@ namespace engine
 namespace plugins
 {
 
-NearestPlugin::NearestPlugin(datafacade::BaseDataFacade &facade) : BasePlugin{facade} {}
+NearestPlugin::NearestPlugin(datafacade::BaseDataFacade &facade, const int max_results_)
+    : BasePlugin{facade}, max_results{max_results_}
+{
+}
 
 Status NearestPlugin::HandleRequest(const api::NearestParameters &params,
                                     util::json::Object &json_result)
 {
     BOOST_ASSERT(params.IsValid());
 
+    if (max_results > 0 &&
+        (boost::numeric_cast<std::int64_t>(params.number_of_results) > max_results))
+    {
+        return Error("TooBig",
+                     "Number of results " + std::to_string(params.number_of_results) +
+                         " is higher than current maximum (" + std::to_string(max_results) + ")",
+                     json_result);
+    }
+
     if (!CheckAllCoordinates(params.coordinates))
         return Error("InvalidOptions", "Coordinates are invalid", json_result);
 
diff --git a/src/engine/plugins/tile.cpp b/src/engine/plugins/tile.cpp
index 3877b07..67ebc93 100644
--- a/src/engine/plugins/tile.cpp
+++ b/src/engine/plugins/tile.cpp
@@ -335,7 +335,7 @@ Status TilePlugin::HandleRequest(const api::TileParameters &parameters, std::str
                                                &used_weights](const detail::FixedLine &tile_line,
                                                               const std::uint32_t speed_kmh,
                                                               const std::size_t duration,
-                                                              const std::uint8_t datasource,
+                                                              const DatasourceID datasource,
                                                               const std::size_t name,
                                                               std::int32_t &start_x,
                                                               std::int32_t &start_y) {
diff --git a/src/engine/polyline_compressor.cpp b/src/engine/polyline_compressor.cpp
index 701dac0..11954b5 100644
--- a/src/engine/polyline_compressor.cpp
+++ b/src/engine/polyline_compressor.cpp
@@ -100,7 +100,7 @@ std::vector<util::Coordinate> decodePolyline(const std::string &geometry_string)
             b = geometry_string.at(index++) - 63;
             result |= (b & 0x1f) << shift;
             shift += 5;
-        } while (b >= 0x20);
+        } while (b >= 0x20 && index < len);
         int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
         lat += dlat;
 
@@ -111,13 +111,15 @@ std::vector<util::Coordinate> decodePolyline(const std::string &geometry_string)
             b = geometry_string.at(index++) - 63;
             result |= (b & 0x1f) << shift;
             shift += 5;
-        } while (b >= 0x20);
+        } while (b >= 0x20 && index < len);
         int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
         lng += dlng;
 
         util::Coordinate p;
-        p.lat = util::FixedLatitude{static_cast<std::int32_t>(lat * detail::POLYLINE_TO_COORDINATE)};
-        p.lon = util::FixedLongitude{static_cast<std::int32_t>(lng * detail::POLYLINE_TO_COORDINATE)};
+        p.lat =
+            util::FixedLatitude{static_cast<std::int32_t>(lat * detail::POLYLINE_TO_COORDINATE)};
+        p.lon =
+            util::FixedLongitude{static_cast<std::int32_t>(lng * detail::POLYLINE_TO_COORDINATE)};
         new_coordinates.push_back(p);
     }
 
diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp
index d7182f0..a6fa6c6 100644
--- a/src/extractor/edge_based_graph_factory.cpp
+++ b/src/extractor/edge_based_graph_factory.cpp
@@ -1,10 +1,9 @@
-#include "extractor/edge_based_edge.hpp"
 #include "extractor/edge_based_graph_factory.hpp"
+#include "extractor/edge_based_edge.hpp"
 #include "util/coordinate.hpp"
 #include "util/coordinate_calculation.hpp"
 #include "util/exception.hpp"
 #include "util/integer_range.hpp"
-#include "util/lua_util.hpp"
 #include "util/percent.hpp"
 #include "util/simple_logger.hpp"
 #include "util/timing_util.hpp"
@@ -12,6 +11,7 @@
 #include "extractor/guidance/toolkit.hpp"
 #include "extractor/guidance/turn_analysis.hpp"
 #include "extractor/guidance/turn_lane_handler.hpp"
+#include "extractor/scripting_environment.hpp"
 #include "extractor/suffix_table.hpp"
 
 #include <boost/assert.hpp>
@@ -41,14 +41,16 @@ EdgeBasedGraphFactory::EdgeBasedGraphFactory(
     const std::vector<QueryNode> &node_info_list,
     ProfileProperties profile_properties,
     const util::NameTable &name_table,
-    const std::vector<std::uint32_t> &turn_lane_offsets,
-    const std::vector<guidance::TurnLaneType::Mask> &turn_lane_masks)
+    std::vector<std::uint32_t> &turn_lane_offsets,
+    std::vector<guidance::TurnLaneType::Mask> &turn_lane_masks,
+    guidance::LaneDescriptionMap &lane_description_map)
     : m_max_edge_id(0), m_node_info_list(node_info_list),
       m_node_based_graph(std::move(node_based_graph)),
       m_restriction_map(std::move(restriction_map)), m_barrier_nodes(barrier_nodes),
       m_traffic_lights(traffic_lights), m_compressed_edge_container(compressed_edge_container),
       profile_properties(std::move(profile_properties)), name_table(name_table),
-      turn_lane_offsets(turn_lane_offsets), turn_lane_masks(turn_lane_masks)
+      turn_lane_offsets(turn_lane_offsets), turn_lane_masks(turn_lane_masks),
+      lane_description_map(lane_description_map)
 {
 }
 
@@ -182,9 +184,9 @@ void EdgeBasedGraphFactory::FlushVectorToStream(
     original_edge_data_vector.clear();
 }
 
-void EdgeBasedGraphFactory::Run(const std::string &original_edge_data_filename,
+void EdgeBasedGraphFactory::Run(ScriptingEnvironment &scripting_environment,
+                                const std::string &original_edge_data_filename,
                                 const std::string &turn_lane_data_filename,
-                                lua_State *lua_state,
                                 const std::string &edge_segment_lookup_filename,
                                 const std::string &edge_penalty_filename,
                                 const bool generate_edge_lookup)
@@ -199,9 +201,9 @@ void EdgeBasedGraphFactory::Run(const std::string &original_edge_data_filename,
     TIMER_STOP(generate_nodes);
 
     TIMER_START(generate_edges);
-    GenerateEdgeExpandedEdges(original_edge_data_filename,
+    GenerateEdgeExpandedEdges(scripting_environment,
+                              original_edge_data_filename,
                               turn_lane_data_filename,
-                              lua_state,
                               edge_segment_lookup_filename,
                               edge_penalty_filename,
                               generate_edge_lookup);
@@ -298,18 +300,15 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedNodes()
 
 /// Actually it also generates OriginalEdgeData and serializes them...
 void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
+    ScriptingEnvironment &scripting_environment,
     const std::string &original_edge_data_filename,
     const std::string &turn_lane_data_filename,
-    lua_State *lua_state,
     const std::string &edge_segment_lookup_filename,
     const std::string &edge_fixed_penalties_filename,
     const bool generate_edge_lookup)
 {
     util::SimpleLogger().Write() << "generating edge-expanded edges";
 
-    BOOST_ASSERT(lua_state != nullptr);
-    const bool use_turn_function = util::luaFunctionExists(lua_state, "turn_function");
-
     std::size_t node_based_edge_counter = 0;
     std::size_t original_edges_counter = 0;
     restricted_turns_counter = 0;
@@ -338,21 +337,28 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
     // Three nested loop look super-linear, but we are dealing with a (kind of)
     // linear number of turns only.
     util::Percent progress(m_node_based_graph->GetNumberOfNodes());
-    SuffixTable street_name_suffix_table(lua_state);
+    SuffixTable street_name_suffix_table(scripting_environment);
     guidance::TurnAnalysis turn_analysis(*m_node_based_graph,
                                          m_node_info_list,
                                          *m_restriction_map,
                                          m_barrier_nodes,
                                          m_compressed_edge_container,
                                          name_table,
-                                         street_name_suffix_table);
-    guidance::lanes::TurnLaneHandler turn_lane_handler(
-        *m_node_based_graph, turn_lane_offsets, turn_lane_masks, m_node_info_list, turn_analysis);
+                                         street_name_suffix_table,
+                                         profile_properties);
+
+    guidance::LaneDataIdMap lane_data_map;
+    guidance::lanes::TurnLaneHandler turn_lane_handler(*m_node_based_graph,
+                                                       turn_lane_offsets,
+                                                       turn_lane_masks,
+                                                       lane_description_map,
+                                                       m_node_info_list,
+                                                       turn_analysis,
+                                                       lane_data_map);
 
     bearing_class_by_node_based_node.resize(m_node_based_graph->GetNumberOfNodes(),
                                             std::numeric_limits<std::uint32_t>::max());
 
-    guidance::LaneDataIdMap lane_data_map;
     for (const auto node_u : util::irange(0u, m_node_based_graph->GetNumberOfNodes()))
     {
         progress.PrintStatus(node_u);
@@ -369,8 +375,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
             intersection =
                 turn_analysis.assignTurnTypes(node_u, edge_from_u, std::move(intersection));
 
-            intersection = turn_lane_handler.assignTurnLanes(
-                node_u, edge_from_u, std::move(intersection), lane_data_map);
+            intersection =
+                turn_lane_handler.assignTurnLanes(node_u, edge_from_u, std::move(intersection));
             const auto possible_turns = turn_analysis.transformIntersectionIntoTurns(intersection);
 
             // the entry class depends on the turn, so we have to classify the interesction for
@@ -410,8 +416,6 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
 
             for (const auto turn : possible_turns)
             {
-                const double turn_angle = turn.angle;
-
                 // only add an edge if turn is not prohibited
                 const EdgeData &edge_data1 = m_node_based_graph->GetEdgeData(edge_from_u);
                 const EdgeData &edge_data2 = m_node_based_graph->GetEdgeData(turn.eid);
@@ -427,11 +431,11 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
                     distance += profile_properties.traffic_signal_penalty;
                 }
 
-                const int turn_penalty =
-                    use_turn_function ? GetTurnPenalty(turn_angle, lua_state) : 0;
+                const int32_t turn_penalty =
+                    scripting_environment.GetTurnPenalty(180. - turn.angle);
                 const auto turn_instruction = turn.instruction;
 
-                if (guidance::isUturn(turn_instruction))
+                if (turn_instruction.direction_modifier == guidance::DirectionModifier::UTurn)
                 {
                     distance += profile_properties.u_turn_penalty;
                 }
@@ -615,23 +619,5 @@ std::vector<util::guidance::EntryClass> EdgeBasedGraphFactory::GetEntryClasses()
     return result;
 }
 
-int EdgeBasedGraphFactory::GetTurnPenalty(double angle, lua_State *lua_state) const
-{
-    BOOST_ASSERT(lua_state != nullptr);
-    try
-    {
-        // call lua profile to compute turn penalty
-        double penalty = luabind::call_function<double>(lua_state, "turn_function", 180. - angle);
-        BOOST_ASSERT(penalty < std::numeric_limits<int>::max());
-        BOOST_ASSERT(penalty > std::numeric_limits<int>::min());
-        return boost::numeric_cast<int>(penalty);
-    }
-    catch (const luabind::error &er)
-    {
-        util::SimpleLogger().Write(logWARNING) << er.what();
-    }
-    return 0;
-}
-
 } // namespace extractor
 } // namespace osrm
diff --git a/src/extractor/extraction_containers.cpp b/src/extractor/extraction_containers.cpp
index 908083f..11261f2 100644
--- a/src/extractor/extraction_containers.cpp
+++ b/src/extractor/extraction_containers.cpp
@@ -7,7 +7,6 @@
 #include "util/exception.hpp"
 #include "util/fingerprint.hpp"
 #include "util/io.hpp"
-#include "util/lua_util.hpp"
 #include "util/simple_logger.hpp"
 #include "util/timing_util.hpp"
 
@@ -17,8 +16,6 @@
 #include <boost/numeric/conversion/cast.hpp>
 #include <boost/ref.hpp>
 
-#include <luabind/luabind.hpp>
-
 #include <stxxl/sort>
 
 #include <chrono>
@@ -99,7 +96,6 @@ struct CmpEdgeByInternalSourceTargetAndName
     const oe::ExtractionContainers::STXXLNameCharData &name_data;
     const oe::ExtractionContainers::STXXLNameOffsets &name_offsets;
 };
-
 }
 
 namespace osrm
@@ -114,17 +110,13 @@ ExtractionContainers::ExtractionContainers()
     // Check if stxxl can be instantiated
     stxxl::vector<unsigned> dummy_vector;
 
-    // Insert three empty strings offsets for name, destination and pronunciation
+    // Insert three empty strings offsets for name, ref, destination and pronunciation
+    name_offsets.push_back(0);
     name_offsets.push_back(0);
     name_offsets.push_back(0);
     name_offsets.push_back(0);
     // Insert the total length sentinel (corresponds to the next name string offset)
     name_offsets.push_back(0);
-
-    // the offsets have to be initialized with two values, since we have the empty turn string for
-    // the first id
-    turn_lane_offsets.push_back(0);
-    turn_lane_offsets.push_back(0);
 }
 
 /**
@@ -137,60 +129,24 @@ ExtractionContainers::ExtractionContainers()
  * - merge edges with nodes to include location of start/end points and serialize
  *
  */
-void ExtractionContainers::PrepareData(const std::string &output_file_name,
+void ExtractionContainers::PrepareData(ScriptingEnvironment &scripting_environment,
+                                       const std::string &output_file_name,
                                        const std::string &restrictions_file_name,
-                                       const std::string &name_file_name,
-                                       const std::string &turn_lane_file_name,
-                                       lua_State *segment_state)
-{
-    try
-    {
-        std::ofstream file_out_stream;
-        file_out_stream.open(output_file_name.c_str(), std::ios::binary);
-        const util::FingerPrint fingerprint = util::FingerPrint::GetValid();
-        file_out_stream.write((char *)&fingerprint, sizeof(util::FingerPrint));
-
-        PrepareNodes();
-        WriteNodes(file_out_stream);
-        PrepareEdges(segment_state);
-        WriteEdges(file_out_stream);
-
-        PrepareRestrictions();
-        WriteRestrictions(restrictions_file_name);
-
-        WriteCharData(name_file_name);
-        WriteTurnLaneMasks(turn_lane_file_name, turn_lane_offsets, turn_lane_masks);
-    }
-    catch (const std::exception &e)
-    {
-        std::cerr << "Caught Execption:" << e.what() << std::endl;
-    }
-}
-
-void ExtractionContainers::WriteTurnLaneMasks(
-    const std::string &file_name,
-    const stxxl::vector<std::uint32_t> &offsets,
-    const stxxl::vector<guidance::TurnLaneType::Mask> &masks) const
+                                       const std::string &name_file_name)
 {
-    util::SimpleLogger().Write() << "Writing turn lane masks...";
-    TIMER_START(turn_lane_timer);
-
-    std::ofstream ofs(file_name, std::ios::binary);
-
-    if (!util::serializeVector(ofs, offsets))
-    {
-        util::SimpleLogger().Write(logWARNING) << "Error while writing.";
-        return;
-    }
+    std::ofstream file_out_stream;
+    file_out_stream.open(output_file_name.c_str(), std::ios::binary);
+    const util::FingerPrint fingerprint = util::FingerPrint::GetValid();
+    file_out_stream.write((char *)&fingerprint, sizeof(util::FingerPrint));
 
-    if (!util::serializeVector(ofs, masks))
-    {
-        util::SimpleLogger().Write(logWARNING) << "Error while writing.";
-        return;
-    }
+    PrepareNodes();
+    WriteNodes(file_out_stream);
+    PrepareEdges(scripting_environment);
+    WriteEdges(file_out_stream);
 
-    TIMER_STOP(turn_lane_timer);
-    util::SimpleLogger().Write() << "done (" << TIMER_SEC(turn_lane_timer) << ")";
+    PrepareRestrictions();
+    WriteRestrictions(restrictions_file_name);
+    WriteCharData(name_file_name);
 }
 
 void ExtractionContainers::WriteCharData(const std::string &file_name)
@@ -202,7 +158,8 @@ void ExtractionContainers::WriteCharData(const std::string &file_name)
     // transforms in-place name offsets to name lengths
     BOOST_ASSERT(!name_offsets.empty());
     for (auto curr = name_offsets.begin(), next = name_offsets.begin() + 1;
-         next != name_offsets.end(); ++curr, ++next)
+         next != name_offsets.end();
+         ++curr, ++next)
     {
         *curr = *next - *curr;
     }
@@ -304,7 +261,7 @@ void ExtractionContainers::PrepareNodes()
     std::cout << "ok, after " << TIMER_SEC(id_map) << "s" << std::endl;
 }
 
-void ExtractionContainers::PrepareEdges(lua_State *segment_state)
+void ExtractionContainers::PrepareEdges(ScriptingEnvironment &scripting_environment)
 {
     // Sort edges by start.
     std::cout << "[extractor] Sorting edges by start    ... " << std::flush;
@@ -326,8 +283,8 @@ void ExtractionContainers::PrepareEdges(lua_State *segment_state)
     {
         if (edge_iterator->result.osm_source_id < node_iterator->node_id)
         {
-            util::SimpleLogger().Write(LogLevel::logWARNING) << "Found invalid node reference "
-                                                             << edge_iterator->result.source;
+            util::SimpleLogger().Write(LogLevel::logDEBUG) << "Found invalid node reference "
+                                                           << edge_iterator->result.source;
             edge_iterator->result.source = SPECIAL_NODEID;
             ++edge_iterator;
             continue;
@@ -362,8 +319,8 @@ void ExtractionContainers::PrepareEdges(lua_State *segment_state)
     // Remove all remaining edges. They are invalid because there are no corresponding nodes for
     // them. This happens when using osmosis with bbox or polygon to extract smaller areas.
     auto markSourcesInvalid = [](InternalExtractorEdge &edge) {
-        util::SimpleLogger().Write(LogLevel::logWARNING) << "Found invalid node reference "
-                                                         << edge.result.source;
+        util::SimpleLogger().Write(LogLevel::logDEBUG) << "Found invalid node reference "
+                                                       << edge.result.source;
         edge.result.source = SPECIAL_NODEID;
         edge.result.osm_source_id = SPECIAL_OSM_NODEID;
     };
@@ -386,8 +343,6 @@ void ExtractionContainers::PrepareEdges(lua_State *segment_state)
     const auto all_edges_list_end_ = all_edges_list.end();
     const auto all_nodes_list_end_ = all_nodes_list.end();
 
-    const auto has_segment_function = util::luaFunctionExists(segment_state, "segment_function");
-
     while (edge_iterator != all_edges_list_end_ && node_iterator != all_nodes_list_end_)
     {
         // skip all invalid edges
@@ -399,7 +354,7 @@ void ExtractionContainers::PrepareEdges(lua_State *segment_state)
 
         if (edge_iterator->result.osm_target_id < node_iterator->node_id)
         {
-            util::SimpleLogger().Write(LogLevel::logWARNING)
+            util::SimpleLogger().Write(LogLevel::logDEBUG)
                 << "Found invalid node reference "
                 << static_cast<uint64_t>(edge_iterator->result.osm_target_id);
             edge_iterator->result.target = SPECIAL_NODEID;
@@ -423,15 +378,8 @@ void ExtractionContainers::PrepareEdges(lua_State *segment_state)
             edge_iterator->source_coordinate,
             util::Coordinate(node_iterator->lon, node_iterator->lat));
 
-        if (has_segment_function)
-        {
-            luabind::call_function<void>(segment_state,
-                                         "segment_function",
-                                         boost::cref(edge_iterator->source_coordinate),
-                                         boost::cref(*node_iterator),
-                                         distance,
-                                         boost::ref(edge_iterator->weight_data));
-        }
+        scripting_environment.ProcessSegment(
+            edge_iterator->source_coordinate, *node_iterator, distance, edge_iterator->weight_data);
 
         const double weight = [distance](const InternalExtractorEdge::WeightData &data) {
             switch (data.type)
@@ -474,8 +422,8 @@ void ExtractionContainers::PrepareEdges(lua_State *segment_state)
     // Remove all remaining edges. They are invalid because there are no corresponding nodes for
     // them. This happens when using osmosis with bbox or polygon to extract smaller areas.
     auto markTargetsInvalid = [](InternalExtractorEdge &edge) {
-        util::SimpleLogger().Write(LogLevel::logWARNING) << "Found invalid node reference "
-                                                         << edge.result.target;
+        util::SimpleLogger().Write(LogLevel::logDEBUG) << "Found invalid node reference "
+                                                       << edge.result.target;
         edge.result.target = SPECIAL_NODEID;
     };
     std::for_each(edge_iterator, all_edges_list_end_, markTargetsInvalid);
@@ -734,7 +682,7 @@ void ExtractionContainers::PrepareRestrictions()
         if (way_start_and_end_iterator->way_id >
             OSMWayID{static_cast<std::uint32_t>(restrictions_iterator->restriction.from.way)})
         {
-            util::SimpleLogger().Write(LogLevel::logWARNING)
+            util::SimpleLogger().Write(LogLevel::logDEBUG)
                 << "Restriction references invalid way: "
                 << restrictions_iterator->restriction.from.way;
             restrictions_iterator->restriction.from.node = SPECIAL_NODEID;
@@ -742,8 +690,9 @@ void ExtractionContainers::PrepareRestrictions()
             continue;
         }
 
-        BOOST_ASSERT(way_start_and_end_iterator->way_id ==
-                     OSMWayID{static_cast<std::uint32_t>(restrictions_iterator->restriction.from.way)});
+        BOOST_ASSERT(
+            way_start_and_end_iterator->way_id ==
+            OSMWayID{static_cast<std::uint32_t>(restrictions_iterator->restriction.from.way)});
         // we do not remap the via id yet, since we will need it for the to node as well
         const OSMNodeID via_node_id = OSMNodeID{restrictions_iterator->restriction.via.node};
 
@@ -751,7 +700,7 @@ void ExtractionContainers::PrepareRestrictions()
         auto via_id_iter = external_to_internal_node_id_map.find(via_node_id);
         if (via_id_iter == external_to_internal_node_id_map.end())
         {
-            util::SimpleLogger().Write(LogLevel::logWARNING)
+            util::SimpleLogger().Write(LogLevel::logDEBUG)
                 << "Restriction references invalid node: "
                 << restrictions_iterator->restriction.via.node;
             restrictions_iterator->restriction.via.node = SPECIAL_NODEID;
@@ -766,7 +715,7 @@ void ExtractionContainers::PrepareRestrictions()
                 way_start_and_end_iterator->first_segment_target_id);
             if (id_iter == external_to_internal_node_id_map.end())
             {
-                util::SimpleLogger().Write(LogLevel::logWARNING)
+                util::SimpleLogger().Write(LogLevel::logDEBUG)
                     << "Way references invalid node: "
                     << way_start_and_end_iterator->first_segment_target_id;
                 restrictions_iterator->restriction.from.node = SPECIAL_NODEID;
@@ -783,7 +732,7 @@ void ExtractionContainers::PrepareRestrictions()
                 way_start_and_end_iterator->last_segment_source_id);
             if (id_iter == external_to_internal_node_id_map.end())
             {
-                util::SimpleLogger().Write(LogLevel::logWARNING)
+                util::SimpleLogger().Write(LogLevel::logDEBUG)
                     << "Way references invalid node: "
                     << way_start_and_end_iterator->last_segment_target_id;
                 restrictions_iterator->restriction.from.node = SPECIAL_NODEID;
@@ -840,8 +789,9 @@ void ExtractionContainers::PrepareRestrictions()
             ++restrictions_iterator;
             continue;
         }
-        BOOST_ASSERT(way_start_and_end_iterator->way_id ==
-                     OSMWayID{static_cast<std::uint32_t>(restrictions_iterator->restriction.to.way)});
+        BOOST_ASSERT(
+            way_start_and_end_iterator->way_id ==
+            OSMWayID{static_cast<std::uint32_t>(restrictions_iterator->restriction.to.way)});
         const OSMNodeID via_node_id = OSMNodeID{restrictions_iterator->restriction.via.node};
 
         // assign new via node id
@@ -855,7 +805,7 @@ void ExtractionContainers::PrepareRestrictions()
                 way_start_and_end_iterator->first_segment_target_id);
             if (to_id_iter == external_to_internal_node_id_map.end())
             {
-                util::SimpleLogger().Write(LogLevel::logWARNING)
+                util::SimpleLogger().Write(LogLevel::logDEBUG)
                     << "Way references invalid node: "
                     << way_start_and_end_iterator->first_segment_source_id;
                 restrictions_iterator->restriction.to.node = SPECIAL_NODEID;
@@ -871,7 +821,7 @@ void ExtractionContainers::PrepareRestrictions()
                 way_start_and_end_iterator->last_segment_source_id);
             if (to_id_iter == external_to_internal_node_id_map.end())
             {
-                util::SimpleLogger().Write(LogLevel::logWARNING)
+                util::SimpleLogger().Write(LogLevel::logDEBUG)
                     << "Way references invalid node: "
                     << way_start_and_end_iterator->last_segment_source_id;
                 restrictions_iterator->restriction.to.node = SPECIAL_NODEID;
diff --git a/src/extractor/extractor.cpp b/src/extractor/extractor.cpp
index 45ed06e..39af536 100644
--- a/src/extractor/extractor.cpp
+++ b/src/extractor/extractor.cpp
@@ -11,7 +11,6 @@
 #include "extractor/raster_source.hpp"
 #include "util/graph_loader.hpp"
 #include "util/io.hpp"
-#include "util/lua_util.hpp"
 #include "util/make_unique.hpp"
 #include "util/name_table.hpp"
 #include "util/range_table.hpp"
@@ -23,17 +22,18 @@
 #include "util/static_graph.hpp"
 #include "util/static_rtree.hpp"
 
+// Keep debug include to make sure the debug header is in sync with types.
+#include "util/debug.hpp"
+
 #include "extractor/tarjan_scc.hpp"
 
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
 #include <boost/optional/optional.hpp>
 
-#include <luabind/luabind.hpp>
-
 #include <osmium/io/any_input.hpp>
 
-#include <tbb/parallel_for.h>
+#include <tbb/concurrent_vector.h>
 #include <tbb/task_scheduler_init.h>
 
 #include <cstdlib>
@@ -45,7 +45,9 @@
 #include <chrono>
 #include <fstream>
 #include <iostream>
+#include <numeric> //partial_sum
 #include <thread>
+#include <tuple>
 #include <type_traits>
 #include <unordered_map>
 #include <vector>
@@ -55,6 +57,36 @@ namespace osrm
 namespace extractor
 {
 
+namespace
+{
+std::tuple<std::vector<std::uint32_t>, std::vector<guidance::TurnLaneType::Mask>>
+transformTurnLaneMapIntoArrays(const guidance::LaneDescriptionMap &turn_lane_map)
+{
+    // could use some additional capacity? To avoid a copy during processing, though small data so
+    // probably not that important.
+    //
+    // From the map, we construct an adjacency array that allows access from all IDs to the list of
+    // associated Turn Lane Masks.
+    //
+    // turn lane offsets points into the locations of the turn_lane_masks array. We use a standard
+    // adjacency array like structure to store the turn lane masks.
+    std::vector<std::uint32_t> turn_lane_offsets(turn_lane_map.size() + 2); // empty ID + sentinel
+    for (auto entry = turn_lane_map.begin(); entry != turn_lane_map.end(); ++entry)
+        turn_lane_offsets[entry->second + 1] = entry->first.size();
+
+    // inplace prefix sum
+    std::partial_sum(turn_lane_offsets.begin(), turn_lane_offsets.end(), turn_lane_offsets.begin());
+
+    // allocate the current masks
+    std::vector<guidance::TurnLaneType::Mask> turn_lane_masks(turn_lane_offsets.back());
+    for (auto entry = turn_lane_map.begin(); entry != turn_lane_map.end(); ++entry)
+        std::copy(entry->first.begin(),
+                  entry->first.end(),
+                  turn_lane_masks.begin() + turn_lane_offsets[entry->second]);
+    return std::make_tuple(std::move(turn_lane_offsets), std::move(turn_lane_masks));
+}
+} // namespace
+
 /**
  * TODO: Refactor this function into smaller functions for better readability.
  *
@@ -74,12 +106,8 @@ namespace extractor
  * graph
  *
  */
-int Extractor::run()
+int Extractor::run(ScriptingEnvironment &scripting_environment)
 {
-    // setup scripting environment
-    ScriptingEnvironment scripting_environment(config.profile_path.string().c_str());
-
-    try
     {
         util::LogPolicy::GetInstance().Unmute();
         TIMER_START(extracting);
@@ -90,7 +118,10 @@ int Extractor::run()
         tbb::task_scheduler_init init(number_of_threads);
 
         util::SimpleLogger().Write() << "Input file: " << config.input_path.filename().string();
-        util::SimpleLogger().Write() << "Profile: " << config.profile_path.filename().string();
+        if (!config.profile_path.empty())
+        {
+            util::SimpleLogger().Write() << "Profile: " << config.profile_path.filename().string();
+        }
         util::SimpleLogger().Write() << "Threads: " << number_of_threads;
 
         ExtractionContainers extraction_containers;
@@ -100,21 +131,15 @@ int Extractor::run()
         osmium::io::Reader reader(input_file);
         const osmium::io::Header header = reader.header();
 
-        std::atomic<unsigned> number_of_nodes{0};
-        std::atomic<unsigned> number_of_ways{0};
-        std::atomic<unsigned> number_of_relations{0};
-        std::atomic<unsigned> number_of_others{0};
+        unsigned number_of_nodes = 0;
+        unsigned number_of_ways = 0;
+        unsigned number_of_relations = 0;
 
         util::SimpleLogger().Write() << "Parsing in progress..";
         TIMER_START(parsing);
 
-        auto &main_context = scripting_environment.GetContex();
-
         // setup raster sources
-        if (util::luaFunctionExists(main_context.state, "source_function"))
-        {
-            luabind::call_function<void>(main_context.state, "source_function");
-        }
+        scripting_environment.SetupSources();
 
         std::string generator = header.get("generator");
         if (generator.empty())
@@ -140,7 +165,7 @@ int Extractor::run()
         tbb::concurrent_vector<boost::optional<InputRestrictionContainer>> resulting_restrictions;
 
         // setup restriction parser
-        const RestrictionParser restriction_parser(main_context.state, main_context.properties);
+        const RestrictionParser restriction_parser(scripting_environment);
 
         while (const osmium::memory::Buffer buffer = reader.read())
         {
@@ -156,52 +181,13 @@ int Extractor::run()
             resulting_ways.clear();
             resulting_restrictions.clear();
 
-            // parse OSM entities in parallel, store in resulting vectors
-            tbb::parallel_for(
-                tbb::blocked_range<std::size_t>(0, osm_elements.size()),
-                [&](const tbb::blocked_range<std::size_t> &range) {
-                    ExtractionNode result_node;
-                    ExtractionWay result_way;
-                    auto &local_context = scripting_environment.GetContex();
-
-                    for (auto x = range.begin(), end = range.end(); x != end; ++x)
-                    {
-                        const auto entity = osm_elements[x];
-
-                        switch (entity->type())
-                        {
-                        case osmium::item_type::node:
-                            result_node.clear();
-                            ++number_of_nodes;
-                            luabind::call_function<void>(
-                                local_context.state,
-                                "node_function",
-                                boost::cref(static_cast<const osmium::Node &>(*entity)),
-                                boost::ref(result_node));
-                            resulting_nodes.push_back(std::make_pair(x, std::move(result_node)));
-                            break;
-                        case osmium::item_type::way:
-                            result_way.clear();
-                            ++number_of_ways;
-                            luabind::call_function<void>(
-                                local_context.state,
-                                "way_function",
-                                boost::cref(static_cast<const osmium::Way &>(*entity)),
-                                boost::ref(result_way));
-                            resulting_ways.push_back(std::make_pair(x, std::move(result_way)));
-                            break;
-                        case osmium::item_type::relation:
-                            ++number_of_relations;
-                            resulting_restrictions.push_back(restriction_parser.TryParse(
-                                static_cast<const osmium::Relation &>(*entity)));
-                            break;
-                        default:
-                            ++number_of_others;
-                            break;
-                        }
-                    }
-                });
+            scripting_environment.ProcessElements(osm_elements,
+                                                  restriction_parser,
+                                                  resulting_nodes,
+                                                  resulting_ways,
+                                                  resulting_restrictions);
 
+            number_of_nodes += resulting_nodes.size();
             // put parsed objects thru extractor callbacks
             for (const auto &result : resulting_nodes)
             {
@@ -209,11 +195,13 @@ int Extractor::run()
                     static_cast<const osmium::Node &>(*(osm_elements[result.first])),
                     result.second);
             }
+            number_of_ways += resulting_ways.size();
             for (const auto &result : resulting_ways)
             {
                 extractor_callbacks->ProcessWay(
                     static_cast<const osmium::Way &>(*(osm_elements[result.first])), result.second);
             }
+            number_of_relations += resulting_restrictions.size();
             for (const auto &result : resulting_restrictions)
             {
                 extractor_callbacks->ProcessRestriction(result);
@@ -223,10 +211,12 @@ int Extractor::run()
         util::SimpleLogger().Write() << "Parsing finished after " << TIMER_SEC(parsing)
                                      << " seconds";
 
-        util::SimpleLogger().Write() << "Raw input contains " << number_of_nodes.load()
-                                     << " nodes, " << number_of_ways.load() << " ways, and "
-                                     << number_of_relations.load() << " relations, and "
-                                     << number_of_others.load() << " unknown entities";
+        util::SimpleLogger().Write() << "Raw input contains " << number_of_nodes << " nodes, "
+                                     << number_of_ways << " ways, and " << number_of_relations
+                                     << " relations";
+
+        // take control over the turn lane map
+        turn_lane_map = extractor_callbacks->moveOutLaneDescriptionMap();
 
         extractor_callbacks.reset();
 
@@ -236,34 +226,24 @@ int Extractor::run()
             return 1;
         }
 
-        extraction_containers.PrepareData(config.output_file_name,
+        extraction_containers.PrepareData(scripting_environment,
+                                          config.output_file_name,
                                           config.restriction_file_name,
-                                          config.names_file_name,
-                                          config.turn_lane_descriptions_file_name,
-                                          main_context.state);
+                                          config.names_file_name);
 
-        WriteProfileProperties(config.profile_properties_output_path, main_context.properties);
+        WriteProfileProperties(config.profile_properties_output_path,
+                               scripting_environment.GetProfileProperties());
 
         TIMER_STOP(extracting);
         util::SimpleLogger().Write() << "extraction finished after " << TIMER_SEC(extracting)
                                      << "s";
     }
-    // we do this for scoping
-    // TODO move to own functions
-    catch (const std::exception &e)
-    {
-        util::SimpleLogger().Write(logWARNING) << e.what();
-        return 1;
-    }
-    try
+
     {
         // Transform the node-based graph that OSM is based on into an edge-based graph
         // that is better for routing.  Every edge becomes a node, and every valid
         // movement (e.g. turn from A->B, and B->A) becomes an edge
         //
-
-        auto &main_context = scripting_environment.GetContex();
-
         util::SimpleLogger().Write() << "Generating edge-expanded graph representation";
 
         TIMER_START(expansion);
@@ -273,8 +253,7 @@ int Extractor::run()
         std::vector<bool> node_is_startpoint;
         std::vector<EdgeWeight> edge_based_node_weights;
         std::vector<QueryNode> internal_to_external_node_map;
-        auto graph_size = BuildEdgeExpandedGraph(main_context.state,
-                                                 main_context.properties,
+        auto graph_size = BuildEdgeExpandedGraph(scripting_environment,
                                                  internal_to_external_node_map,
                                                  edge_based_node_list,
                                                  node_is_startpoint,
@@ -294,18 +273,18 @@ int Extractor::run()
         util::SimpleLogger().Write() << "Done writing. (" << TIMER_SEC(timer_write_node_weights)
                                      << ")";
 
-        util::SimpleLogger().Write() << "building r-tree ...";
-        TIMER_START(rtree);
-
+        util::SimpleLogger().Write() << "Computing strictly connected components ...";
         FindComponents(max_edge_id, edge_based_edge_list, edge_based_node_list);
 
+        util::SimpleLogger().Write() << "Building r-tree ...";
+        TIMER_START(rtree);
         BuildRTree(std::move(edge_based_node_list),
                    std::move(node_is_startpoint),
                    internal_to_external_node_map);
 
         TIMER_STOP(rtree);
 
-        util::SimpleLogger().Write() << "writing node map ...";
+        util::SimpleLogger().Write() << "Writing node map ...";
         WriteNodeMapping(internal_to_external_node_map);
 
         WriteEdgeBasedGraph(config.edge_graph_output_path, max_edge_id, edge_based_edge_list);
@@ -316,11 +295,6 @@ int Extractor::run()
         util::SimpleLogger().Write() << "To prepare the data for routing, run: "
                                      << "./osrm-contract " << config.output_file_name << std::endl;
     }
-    catch (const std::exception &e)
-    {
-        util::SimpleLogger().Write(logWARNING) << e.what();
-        return 1;
-    }
 
     return 0;
 }
@@ -477,8 +451,7 @@ Extractor::LoadNodeBasedGraph(std::unordered_set<NodeID> &barrier_nodes,
  \brief Building an edge-expanded graph from node-based input and turn restrictions
 */
 std::pair<std::size_t, EdgeID>
-Extractor::BuildEdgeExpandedGraph(lua_State *lua_state,
-                                  const ProfileProperties &profile_properties,
+Extractor::BuildEdgeExpandedGraph(ScriptingEnvironment &scripting_environment,
                                   std::vector<QueryNode> &internal_to_external_node_map,
                                   std::vector<EdgeBasedNode> &node_based_edge_list,
                                   std::vector<bool> &node_is_startpoint,
@@ -505,13 +478,11 @@ Extractor::BuildEdgeExpandedGraph(lua_State *lua_state,
 
     util::NameTable name_table(config.names_file_name);
 
+    // could use some additional capacity? To avoid a copy during processing, though small data so
+    // probably not that important.
     std::vector<std::uint32_t> turn_lane_offsets;
     std::vector<guidance::TurnLaneType::Mask> turn_lane_masks;
-    if (!util::deserializeAdjacencyArray(
-            config.turn_lane_descriptions_file_name, turn_lane_offsets, turn_lane_masks))
-    {
-        util::SimpleLogger().Write(logWARNING) << "Reading Turn Lane Masks failed.";
-    }
+    std::tie(turn_lane_offsets, turn_lane_masks) = transformTurnLaneMapIntoArrays(turn_lane_map);
 
     EdgeBasedGraphFactory edge_based_graph_factory(
         node_based_graph,
@@ -520,18 +491,21 @@ Extractor::BuildEdgeExpandedGraph(lua_State *lua_state,
         traffic_lights,
         std::const_pointer_cast<RestrictionMap const>(restriction_map),
         internal_to_external_node_map,
-        profile_properties,
+        scripting_environment.GetProfileProperties(),
         name_table,
         turn_lane_offsets,
-        turn_lane_masks);
+        turn_lane_masks,
+        turn_lane_map);
 
-    edge_based_graph_factory.Run(config.edge_output_path,
+    edge_based_graph_factory.Run(scripting_environment,
+                                 config.edge_output_path,
                                  config.turn_lane_data_file_name,
-                                 lua_state,
                                  config.edge_segment_lookup_path,
                                  config.edge_penalty_path,
                                  config.generate_edge_lookup);
 
+    WriteTurnLaneData(config.turn_lane_descriptions_file_name);
+
     edge_based_graph_factory.GetEdgeBasedEdges(edge_based_edge_list);
     edge_based_graph_factory.GetEdgeBasedNodes(node_based_edge_list);
     edge_based_graph_factory.GetStartPointMarkers(node_is_startpoint);
@@ -694,5 +668,36 @@ void Extractor::WriteIntersectionClassificationData(
                                  << entry_classes.size() << " entry classes and " << total_bearings
                                  << " bearing values." << std::endl;
 }
+
+void Extractor::WriteTurnLaneData(const std::string &turn_lane_file) const
+{
+    // Write the turn lane data to file
+    std::vector<std::uint32_t> turn_lane_offsets;
+    std::vector<guidance::TurnLaneType::Mask> turn_lane_masks;
+    std::tie(turn_lane_offsets, turn_lane_masks) = transformTurnLaneMapIntoArrays(turn_lane_map);
+
+    util::SimpleLogger().Write() << "Writing turn lane masks...";
+    TIMER_START(turn_lane_timer);
+
+    std::ofstream ofs(turn_lane_file, std::ios::binary);
+    if (!ofs)
+        throw osrm::util::exception("Failed to open " + turn_lane_file + " for writing.");
+
+    if (!util::serializeVector(ofs, turn_lane_offsets))
+    {
+        util::SimpleLogger().Write(logWARNING) << "Error while writing.";
+        return;
+    }
+
+    if (!util::serializeVector(ofs, turn_lane_masks))
+    {
+        util::SimpleLogger().Write(logWARNING) << "Error while writing.";
+        return;
+    }
+
+    TIMER_STOP(turn_lane_timer);
+    util::SimpleLogger().Write() << "done (" << TIMER_SEC(turn_lane_timer) << ")";
 }
-}
+
+} // namespace extractor
+} // namespace osrm
diff --git a/src/extractor/extractor_callbacks.cpp b/src/extractor/extractor_callbacks.cpp
index becb166..850d58d 100644
--- a/src/extractor/extractor_callbacks.cpp
+++ b/src/extractor/extractor_callbacks.cpp
@@ -1,8 +1,9 @@
+#include "extractor/extractor_callbacks.hpp"
 #include "extractor/external_memory_node.hpp"
 #include "extractor/extraction_containers.hpp"
 #include "extractor/extraction_node.hpp"
 #include "extractor/extraction_way.hpp"
-#include "extractor/extractor_callbacks.hpp"
+#include "extractor/guidance/road_classification.hpp"
 #include "extractor/restriction.hpp"
 
 #include "util/for_each_pair.hpp"
@@ -33,8 +34,8 @@ namespace TurnLaneType = guidance::TurnLaneType;
 ExtractorCallbacks::ExtractorCallbacks(ExtractionContainers &extraction_containers)
     : external_memory(extraction_containers)
 {
-    // we reserved 0, 1, 2 for the empty case
-    string_map[MapKey("", "")] = 0;
+    // we reserved 0, 1, 2, 3 for the empty case
+    string_map[MapKey("", "", "", "")] = 0;
     lane_description_map[TurnLaneDescription()] = 0;
 }
 
@@ -140,14 +141,8 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
     }
 
     // FIXME this need to be moved into the profiles
-    const char *data = input_way.get_value_by_key("highway");
-    guidance::RoadClassificationData road_classification;
-    if (data)
-    {
-        road_classification.road_class = guidance::functionalRoadClassFromTag(data);
-    }
-
-    const auto laneStringToDescription = [](std::string lane_string) -> TurnLaneDescription {
+    const guidance::RoadClassification road_classification = parsed_way.road_classification;
+    const auto laneStringToDescription = [](const std::string &lane_string) -> TurnLaneDescription {
         if (lane_string.empty())
             return {};
 
@@ -170,6 +165,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
                                                                 "reverse",
                                                                 "merge_to_left",
                                                                 "merge_to_right"};
+
         const constexpr TurnLaneType::Mask masks_by_osm_string[num_osm_tags + 1] = {
             TurnLaneType::none,
             TurnLaneType::straight,
@@ -218,7 +214,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
 
     // convert the lane description into an ID and, if necessary, remembr the description in the
     // description_map
-    const auto requestId = [&](std::string lane_string) {
+    const auto requestId = [&](const std::string &lane_string) {
         if (lane_string.empty())
             return INVALID_LANE_DESCRIPTIONID;
         TurnLaneDescription lane_description = laneStringToDescription(std::move(lane_string));
@@ -229,16 +225,6 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
             const LaneDescriptionID new_id =
                 boost::numeric_cast<LaneDescriptionID>(lane_description_map.size());
             lane_description_map[lane_description] = new_id;
-
-            // since we are getting a new ID, we can augment the current offsets
-
-            // and store the turn lane masks, sadly stxxl does not support insert
-            for (const auto mask : lane_description)
-                external_memory.turn_lane_masks.push_back(mask);
-
-            external_memory.turn_lane_offsets.push_back(external_memory.turn_lane_offsets.back() +
-                                                        lane_description.size());
-
             return new_id;
         }
         else
@@ -247,16 +233,16 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
         }
     };
 
-    // Deduplicates street names and street destination names based on the street_map map.
-    // In case we do not already store the name, inserts (name, id) tuple and return id.
+    // Deduplicates street names, refs, destinations, pronunciation based on the string_map.
+    // In case we do not already store the key, inserts (key, id) tuple and return id.
     // Otherwise fetches the id based on the name and returns it without insertion.
     const auto turn_lane_id_forward = requestId(parsed_way.turn_lanes_forward);
     const auto turn_lane_id_backward = requestId(parsed_way.turn_lanes_backward);
 
     const constexpr auto MAX_STRING_LENGTH = 255u;
-    // Get the unique identifier for the street name
-    // Get the unique identifier for the street name and destination
-    const auto name_iterator = string_map.find(MapKey(parsed_way.name, parsed_way.destinations));
+    // Get the unique identifier for the street name, destination, and ref
+    const auto name_iterator = string_map.find(
+        MapKey(parsed_way.name, parsed_way.destinations, parsed_way.ref, parsed_way.pronunciation));
     unsigned name_id = EMPTY_NAMEID;
     if (string_map.end() == name_iterator)
     {
@@ -265,12 +251,14 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
             std::min<unsigned>(MAX_STRING_LENGTH, parsed_way.destinations.size());
         const auto pronunciation_length =
             std::min<unsigned>(MAX_STRING_LENGTH, parsed_way.pronunciation.size());
+        const auto ref_length = std::min<unsigned>(MAX_STRING_LENGTH, parsed_way.ref.size());
 
         // name_offsets already has an offset of a new name, take the offset index as the name id
         name_id = external_memory.name_offsets.size() - 1;
 
         external_memory.name_char_data.reserve(external_memory.name_char_data.size() + name_length +
-                                               destinations_length + pronunciation_length);
+                                               destinations_length + pronunciation_length +
+                                               ref_length);
 
         std::copy(parsed_way.name.c_str(),
                   parsed_way.name.c_str() + name_length,
@@ -287,7 +275,13 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
                   std::back_inserter(external_memory.name_char_data));
         external_memory.name_offsets.push_back(external_memory.name_char_data.size());
 
-        auto k = MapKey{parsed_way.name, parsed_way.destinations};
+        std::copy(parsed_way.ref.c_str(),
+                  parsed_way.ref.c_str() + ref_length,
+                  std::back_inserter(external_memory.name_char_data));
+        external_memory.name_offsets.push_back(external_memory.name_char_data.size());
+
+        auto k = MapKey{
+            parsed_way.name, parsed_way.destinations, parsed_way.ref, parsed_way.pronunciation};
         auto v = MapVal{name_id};
         string_map.emplace(std::move(k), std::move(v));
     }
@@ -405,5 +399,10 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
              OSMNodeID{static_cast<std::uint64_t>(input_way.nodes()[0].ref())}});
     }
 }
+
+guidance::LaneDescriptionMap &&ExtractorCallbacks::moveOutLaneDescriptionMap()
+{
+    return std::move(lane_description_map);
 }
-}
+} // namespace extractor
+} // namespace osrm
diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp
index 18af7fd..1e166f2 100644
--- a/src/extractor/graph_compressor.cpp
+++ b/src/extractor/graph_compressor.cpp
@@ -59,7 +59,6 @@ void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
         //    forward_e1
         //
         // If the edges are compatible.
-
         const bool reverse_edge_order = graph.GetEdgeData(graph.BeginEdges(node_v)).reversed;
         const EdgeID forward_e2 = graph.BeginEdges(node_v) + reverse_edge_order;
         BOOST_ASSERT(SPECIAL_EDGEID != forward_e2);
@@ -100,8 +99,8 @@ void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
             continue;
         }
 
-        if (fwd_edge_data1.IsCompatibleTo(fwd_edge_data2) &&
-            rev_edge_data1.IsCompatibleTo(rev_edge_data2))
+        if (fwd_edge_data1.CanCombineWith(fwd_edge_data2) &&
+            rev_edge_data1.CanCombineWith(rev_edge_data2))
         {
             BOOST_ASSERT(graph.GetEdgeData(forward_e1).name_id ==
                          graph.GetEdgeData(reverse_e1).name_id);
@@ -109,7 +108,7 @@ void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
                          graph.GetEdgeData(reverse_e2).name_id);
 
             // Do not compress edge if it crosses a traffic signal.
-            // This can't be done in IsCompatibleTo, becase we only store the
+            // This can't be done in CanCombineWith, becase we only store the
             // traffic signals in the `traffic_lights` list, which EdgeData
             // doesn't have access to.
             const bool has_node_penalty = traffic_lights.find(node_v) != traffic_lights.end();
@@ -160,7 +159,8 @@ void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
              * turn-lanes. Without this,we would have to treat any turn-lane beginning/ending just
              * like a barrier.
              */
-            const auto selectLaneID = [](const LaneDescriptionID front, const LaneDescriptionID back) {
+            const auto selectLaneID = [](const LaneDescriptionID front,
+                                         const LaneDescriptionID back) {
                 // A lane has tags: u - (front) - v - (back) - w
                 // During contraction, we keep only one of the tags. Usually the one closer to the
                 // intersection is preferred. If its empty, however, we keep the non-empty one
@@ -168,10 +168,12 @@ void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
                     return front;
                 return back;
             };
-            graph.GetEdgeData(forward_e1).lane_description_id = selectLaneID(
-                graph.GetEdgeData(forward_e1).lane_description_id, fwd_edge_data2.lane_description_id);
-            graph.GetEdgeData(reverse_e1).lane_description_id = selectLaneID(
-                graph.GetEdgeData(reverse_e1).lane_description_id, rev_edge_data2.lane_description_id);
+            graph.GetEdgeData(forward_e1).lane_description_id =
+                selectLaneID(graph.GetEdgeData(forward_e1).lane_description_id,
+                             fwd_edge_data2.lane_description_id);
+            graph.GetEdgeData(reverse_e1).lane_description_id =
+                selectLaneID(graph.GetEdgeData(reverse_e1).lane_description_id,
+                             rev_edge_data2.lane_description_id);
 
             // remove e2's (if bidir, otherwise only one)
             graph.DeleteEdge(node_v, forward_e2);
diff --git a/src/extractor/guidance/classification_data.cpp b/src/extractor/guidance/classification_data.cpp
deleted file mode 100644
index 78a8684..0000000
--- a/src/extractor/guidance/classification_data.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "extractor/guidance/classification_data.hpp"
-#include "util/simple_logger.hpp"
-
-#include <unordered_map>
-
-namespace osrm
-{
-namespace extractor
-{
-namespace guidance
-{
-
-FunctionalRoadClass functionalRoadClassFromTag(std::string const &value)
-{
-    // FIXME at some point this should be part of the profiles
-    const static auto class_hash = [] {
-        std::unordered_map<std::string, FunctionalRoadClass> hash;
-        hash["motorway"] = FunctionalRoadClass::MOTORWAY;
-        hash["motorway_link"] = FunctionalRoadClass::MOTORWAY_LINK;
-        hash["trunk"] = FunctionalRoadClass::TRUNK;
-        hash["trunk_link"] = FunctionalRoadClass::TRUNK_LINK;
-        hash["primary"] = FunctionalRoadClass::PRIMARY;
-        hash["primary_link"] = FunctionalRoadClass::PRIMARY_LINK;
-        hash["secondary"] = FunctionalRoadClass::SECONDARY;
-        hash["secondary_link"] = FunctionalRoadClass::SECONDARY_LINK;
-        hash["tertiary"] = FunctionalRoadClass::TERTIARY;
-        hash["tertiary_link"] = FunctionalRoadClass::TERTIARY_LINK;
-        hash["unclassified"] = FunctionalRoadClass::UNCLASSIFIED;
-        hash["residential"] = FunctionalRoadClass::RESIDENTIAL;
-        hash["service"] = FunctionalRoadClass::SERVICE;
-        hash["living_street"] = FunctionalRoadClass::LIVING_STREET;
-        hash["track"] = FunctionalRoadClass::LOW_PRIORITY_ROAD;
-        hash["road"] = FunctionalRoadClass::LOW_PRIORITY_ROAD;
-        hash["path"] = FunctionalRoadClass::LOW_PRIORITY_ROAD;
-        hash["driveway"] = FunctionalRoadClass::LOW_PRIORITY_ROAD;
-        return hash;
-    }();
-
-    if (class_hash.find(value) != class_hash.end())
-    {
-        return class_hash.find(value)->second;
-    }
-    else
-    {
-        // TODO activate again, when road classes are moved to the profile
-        // util::SimpleLogger().Write(logDEBUG) << "Unknown road class encountered: " << value;
-        return FunctionalRoadClass::UNKNOWN;
-    }
-}
-
-} // ns guidance
-} // ns extractor
-} // ns osrm
diff --git a/src/extractor/guidance/intersection_generator.cpp b/src/extractor/guidance/intersection_generator.cpp
index a9355ce..81a06f8 100644
--- a/src/extractor/guidance/intersection_generator.cpp
+++ b/src/extractor/guidance/intersection_generator.cpp
@@ -1,10 +1,12 @@
-#include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/guidance/constants.hpp"
+#include "extractor/guidance/intersection_generator.hpp"
 #include "extractor/guidance/toolkit.hpp"
 
 #include <algorithm>
+#include <iomanip>
 #include <iterator>
 #include <limits>
+#include <unordered_set>
 #include <utility>
 
 #include <boost/range/algorithm/count_if.hpp>
@@ -30,7 +32,10 @@ IntersectionGenerator::IntersectionGenerator(
 
 Intersection IntersectionGenerator::operator()(const NodeID from_node, const EdgeID via_eid) const
 {
-    return getConnectedRoads(from_node, via_eid);
+    auto intersection = GetConnectedRoads(from_node, via_eid);
+    const auto node_at_intersection = node_based_graph.GetTarget(via_eid);
+    return AdjustForJoiningRoads(
+        node_at_intersection, MergeSegregatedRoads(node_at_intersection, std::move(intersection)));
 }
 
 //                                               a
@@ -46,13 +51,26 @@ Intersection IntersectionGenerator::operator()(const NodeID from_node, const Edg
 // That means we not only get (from_node, turn_node, c) in the above example
 // but also (from_node, turn_node, a), (from_node, turn_node, b). These turns are
 // marked as invalid and only needed for intersection classification.
-Intersection IntersectionGenerator::getConnectedRoads(const NodeID from_node,
+Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node,
                                                       const EdgeID via_eid) const
 {
     Intersection intersection;
     const NodeID turn_node = node_based_graph.GetTarget(via_eid);
-    const NodeID only_restriction_to_node =
-        restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node);
+    const NodeID only_restriction_to_node = [&]() {
+        // If only restrictions refer to invalid ways somewhere far away, we rather ignore the
+        // restriction than to not route over the intersection at all.
+        const auto only_restriction_to_node =
+            restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node);
+        if (only_restriction_to_node != SPECIAL_NODEID)
+        {
+            // check if we can find an edge in the edge-rage
+            for (const auto onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node))
+                if (only_restriction_to_node == node_based_graph.GetTarget(onto_edge))
+                    return only_restriction_to_node;
+        }
+        // Ignore broken only restrictions.
+        return SPECIAL_NODEID;
+    }();
     const bool is_barrier_node = barrier_nodes.find(turn_node) != barrier_nodes.end();
 
     bool has_uturn_edge = false;
@@ -147,9 +165,9 @@ Intersection IntersectionGenerator::getConnectedRoads(const NodeID from_node,
         // after intersections sorting by angles, find the u-turn with (from_node == to_node)
         // that was inserted together with setting uturn_could_be_valid flag
         std::size_t self_u_turn = 0;
-        while (self_u_turn < intersection.size()
-               && intersection[self_u_turn].turn.angle < std::numeric_limits<double>::epsilon()
-               && from_node != node_based_graph.GetTarget(intersection[self_u_turn].turn.eid))
+        while (self_u_turn < intersection.size() &&
+               intersection[self_u_turn].turn.angle < std::numeric_limits<double>::epsilon() &&
+               from_node != node_based_graph.GetTarget(intersection[self_u_turn].turn.eid))
         {
             ++self_u_turn;
         }
@@ -158,7 +176,143 @@ Intersection IntersectionGenerator::getConnectedRoads(const NodeID from_node,
         intersection[self_u_turn].entry_allowed = true;
     }
 
-    return mergeSegregatedRoads(std::move(intersection));
+    return intersection;
+}
+
+// Checks for mergability of two ways that represent the same intersection. For further information
+// see interface documentation in header.
+bool IntersectionGenerator::CanMerge(const NodeID node_at_intersection,
+                                     const Intersection &intersection,
+                                     std::size_t first_index,
+                                     std::size_t second_index) const
+{
+    const auto &first_data = node_based_graph.GetEdgeData(intersection[first_index].turn.eid);
+    const auto &second_data = node_based_graph.GetEdgeData(intersection[second_index].turn.eid);
+
+    // only merge named ids
+    if (first_data.name_id == EMPTY_NAMEID)
+        return false;
+
+    // need to be same name
+    if (first_data.name_id != second_data.name_id)
+        return false;
+
+    // compatibility is required
+    if (first_data.travel_mode != second_data.travel_mode)
+        return false;
+    if (first_data.road_classification != second_data.road_classification)
+        return false;
+
+    // may not be on a roundabout
+    if (first_data.roundabout || second_data.roundabout)
+        return false;
+
+    // exactly one of them has to be reversed
+    if (first_data.reversed == second_data.reversed)
+        return false;
+
+    // one of them needs to be invalid
+    if (intersection[first_index].entry_allowed && intersection[second_index].entry_allowed)
+        return false;
+
+    // mergeable if the angle is not too big
+    const auto angle_between = angularDeviation(intersection[first_index].turn.angle,
+                                                intersection[second_index].turn.angle);
+
+    const auto coordinate_at_in_edge =
+        getRepresentativeCoordinate(node_at_intersection,
+                                    node_based_graph.GetTarget(intersection[0].turn.eid),
+                                    intersection[0].turn.eid,
+                                    false,
+                                    compressed_edge_container,
+                                    node_info_list);
+    const auto coordinate_at_intersection = node_info_list[node_at_intersection];
+
+    const auto isValidYArm = [this,
+                              intersection,
+                              coordinate_at_in_edge,
+                              coordinate_at_intersection,
+                              node_at_intersection](const std::size_t index,
+                                                    const std::size_t other_index) {
+        const auto GetActualTarget = [&](const std::size_t index) {
+            EdgeID last_in_edge_id;
+            GetActualNextIntersection(
+                node_at_intersection, intersection[index].turn.eid, nullptr, &last_in_edge_id);
+            return node_based_graph.GetTarget(last_in_edge_id);
+        };
+
+        const auto target_id = GetActualTarget(index);
+        const auto other_target_id = GetActualTarget(other_index);
+        if (target_id == node_at_intersection || other_target_id == node_at_intersection)
+            return false;
+
+        const auto coordinate_at_target = node_info_list[target_id];
+        const auto coordinate_at_other_target = node_info_list[other_target_id];
+
+        const auto turn_angle = util::coordinate_calculation::computeAngle(
+            coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_target);
+        const auto other_turn_angle = util::coordinate_calculation::computeAngle(
+            coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_other_target);
+        const double distance_to_target = util::coordinate_calculation::haversineDistance(
+            coordinate_at_intersection, coordinate_at_target);
+
+        const constexpr double MAX_COLLAPSE_DISTANCE = 30;
+        if (distance_to_target < MAX_COLLAPSE_DISTANCE)
+            return false;
+
+        const bool becomes_narrower =
+            angularDeviation(turn_angle, other_turn_angle) < NARROW_TURN_ANGLE &&
+            angularDeviation(turn_angle, other_turn_angle) <
+                angularDeviation(intersection[index].turn.angle,
+                                 intersection[other_index].turn.angle);
+
+        return becomes_narrower;
+    };
+
+    const bool is_y_arm_first = isValidYArm(first_index, second_index);
+    const bool is_y_arm_second = isValidYArm(second_index, first_index);
+
+    // Only merge valid y-arms
+    if (!is_y_arm_first || !is_y_arm_second)
+        return false;
+
+    if (angle_between < 60)
+        return true;
+
+    // Finally, we also allow merging if all streets offer the same name, it is only three roads and
+    // the angle is not fully extreme:
+    if (intersection.size() != 3)
+        return false;
+
+    // since we have an intersection of size three now, there is only one index we are not looking
+    // at right now. The final index in the intersection is calculated next:
+    const std::size_t third_index = [first_index, second_index]() {
+        if (first_index == 0)
+            return second_index == 2 ? 1 : 2;
+        else if (first_index == 1)
+            return second_index == 2 ? 0 : 2;
+        else
+            return second_index == 1 ? 0 : 1;
+    }();
+
+    // needs to be same road coming in
+    if (node_based_graph.GetEdgeData(intersection[third_index].turn.eid).name_id !=
+        first_data.name_id)
+        return false;
+
+    // we only allow collapsing of a Y like fork. So the angle to the third index has to be
+    // roughly equal:
+    const auto y_angle_difference =
+        angularDeviation(angularDeviation(intersection[third_index].turn.angle,
+                                          intersection[first_index].turn.angle),
+                         angularDeviation(intersection[third_index].turn.angle,
+                                          intersection[second_index].turn.angle));
+
+    // Allow larger angles if its three roads only of the same name
+    // This is a heuristic and might need to be revised.
+    const bool assume_y_intersection =
+        angle_between < 100 && y_angle_difference < FUZZY_ANGLE_DIFFERENCE;
+    return assume_y_intersection;
 }
 
 /*
@@ -184,26 +338,13 @@ Intersection IntersectionGenerator::getConnectedRoads(const NodeID from_node,
  * Anything containing the first u-turn in a merge affects all other angles
  * and is handled separately from all others.
  */
-Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersection) const
+Intersection IntersectionGenerator::MergeSegregatedRoads(const NodeID intersection_node,
+                                                         Intersection intersection) const
 {
     const auto getRight = [&](std::size_t index) {
         return (index + intersection.size() - 1) % intersection.size();
     };
 
-    const auto mergable = [&](std::size_t first, std::size_t second) -> bool {
-        const auto &first_data = node_based_graph.GetEdgeData(intersection[first].turn.eid);
-        const auto &second_data = node_based_graph.GetEdgeData(intersection[second].turn.eid);
-
-        return first_data.name_id != EMPTY_NAMEID && first_data.name_id == second_data.name_id &&
-               !first_data.roundabout && !second_data.roundabout &&
-               first_data.travel_mode == second_data.travel_mode &&
-               first_data.road_classification == second_data.road_classification &&
-               // compatible threshold
-               angularDeviation(intersection[first].turn.angle, intersection[second].turn.angle) <
-                   60 &&
-               first_data.reversed != second_data.reversed;
-    };
-
     const auto merge = [](const ConnectedRoad &first,
                           const ConnectedRoad &second) -> ConnectedRoad {
         if (!first.entry_allowed)
@@ -244,9 +385,42 @@ Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersecti
     }();
 
     // check for merges including the basic u-turn
+    // these result in an adjustment of all other angles. This is due to how these angles are
+    // perceived. Considering the following example:
+    //
+    //   c   b
+    //     Y
+    //     a
+    //
+    // coming from a to b (given a road that splits at the fork into two one-ways), the turn is not
+    // considered as a turn but rather as going straight.
+    // Now if we look at the situation merging:
+    //
+    //  a     b
+    //    \ /
+    // e - + - d
+    //     |
+    //     c
+    //
+    // With a,b representing the same road, the intersection itself represents a classif for way
+    // intersection so we handle it like
+    //
+    //   (a),b
+    //      |
+    // e -  + - d
+    //      |
+    //      c
+    //
+    // To be able to consider this adjusted representation down the line, we merge some roads.
+    // If the merge occurs at the u-turn edge, we need to adjust all angles, though, since they are
+    // with respect to the now changed perceived location of a. If we move (a) to the left, we add
+    // the difference to all angles. Otherwise we subtract it.
+    bool merged_first = false;
     // these result in an adjustment of all other angles
-    if (mergable(0, intersection.size() - 1))
+    if (CanMerge(intersection_node, intersection, 0, intersection.size() - 1))
     {
+        merged_first = true;
+        // moving `a` to the left
         const double correction_factor =
             (360 - intersection[intersection.size() - 1].turn.angle) / 2;
         for (std::size_t i = 1; i + 1 < intersection.size(); ++i)
@@ -256,25 +430,12 @@ Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersecti
         intersection[0] = merge(intersection.front(), intersection.back());
         intersection[0].turn.angle = 0;
 
-        if (is_connected_to_roundabout)
-        {
-            /*
-             * We are merging a u-turn against the direction of a roundabout
-             *
-             *     -----------> roundabout
-             *        /    \
-             *     out      in
-             *
-             * These cases have to be disabled, even if they are not forbidden specifically by a
-             * relation
-             */
-            intersection[0].entry_allowed = false;
-        }
-
         intersection.pop_back();
     }
-    else if (mergable(0, 1))
+    else if (CanMerge(intersection_node, intersection, 0, 1))
     {
+        merged_first = true;
+        // moving `a` to the right
         const double correction_factor = (intersection[1].turn.angle) / 2;
         for (std::size_t i = 2; i < intersection.size(); ++i)
             intersection[i].turn.angle -= correction_factor;
@@ -283,11 +444,26 @@ Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersecti
         intersection.erase(intersection.begin() + 1);
     }
 
+    if (merged_first && is_connected_to_roundabout)
+    {
+        /*
+         * We are merging a u-turn against the direction of a roundabout
+         *
+         *     -----------> roundabout
+         *        /    \
+         *     out      in
+         *
+         * These cases have to be disabled, even if they are not forbidden specifically by a
+         * relation
+         */
+        intersection[0].entry_allowed = false;
+    }
+
     // a merge including the first u-turn requres an adjustment of the turn angles
     // therefore these are handled prior to this step
     for (std::size_t index = 2; index < intersection.size(); ++index)
     {
-        if (mergable(index, getRight(index)))
+        if (CanMerge(intersection_node, intersection, index, getRight(index)))
         {
             intersection[getRight(index)] =
                 merge(intersection[getRight(index)], intersection[index]);
@@ -303,6 +479,134 @@ Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersecti
     return intersection;
 }
 
+// OSM can have some very steep angles for joining roads. Considering the following intersection:
+//        x
+//        |
+//        v __________c
+//       /
+// a ---d
+//       \ __________b
+//
+// with c->d as a oneway
+// and d->b as a oneway, the turn von x->d is actually a turn from x->a. So when looking at the
+// intersection coming from x, we want to interpret the situation as
+//           x
+//           |
+// a __ d __ v__________c
+//      |
+//      |_______________b
+//
+// Where we see the turn to `d` as a right turn, rather than going straight.
+// We do this by adjusting the local turn angle at `x` to turn onto `d` to be reflective of this
+// situation, where `v` would be the node at the intersection.
+Intersection IntersectionGenerator::AdjustForJoiningRoads(const NodeID node_at_intersection,
+                                                          Intersection intersection) const
+{
+    // nothing to do for dead ends
+    if (intersection.size() <= 1)
+        return intersection;
+
+    const util::Coordinate coordinate_at_intersection = node_info_list[node_at_intersection];
+    // never adjust u-turns
+    for (std::size_t index = 1; index < intersection.size(); ++index)
+    {
+        auto &road = intersection[index];
+        // to find out about the above situation, we need to look at the next intersection (at d in
+        // the example). If the initial road can be merged to the left/right, we are about to adjust
+        // the angle.
+        const auto next_intersection_along_road =
+            GetConnectedRoads(node_at_intersection, road.turn.eid);
+        if (next_intersection_along_road.size() <= 1)
+            continue;
+
+        const auto node_at_next_intersection = node_based_graph.GetTarget(road.turn.eid);
+        const util::Coordinate coordinate_at_next_intersection =
+            node_info_list[node_at_next_intersection];
+        if (util::coordinate_calculation::haversineDistance(coordinate_at_intersection,
+                                                            coordinate_at_next_intersection) > 30)
+            continue;
+
+        const auto adjustAngle = [](double angle, double offset) {
+            angle += offset;
+            if (angle > 360)
+                return angle - 360.;
+            else if (angle < 0)
+                return angle + 360.;
+            return angle;
+        };
+
+        // check if the u-turn edge at the next intersection could be merged to the left/right. If
+        // this is the case and the road is not far away (see previous distance check), if
+        // influences the perceived angle.
+        if (CanMerge(node_at_next_intersection, next_intersection_along_road, 0, 1))
+        {
+            const auto offset = 0.5 * angularDeviation(next_intersection_along_road[0].turn.angle,
+                                                       next_intersection_along_road[1].turn.angle);
+            // at the target intersection, we merge to the right, so we need to shift the current
+            // angle to the left
+            road.turn.angle = adjustAngle(road.turn.angle, offset);
+        }
+        else if (CanMerge(node_at_next_intersection,
+                          next_intersection_along_road,
+                          0,
+                          next_intersection_along_road.size() - 1))
+        {
+            const auto offset =
+                0.5 * angularDeviation(
+                          next_intersection_along_road[0].turn.angle,
+                          next_intersection_along_road[next_intersection_along_road.size() - 1]
+                              .turn.angle);
+            // at the target intersection, we merge to the left, so we need to shift the current
+            // angle to the right
+            road.turn.angle = adjustAngle(road.turn.angle, -offset);
+        }
+    }
+    return intersection;
+}
+
+Intersection
+IntersectionGenerator::GetActualNextIntersection(const NodeID starting_node,
+                                                 const EdgeID via_edge,
+                                                 NodeID *resulting_from_node = nullptr,
+                                                 EdgeID *resulting_via_edge = nullptr) const
+{
+    // This function skips over traffic lights/graph compression issues and similar to find the next
+    // actual intersection
+    Intersection result = GetConnectedRoads(starting_node, via_edge);
+
+    // Skip over stuff that has not been compressed due to barriers/parallel edges
+    NodeID node_at_intersection = starting_node;
+    EdgeID incoming_edge = via_edge;
+
+    // to prevent endless loops
+    const auto termination_node = node_based_graph.GetTarget(via_edge);
+
+    // using a maximum lookahead, we make sure not to end up in some form of loop
+    std::unordered_set<NodeID> visited_nodes;
+    while (visited_nodes.count(node_at_intersection) == 0 &&
+           (result.size() == 2 &&
+            node_based_graph.GetEdgeData(via_edge).IsCompatibleTo(
+                node_based_graph.GetEdgeData(result[1].turn.eid))))
+    {
+        visited_nodes.insert(node_at_intersection);
+        node_at_intersection = node_based_graph.GetTarget(incoming_edge);
+        incoming_edge = result[1].turn.eid;
+        result = GetConnectedRoads(node_at_intersection, incoming_edge);
+
+        // When looping back to the original node, we obviously are in a loop. Stop there.
+        if (termination_node == node_based_graph.GetTarget(incoming_edge))
+            break;
+    }
+
+    // return output if requested
+    if (resulting_from_node)
+        *resulting_from_node = node_at_intersection;
+    if (resulting_via_edge)
+        *resulting_via_edge = incoming_edge;
+
+    return result;
+}
+
 } // namespace guidance
 } // namespace extractor
 } // namespace osrm
diff --git a/src/extractor/guidance/intersection_handler.cpp b/src/extractor/guidance/intersection_handler.cpp
index 41023f8..8d80138 100644
--- a/src/extractor/guidance/intersection_handler.cpp
+++ b/src/extractor/guidance/intersection_handler.cpp
@@ -2,10 +2,13 @@
 #include "extractor/guidance/constants.hpp"
 #include "extractor/guidance/toolkit.hpp"
 
+#include "util/coordinate_calculation.hpp"
+#include "util/guidance/toolkit.hpp"
 #include "util/guidance/toolkit.hpp"
 #include "util/simple_logger.hpp"
 
 #include <algorithm>
+#include <cstddef>
 
 using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
 using osrm::util::guidance::getTurnDirection;
@@ -21,21 +24,21 @@ namespace detail
 {
 inline bool requiresAnnouncement(const EdgeData &from, const EdgeData &to)
 {
-    return !from.IsCompatibleTo(to);
+    return !from.CanCombineWith(to);
 }
 }
 
 IntersectionHandler::IntersectionHandler(const util::NodeBasedDynamicGraph &node_based_graph,
                                          const std::vector<QueryNode> &node_info_list,
                                          const util::NameTable &name_table,
-                                         const SuffixTable &street_name_suffix_table)
+                                         const SuffixTable &street_name_suffix_table,
+                                         const IntersectionGenerator &intersection_generator)
     : node_based_graph(node_based_graph), node_info_list(node_info_list), name_table(name_table),
-      street_name_suffix_table(street_name_suffix_table)
+      street_name_suffix_table(street_name_suffix_table),
+      intersection_generator(intersection_generator)
 {
 }
 
-IntersectionHandler::~IntersectionHandler() = default;
-
 std::size_t IntersectionHandler::countValid(const Intersection &intersection) const
 {
     return std::count_if(intersection.begin(), intersection.end(), [](const ConnectedRoad &road) {
@@ -50,9 +53,9 @@ TurnType::Enum IntersectionHandler::findBasicTurnType(const EdgeID via_edge,
     const auto &in_data = node_based_graph.GetEdgeData(via_edge);
     const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
 
-    bool on_ramp = isRampClass(in_data.road_classification.road_class);
+    bool on_ramp = in_data.road_classification.IsRampClass();
 
-    bool onto_ramp = isRampClass(out_data.road_classification.road_class);
+    bool onto_ramp = out_data.road_classification.IsRampClass();
 
     if (!on_ramp && onto_ramp)
         return TurnType::OnRamp;
@@ -88,16 +91,52 @@ TurnInstruction IntersectionHandler::getInstructionForObvious(const std::size_t
         const auto &in_data = node_based_graph.GetEdgeData(via_edge);
         const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
         if (in_data.name_id != out_data.name_id &&
-            requiresNameAnnounced(name_table.GetNameForID(in_data.name_id),
-                                  name_table.GetNameForID(out_data.name_id),
-                                  street_name_suffix_table))
+            util::guidance::requiresNameAnnounced(name_table.GetNameForID(in_data.name_id),
+                                                  name_table.GetRefForID(in_data.name_id),
+                                                  name_table.GetNameForID(out_data.name_id),
+                                                  name_table.GetRefForID(out_data.name_id),
+                                                  street_name_suffix_table))
         {
             // obvious turn onto a through street is a merge
             if (through_street)
             {
-                return {TurnType::Merge,
-                        road.turn.angle > STRAIGHT_ANGLE ? DirectionModifier::SlightRight
-                                                         : DirectionModifier::SlightLeft};
+                // We reserve merges for motorway types. All others are considered for simply going
+                // straight onto a road. This avoids confusion about merge directions on streets
+                // that could potentially also offer different choices
+                if (out_data.road_classification.IsMotorwayClass())
+                    return {TurnType::Merge,
+                            road.turn.angle > STRAIGHT_ANGLE ? DirectionModifier::SlightRight
+                                                             : DirectionModifier::SlightLeft};
+                else if (in_data.road_classification.IsRampClass() &&
+                         out_data.road_classification.IsRampClass())
+                {
+                    if (in_mode == out_mode)
+                        return {TurnType::Suppressed, getTurnDirection(road.turn.angle)};
+                    else
+                        return {TurnType::Notification, getTurnDirection(road.turn.angle)};
+                }
+                else
+                {
+                    const double constexpr MAX_COLLAPSE_DISTANCE = 30;
+                    // in normal road condidtions, we check if the turn is nearly straight.
+                    // Doing so, we widen the angle that a turn is considered straight, but since it
+                    // is obvious, the choice is arguably better.
+
+                    // FIXME this requires https://github.com/Project-OSRM/osrm-backend/pull/2399,
+                    // since `distance` does not refer to an actual distance but rather to the
+                    // duration/weight of the traversal. We can only approximate the distance here
+                    // or actually follow the full road. When 2399 lands, we can exchange here for a
+                    // precalculated distance value.
+                    const auto distance = util::coordinate_calculation::haversineDistance(
+                        node_info_list[node_based_graph.GetTarget(via_edge)],
+                        node_info_list[node_based_graph.GetTarget(road.turn.eid)]);
+                    return {TurnType::Turn,
+                            (angularDeviation(road.turn.angle, STRAIGHT_ANGLE) <
+                                 FUZZY_ANGLE_DIFFERENCE ||
+                             distance > 2 * MAX_COLLAPSE_DISTANCE)
+                                ? DirectionModifier::Straight
+                                : getTurnDirection(road.turn.angle)};
+                }
             }
             else
             {
@@ -132,10 +171,10 @@ void IntersectionHandler::assignFork(const EdgeID via_edge,
                                      ConnectedRoad &right) const
 {
     const auto &in_data = node_based_graph.GetEdgeData(via_edge);
-    const bool low_priority_left = isLowPriorityRoadClass(
-        node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class);
-    const bool low_priority_right = isLowPriorityRoadClass(
-        node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class);
+    const bool low_priority_left =
+        node_based_graph.GetEdgeData(left.turn.eid).road_classification.IsLowPriorityRoadClass();
+    const bool low_priority_right =
+        node_based_graph.GetEdgeData(right.turn.eid).road_classification.IsLowPriorityRoadClass();
     if ((angularDeviation(left.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
          angularDeviation(right.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE))
     {
@@ -308,20 +347,319 @@ bool IntersectionHandler::isThroughStreet(const std::size_t index,
 {
     if (node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id == EMPTY_NAMEID)
         return false;
-    for (const auto &road : intersection)
+
+    const auto &data_at_index = node_based_graph.GetEdgeData(intersection[index].turn.eid);
+
+    // a through street cannot start at our own position -> index 1
+    for (std::size_t road_index = 1; road_index < intersection.size(); ++road_index)
     {
-        // a through street cannot start at our own position
-        if (road.turn.angle < std::numeric_limits<double>::epsilon())
+        if (road_index == index)
             continue;
-        if (angularDeviation(road.turn.angle, intersection[index].turn.angle) >
-                (STRAIGHT_ANGLE - NARROW_TURN_ANGLE) &&
-            node_based_graph.GetEdgeData(road.turn.eid).name_id ==
-                node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id)
+
+        const auto &road = intersection[road_index];
+        const auto &road_data = node_based_graph.GetEdgeData(road.turn.eid);
+
+        // roads have a near straight angle (180 degree)
+        const bool is_nearly_straight =
+            angularDeviation(road.turn.angle, intersection[index].turn.angle) >
+            (STRAIGHT_ANGLE - FUZZY_ANGLE_DIFFERENCE);
+
+        const bool have_same_name = data_at_index.name_id == road_data.name_id;
+        const bool have_same_category =
+            data_at_index.road_classification == road_data.road_classification;
+
+        if (is_nearly_straight && have_same_name && have_same_category)
             return true;
     }
     return false;
 }
 
+std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge,
+                                                 const Intersection &intersection) const
+{
+    // no obvious road
+    if (intersection.size() == 1)
+        return 0;
+
+    // a single non u-turn is obvious
+    if (intersection.size() == 2)
+        return 1;
+
+    // at least three roads
+    std::size_t best = 0;
+    double best_deviation = 180;
+
+    std::size_t best_continue = 0;
+    double best_continue_deviation = 180;
+
+    const EdgeData &in_data = node_based_graph.GetEdgeData(via_edge);
+    const auto in_classification = in_data.road_classification;
+
+    const auto obvious_by_road_class = [](const RoadClassification in_classification,
+                                          const RoadClassification obvious_candidate,
+                                          const RoadClassification compare_candidate) {
+        const bool has_high_priority =
+            PRIORITY_DISTINCTION_FACTOR * obvious_candidate.GetPriority() <
+            compare_candidate.GetPriority();
+        const bool continues_on_same_class = in_classification == obvious_candidate;
+        return (has_high_priority && continues_on_same_class) ||
+               (!obvious_candidate.IsLowPriorityRoadClass() &&
+                compare_candidate.IsLowPriorityRoadClass());
+    };
+
+    for (std::size_t i = 1; i < intersection.size(); ++i)
+    {
+        const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE);
+        if (!intersection[i].entry_allowed)
+            continue;
+
+        const auto out_data = node_based_graph.GetEdgeData(intersection[i].turn.eid);
+        const auto continue_class =
+            node_based_graph.GetEdgeData(intersection[best_continue].turn.eid).road_classification;
+
+        if (out_data.name_id == in_data.name_id &&
+            (best_continue == 0 ||
+             (continue_class.GetPriority() > out_data.road_classification.GetPriority() &&
+              in_classification != continue_class) ||
+             (deviation < best_continue_deviation &&
+              out_data.road_classification == continue_class) ||
+             (continue_class != in_classification &&
+              out_data.road_classification == continue_class)))
+        {
+            best_continue_deviation = deviation;
+            best_continue = i;
+        }
+
+        const auto current_best_class =
+            node_based_graph.GetEdgeData(intersection[best_continue].turn.eid).road_classification;
+
+        // don't prefer low priority classes
+        if (out_data.road_classification.IsLowPriorityRoadClass() &&
+            !current_best_class.IsLowPriorityRoadClass())
+            continue;
+
+        const bool is_better_choice_by_priority = obvious_by_road_class(
+            in_data.road_classification, out_data.road_classification, current_best_class);
+
+        const bool other_is_better_choice_by_priority = obvious_by_road_class(
+            in_data.road_classification, current_best_class, out_data.road_classification);
+
+        if ((!other_is_better_choice_by_priority && deviation < best_deviation) ||
+            is_better_choice_by_priority)
+        {
+            best_deviation = deviation;
+            best = i;
+        }
+    }
+
+    // We don't consider empty names a valid continue feature. This distinguishes between missing
+    // names and actual continuing roads.
+    if (in_data.name_id == EMPTY_NAMEID)
+        best_continue = 0;
+
+    if (best == 0)
+        return 0;
+
+    const std::pair<std::int64_t, std::int64_t> num_continue_names = [&]() {
+        std::int64_t count = 0, count_valid = 0;
+        if (in_data.name_id != EMPTY_NAMEID)
+        {
+            for (std::size_t i = 1; i < intersection.size(); ++i)
+            {
+                const auto &road = intersection[i];
+                if ((in_data.name_id == node_based_graph.GetEdgeData(road.turn.eid).name_id))
+                {
+                    ++count;
+                    if (road.entry_allowed)
+                        ++count_valid;
+                }
+            }
+        }
+        return std::make_pair(count, count_valid);
+    }();
+
+    if (0 != best_continue && best != best_continue &&
+        angularDeviation(intersection[best].turn.angle, STRAIGHT_ANGLE) <
+            MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
+        node_based_graph.GetEdgeData(intersection[best_continue].turn.eid).road_classification ==
+            node_based_graph.GetEdgeData(intersection[best].turn.eid).road_classification)
+    {
+        // if the best angle is going straight but the road is turning, we don't name anything
+        // obvious
+        return 0;
+    }
+
+    const bool all_continues_are_narrow = [&]() {
+        if (in_data.name_id == EMPTY_NAMEID)
+            return false;
+
+        return std::count_if(
+                   intersection.begin() + 1, intersection.end(), [&](const ConnectedRoad &road) {
+                       return (in_data.name_id ==
+                               node_based_graph.GetEdgeData(road.turn.eid).name_id) &&
+                              angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE;
+                   }) == num_continue_names.first;
+    }();
+
+    // has no obvious continued road
+    const auto &best_data = node_based_graph.GetEdgeData(intersection[best].turn.eid);
+    if (best_continue == 0 || (!all_continues_are_narrow &&
+                               (num_continue_names.first >= 2 && intersection.size() >= 4)) ||
+        (num_continue_names.second >= 2 && best_continue_deviation >= 2 * NARROW_TURN_ANGLE) ||
+        (best_deviation != best_continue_deviation && best_deviation < FUZZY_ANGLE_DIFFERENCE &&
+         !best_data.road_classification.IsRampClass()))
+    {
+        // Find left/right deviation
+        const double left_deviation = angularDeviation(
+            intersection[(best + 1) % intersection.size()].turn.angle, STRAIGHT_ANGLE);
+        const double right_deviation =
+            angularDeviation(intersection[best - 1].turn.angle, STRAIGHT_ANGLE);
+
+        if (best_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
+            std::min(left_deviation, right_deviation) > FUZZY_ANGLE_DIFFERENCE)
+            return best;
+
+        const auto left_index = (best + 1) % intersection.size();
+        const auto right_index = best - 1;
+        const auto &left_data = node_based_graph.GetEdgeData(intersection[left_index].turn.eid);
+        const auto &right_data = node_based_graph.GetEdgeData(intersection[right_index].turn.eid);
+
+        const bool obvious_to_left =
+            left_index == 0 || obvious_by_road_class(in_data.road_classification,
+                                                     best_data.road_classification,
+                                                     left_data.road_classification);
+        const bool obvious_to_right =
+            right_index == 0 || obvious_by_road_class(in_data.road_classification,
+                                                      best_data.road_classification,
+                                                      right_data.road_classification);
+
+        // other narrow turns?
+        if (angularDeviation(intersection[right_index].turn.angle, STRAIGHT_ANGLE) <=
+                FUZZY_ANGLE_DIFFERENCE &&
+            !obvious_to_right)
+            return 0;
+
+        if (angularDeviation(intersection[left_index].turn.angle, STRAIGHT_ANGLE) <=
+                FUZZY_ANGLE_DIFFERENCE &&
+            !obvious_to_left)
+            return 0;
+
+        const bool distinct_to_left =
+            left_deviation / best_deviation >= DISTINCTION_RATIO ||
+            (left_deviation > best_deviation &&
+             (!intersection[left_index].entry_allowed && in_data.distance > 30));
+        const bool distinct_to_right =
+            right_deviation / best_deviation >= DISTINCTION_RATIO ||
+            (right_deviation > best_deviation &&
+             (!intersection[right_index].entry_allowed && in_data.distance > 30));
+
+        // Well distinct turn that is nearly straight
+        if ((distinct_to_left || obvious_to_left) && (distinct_to_right || obvious_to_right))
+            return best;
+    }
+    else
+    {
+        const double deviation =
+            angularDeviation(intersection[best_continue].turn.angle, STRAIGHT_ANGLE);
+        const auto &continue_data =
+            node_based_graph.GetEdgeData(intersection[best_continue].turn.eid);
+        if (std::abs(deviation) < 1)
+            return best_continue;
+
+        // check if any other similar best continues exist
+        for (std::size_t i = 1; i < intersection.size(); ++i)
+        {
+            if (i == best_continue || !intersection[i].entry_allowed)
+                continue;
+
+            const auto &turn_data = node_based_graph.GetEdgeData(intersection[i].turn.eid);
+            const bool is_obvious_by_road_class =
+                obvious_by_road_class(in_data.road_classification,
+                                      continue_data.road_classification,
+                                      turn_data.road_classification);
+
+            // if the main road is obvious by class, we ignore the current road as a potential
+            // prevention of obviousness
+            if (is_obvious_by_road_class)
+                continue;
+
+            // continuation could be grouped with a straight turn and the turning road is a ramp
+            if (turn_data.road_classification.IsRampClass() && deviation < GROUP_ANGLE)
+                continue;
+
+            // perfectly straight turns prevent obviousness
+            const auto turn_deviation =
+                angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE);
+            if (turn_deviation < FUZZY_ANGLE_DIFFERENCE)
+                return 0;
+
+            const auto deviation_ratio = turn_deviation / deviation;
+
+            // in comparison to normal devitions, a continue road can offer a smaller distinction
+            // ratio. Other roads close to the turn angle are not as obvious, if one road continues.
+            if (deviation_ratio < DISTINCTION_RATIO / 1.5)
+                return 0;
+
+            /* in comparison to another continuing road, we need a better distinction. This prevents
+               situations where the turn is probably less obvious. An example are places that have a
+               road with the same name entering/exiting:
+
+                       d
+                      /
+                     /
+               a -- b
+                     \
+                      \
+                       c
+            */
+
+            if (turn_data.name_id == continue_data.name_id &&
+                deviation_ratio < 1.5 * DISTINCTION_RATIO)
+                return 0;
+        }
+
+        // Segregated intersections can result in us finding an obvious turn, even though its only
+        // obvious due to a very short segment in between. So if the segment coming in is very
+        // short, we check the previous intersection for other continues in the opposite bearing.
+        const auto node_at_intersection = node_based_graph.GetTarget(via_edge);
+        const util::Coordinate coordinate_at_intersection = node_info_list[node_at_intersection];
+
+        const auto node_at_u_turn = node_based_graph.GetTarget(intersection[0].turn.eid);
+        const util::Coordinate coordinate_at_u_turn = node_info_list[node_at_u_turn];
+
+        const double constexpr MAX_COLLAPSE_DISTANCE = 30;
+        if (util::coordinate_calculation::haversineDistance(
+                coordinate_at_intersection, coordinate_at_u_turn) < MAX_COLLAPSE_DISTANCE)
+        {
+            // this request here actually goes against the direction of the ingoing edgeid. This can
+            // even reverse the direction. Since we don't want to compute actual turns but simply
+            // try to find whether there is a turn going to the opposite direction of our obvious
+            // turn, this should be alright.
+            const auto previous_intersection = intersection_generator.GetActualNextIntersection(
+                node_at_intersection, intersection[0].turn.eid, nullptr, nullptr);
+
+            const auto continue_road = intersection[best_continue];
+            for (const auto &comparison_road : previous_intersection)
+            {
+                // since we look at the intersection in the wrong direction, a similar angle
+                // actually represents a near 180 degree different in bearings between the two
+                // roads. So if there is a road that is enterable in the opposite direction just
+                // prior, a turn is not obvious
+                const auto &turn_data = node_based_graph.GetEdgeData(comparison_road.turn.eid);
+                if (angularDeviation(comparison_road.turn.angle, STRAIGHT_ANGLE) > GROUP_ANGLE &&
+                    angularDeviation(comparison_road.turn.angle, continue_road.turn.angle) <
+                        FUZZY_ANGLE_DIFFERENCE &&
+                    !turn_data.reversed && continue_data.CanCombineWith(turn_data))
+                    return 0;
+            }
+        }
+
+        return best_continue;
+    }
+
+    return 0;
+}
+
 } // namespace guidance
 } // namespace extractor
 } // namespace osrm
diff --git a/src/extractor/guidance/intersection_scenario_three_way.cpp b/src/extractor/guidance/intersection_scenario_three_way.cpp
index 7241eb7..d85e7df 100644
--- a/src/extractor/guidance/intersection_scenario_three_way.cpp
+++ b/src/extractor/guidance/intersection_scenario_three_way.cpp
@@ -13,14 +13,6 @@ namespace extractor
 namespace guidance
 {
 
-bool isFork(const ConnectedRoad &,
-            const ConnectedRoad &possible_right_fork,
-            const ConnectedRoad &possible_left_fork)
-{
-    return angularDeviation(possible_right_fork.turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE &&
-           angularDeviation(possible_left_fork.turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE;
-}
-
 bool isEndOfRoad(const ConnectedRoad &,
                  const ConnectedRoad &possible_right_turn,
                  const ConnectedRoad &possible_left_turn)
diff --git a/src/extractor/guidance/motorway_handler.cpp b/src/extractor/guidance/motorway_handler.cpp
index e08b034..d9ed02c 100644
--- a/src/extractor/guidance/motorway_handler.cpp
+++ b/src/extractor/guidance/motorway_handler.cpp
@@ -1,6 +1,6 @@
 #include "extractor/guidance/motorway_handler.hpp"
-#include "extractor/guidance/classification_data.hpp"
 #include "extractor/guidance/constants.hpp"
+#include "extractor/guidance/road_classification.hpp"
 #include "extractor/guidance/toolkit.hpp"
 
 #include "util/guidance/toolkit.hpp"
@@ -20,35 +20,39 @@ namespace extractor
 {
 namespace guidance
 {
-namespace detail
+namespace
 {
 
 inline bool isMotorwayClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
 {
-    return isMotorwayClass(node_based_graph.GetEdgeData(eid).road_classification.road_class);
+    return node_based_graph.GetEdgeData(eid).road_classification.IsMotorwayClass();
 }
-inline FunctionalRoadClass roadClass(const ConnectedRoad &road,
-                                     const util::NodeBasedDynamicGraph &graph)
+inline RoadClassification roadClass(const ConnectedRoad &road,
+                                    const util::NodeBasedDynamicGraph &graph)
 {
-    return graph.GetEdgeData(road.turn.eid).road_classification.road_class;
+    return graph.GetEdgeData(road.turn.eid).road_classification;
 }
 
 inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
 {
-    return isRampClass(node_based_graph.GetEdgeData(eid).road_classification.road_class);
-}
+    return node_based_graph.GetEdgeData(eid).road_classification.IsRampClass();
 }
 
+} // namespace
+
 MotorwayHandler::MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
                                  const std::vector<QueryNode> &node_info_list,
                                  const util::NameTable &name_table,
-                                 const SuffixTable &street_name_suffix_table)
-    : IntersectionHandler(node_based_graph, node_info_list, name_table, street_name_suffix_table)
+                                 const SuffixTable &street_name_suffix_table,
+                                 const IntersectionGenerator &intersection_generator)
+    : IntersectionHandler(node_based_graph,
+                          node_info_list,
+                          name_table,
+                          street_name_suffix_table,
+                          intersection_generator)
 {
 }
 
-MotorwayHandler::~MotorwayHandler() = default;
-
 bool MotorwayHandler::canProcess(const NodeID,
                                  const EdgeID via_eid,
                                  const Intersection &intersection) const
@@ -58,36 +62,29 @@ bool MotorwayHandler::canProcess(const NodeID,
 
     for (const auto &road : intersection)
     {
-        const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
         // not merging or forking?
         if (road.entry_allowed && angularDeviation(road.turn.angle, STRAIGHT_ANGLE) > 60)
             return false;
-        else if (out_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY ||
-                 out_data.road_classification.road_class == FunctionalRoadClass::TRUNK)
+        else if (isMotorwayClass(road.turn.eid, node_based_graph))
         {
             if (road.entry_allowed)
                 has_motorway = true;
         }
-        else if (!isRampClass(out_data.road_classification.road_class))
+        else if (!isRampClass(road.turn.eid, node_based_graph))
             has_normal_roads = true;
     }
 
     if (has_normal_roads)
         return false;
 
-    const auto &in_data = node_based_graph.GetEdgeData(via_eid);
-    return has_motorway ||
-           in_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY ||
-           in_data.road_classification.road_class == FunctionalRoadClass::TRUNK;
+    return has_motorway || isMotorwayClass(via_eid, node_based_graph);
 }
 
 Intersection MotorwayHandler::
 operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const
 {
-    const auto &in_data = node_based_graph.GetEdgeData(via_eid);
-
     // coming from motorway
-    if (isMotorwayClass(in_data.road_classification.road_class))
+    if (isMotorwayClass(via_eid, node_based_graph))
     {
         intersection = fromMotorway(via_eid, std::move(intersection));
         std::for_each(intersection.begin(), intersection.end(), [](ConnectedRoad &road) {
@@ -106,13 +103,13 @@ operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const
 Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection intersection) const
 {
     const auto &in_data = node_based_graph.GetEdgeData(via_eid);
-    BOOST_ASSERT(isMotorwayClass(in_data.road_classification.road_class));
+    BOOST_ASSERT(isMotorwayClass(via_eid, node_based_graph));
 
     const auto countExitingMotorways = [this](const Intersection &intersection) {
         unsigned count = 0;
         for (const auto &road : intersection)
         {
-            if (road.entry_allowed && detail::isMotorwayClass(road.turn.eid, node_based_graph))
+            if (road.entry_allowed && isMotorwayClass(road.turn.eid, node_based_graph))
                 ++count;
         }
         return count;
@@ -124,8 +121,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
         {
             const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
             if (road.turn.angle != 0 && in_data.name_id == out_data.name_id &&
-                in_data.name_id != EMPTY_NAMEID &&
-                isMotorwayClass(out_data.road_classification.road_class))
+                in_data.name_id != EMPTY_NAMEID && isMotorwayClass(road.turn.eid, node_based_graph))
                 return road.turn.angle;
         }
         return intersection[0].turn.angle;
@@ -136,8 +132,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
         double best = 180;
         for (const auto &road : intersection)
         {
-            const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
-            if (isMotorwayClass(out_data.road_classification.road_class) &&
+            if (isMotorwayClass(road.turn.eid, node_based_graph) &&
                 angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < best)
             {
                 best = angularDeviation(road.turn.angle, STRAIGHT_ANGLE);
@@ -185,10 +180,10 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
             }
         }
         else if (intersection.size() == 4 &&
-                 detail::roadClass(intersection[1], node_based_graph) ==
-                     detail::roadClass(intersection[2], node_based_graph) &&
-                 detail::roadClass(intersection[2], node_based_graph) ==
-                     detail::roadClass(intersection[3], node_based_graph))
+                 roadClass(intersection[1], node_based_graph) ==
+                     roadClass(intersection[2], node_based_graph) &&
+                 roadClass(intersection[2], node_based_graph) ==
+                     roadClass(intersection[3], node_based_graph))
         {
             // tripple fork at the end
             assignFork(via_eid, intersection[3], intersection[2], intersection[1]);
@@ -214,7 +209,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
             {
                 if (road.entry_allowed)
                 {
-                    BOOST_ASSERT(detail::isRampClass(road.turn.eid, node_based_graph));
+                    BOOST_ASSERT(isRampClass(road.turn.eid, node_based_graph));
                     road.turn.instruction =
                         TurnInstruction::SUPPRESSED(getTurnDirection(road.turn.angle));
                 }
@@ -225,7 +220,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
             // normal motorway passing some ramps or mering onto another motorway
             if (intersection.size() == 2)
             {
-                BOOST_ASSERT(!detail::isRampClass(intersection[1].turn.eid, node_based_graph));
+                BOOST_ASSERT(!isRampClass(intersection[1].turn.eid, node_based_graph));
 
                 intersection[1].turn.instruction =
                     getInstructionForObvious(intersection.size(),
@@ -250,16 +245,16 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
                     else if (road.turn.angle < continue_angle)
                     {
                         road.turn.instruction = {
-                            detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::OffRamp
-                                                                                 : TurnType::Turn,
+                            isRampClass(road.turn.eid, node_based_graph) ? TurnType::OffRamp
+                                                                         : TurnType::Turn,
                             (road.turn.angle < 145) ? DirectionModifier::Right
                                                     : DirectionModifier::SlightRight};
                     }
                     else if (road.turn.angle > continue_angle)
                     {
                         road.turn.instruction = {
-                            detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::OffRamp
-                                                                                 : TurnType::Turn,
+                            isRampClass(road.turn.eid, node_based_graph) ? TurnType::OffRamp
+                                                                         : TurnType::Turn,
                             (road.turn.angle > 215) ? DirectionModifier::Left
                                                     : DirectionModifier::SlightLeft};
                     }
@@ -287,7 +282,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
                 for (std::size_t i = 0; i < intersection.size(); ++i)
                 {
                     if (intersection[i].entry_allowed &&
-                        detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph))
+                        isMotorwayClass(intersection[i].turn.eid, node_based_graph))
                     {
                         if (first_valid < intersection.size())
                         {
@@ -311,7 +306,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
                 for (std::size_t i = 0; i < intersection.size(); ++i)
                 {
                     if (intersection[i].entry_allowed &&
-                        detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph))
+                        isMotorwayClass(intersection[i].turn.eid, node_based_graph))
                     {
                         if (second_valid < intersection.size())
                         {
@@ -351,7 +346,7 @@ Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection inters
     if (intersection.size() == 2 && num_valid_turns == 1)
     {
         BOOST_ASSERT(!intersection[0].entry_allowed);
-        BOOST_ASSERT(detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph));
+        BOOST_ASSERT(isMotorwayClass(intersection[1].turn.eid, node_based_graph));
 
         intersection[1].turn.instruction = getInstructionForObvious(
             intersection.size(), via_eid, isThroughStreet(1, intersection), intersection[1]);
@@ -372,7 +367,7 @@ Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection inters
             //          0
             if (intersection[1].entry_allowed)
             {
-                if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
+                if (isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
                     node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id !=
                         EMPTY_NAMEID &&
                     node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id ==
@@ -399,7 +394,7 @@ Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection inters
             else
             {
                 BOOST_ASSERT(intersection[2].entry_allowed);
-                if (detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph) &&
+                if (isMotorwayClass(intersection[2].turn.eid, node_based_graph) &&
                     node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id !=
                         EMPTY_NAMEID &&
                     node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id ==
@@ -436,8 +431,8 @@ Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection inters
             //    \   /
             //      |
             //      R
-            if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
-                detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph))
+            if (isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
+                isMotorwayClass(intersection[2].turn.eid, node_based_graph))
             {
                 assignFork(via_eid, intersection[2], intersection[1]);
             }
@@ -448,8 +443,7 @@ Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection inters
                 //      M  R
                 //      | /
                 //      R
-                if (isMotorwayClass(node_based_graph.GetEdgeData(intersection[1].turn.eid)
-                                        .road_classification.road_class))
+                if (isMotorwayClass(intersection[1].turn.eid, node_based_graph))
                 {
                     intersection[1].turn.instruction = {TurnType::Turn,
                                                         DirectionModifier::SlightRight};
@@ -469,12 +463,11 @@ Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection inters
         bool passed_highway_entry = false;
         for (auto &road : intersection)
         {
-            const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid);
-            if (!road.entry_allowed && isMotorwayClass(edge_data.road_classification.road_class))
+            if (!road.entry_allowed && isMotorwayClass(road.turn.eid, node_based_graph))
             {
                 passed_highway_entry = true;
             }
-            else if (isMotorwayClass(edge_data.road_classification.road_class))
+            else if (isMotorwayClass(road.turn.eid, node_based_graph))
             {
                 road.turn.instruction = {TurnType::Merge,
                                          passed_highway_entry ? DirectionModifier::SlightRight
@@ -482,7 +475,7 @@ Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection inters
             }
             else
             {
-                BOOST_ASSERT(isRampClass(edge_data.road_classification.road_class));
+                BOOST_ASSERT(isRampClass(road.turn.eid, node_based_graph));
                 road.turn.instruction = {TurnType::OffRamp, getTurnDirection(road.turn.angle)};
             }
         }
@@ -505,13 +498,13 @@ Intersection MotorwayHandler::fallback(Intersection intersection) const
 
         util::SimpleLogger().Write(logDEBUG)
             << "road: " << toString(road) << " Name: " << out_data.name_id
-            << " Road Class: " << (int)out_data.road_classification.road_class;
+            << " Road Class: " << out_data.road_classification.ToString();
 
         if (!road.entry_allowed)
             continue;
 
-        const auto type = isMotorwayClass(out_data.road_classification.road_class) ? TurnType::Merge
-                                                                                   : TurnType::Turn;
+        const auto type =
+            isMotorwayClass(road.turn.eid, node_based_graph) ? TurnType::Merge : TurnType::Turn;
 
         if (type == TurnType::Turn)
         {
diff --git a/src/extractor/guidance/roundabout_handler.cpp b/src/extractor/guidance/roundabout_handler.cpp
index df0455b..4018f26 100644
--- a/src/extractor/guidance/roundabout_handler.cpp
+++ b/src/extractor/guidance/roundabout_handler.cpp
@@ -25,14 +25,18 @@ RoundaboutHandler::RoundaboutHandler(const util::NodeBasedDynamicGraph &node_bas
                                      const std::vector<QueryNode> &node_info_list,
                                      const CompressedEdgeContainer &compressed_edge_container,
                                      const util::NameTable &name_table,
-                                     const SuffixTable &street_name_suffix_table)
-    : IntersectionHandler(node_based_graph, node_info_list, name_table, street_name_suffix_table),
-      compressed_edge_container(compressed_edge_container)
+                                     const SuffixTable &street_name_suffix_table,
+                                     const ProfileProperties &profile_properties,
+                                     const IntersectionGenerator &intersection_generator)
+    : IntersectionHandler(node_based_graph,
+                          node_info_list,
+                          name_table,
+                          street_name_suffix_table,
+                          intersection_generator),
+      compressed_edge_container(compressed_edge_container), profile_properties(profile_properties)
 {
 }
 
-RoundaboutHandler::~RoundaboutHandler() = default;
-
 bool RoundaboutHandler::canProcess(const NodeID from_nid,
                                    const EdgeID via_eid,
                                    const Intersection &intersection) const
@@ -66,8 +70,13 @@ detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags(
     bool on_roundabout = in_edge_data.roundabout;
     bool can_enter_roundabout = false;
     bool can_exit_roundabout_separately = false;
-    for (const auto &road : intersection)
+
+    const bool lhs = profile_properties.left_hand_driving;
+    const int step = lhs ? -1 : 1;
+    for (std::size_t cnt = 0, idx = lhs ? intersection.size() - 1 : 0; cnt < intersection.size();
+         ++cnt, idx += step)
     {
+        const auto &road = intersection[idx];
         const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid);
         // only check actual outgoing edges
         if (edge_data.reversed || !road.entry_allowed)
@@ -83,8 +92,6 @@ detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags(
         // separate vertex than the one we are coming from that are in the direction of
         // the roundabout.
         // The sorting of the angles represents a problem for left-sided driving, though.
-        // FIXME in case of left-sided driving, we have to check whether we can enter the
-        // roundabout later in the cycle, rather than prior.
         // FIXME requires consideration of crossing the roundabout
         else if (node_based_graph.GetTarget(road.turn.eid) != from_nid && !can_enter_roundabout)
         {
@@ -103,8 +110,12 @@ void RoundaboutHandler::invalidateExitAgainstDirection(const NodeID from_nid,
         return;
 
     bool past_roundabout_angle = false;
-    for (auto &road : intersection)
+    const bool lhs = profile_properties.left_hand_driving;
+    const int step = lhs ? -1 : 1;
+    for (std::size_t cnt = 0, idx = lhs ? intersection.size() - 1 : 0; cnt < intersection.size();
+         ++cnt, idx += step)
     {
+        auto &road = intersection[idx];
         const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid);
         // only check actual outgoing edges
         if (edge_data.reversed)
@@ -120,8 +131,6 @@ void RoundaboutHandler::invalidateExitAgainstDirection(const NodeID from_nid,
         // This workaround handles cases in which an exit precedes and entry. The resulting
         // u-turn against the roundabout direction is invalidated.
         // The sorting of the angles represents a problem for left-sided driving, though.
-        // FIXME in case of left-sided driving, we have to check whether we can enter the
-        // roundabout later in the cycle, rather than prior.
         if (!edge_data.roundabout && node_based_graph.GetTarget(road.turn.eid) != from_nid &&
             past_roundabout_angle)
         {
@@ -213,54 +222,65 @@ RoundaboutType RoundaboutHandler::getRoundaboutType(const NodeID nid) const
     std::unordered_set<unsigned> roundabout_name_ids;
     std::unordered_set<unsigned> connected_names;
 
-    const auto getNextOnRoundabout =
-        [this, &roundabout_name_ids, &connected_names](const NodeID node) {
-            EdgeID continue_edge = SPECIAL_EDGEID;
-            for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node))
+    const auto getNextOnRoundabout = [this, &roundabout_name_ids, &connected_names](
+        const NodeID node) {
+        EdgeID continue_edge = SPECIAL_EDGEID;
+        for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node))
+        {
+            const auto &edge_data = node_based_graph.GetEdgeData(edge);
+            if (!edge_data.reversed && edge_data.roundabout)
             {
-                const auto &edge_data = node_based_graph.GetEdgeData(edge);
-                if (!edge_data.reversed && edge_data.roundabout)
+                if (SPECIAL_EDGEID != continue_edge)
                 {
-                    if (SPECIAL_EDGEID != continue_edge)
-                    {
-                        // fork in roundabout
-                        return SPECIAL_EDGEID;
-                    }
-
-                    if (EMPTY_NAMEID != edge_data.name_id)
-                    {
-                        bool add = true;
-                        for (auto name_id : roundabout_name_ids)
-                        {
-
-                            if (!requiresNameAnnounced(name_table.GetNameForID(name_id),
-                                                       name_table.GetNameForID(edge_data.name_id),
-                                                       street_name_suffix_table))
-                            {
-                                add = false;
-                                break;
-                            }
-                        }
-                        if (add)
-                            roundabout_name_ids.insert(edge_data.name_id);
-                    }
-
-                    continue_edge = edge;
+                    // fork in roundabout
+                    return SPECIAL_EDGEID;
                 }
-                else if (!edge_data.roundabout)
+
+                if (EMPTY_NAMEID != edge_data.name_id)
                 {
-                    // remember all connected road names
-                    connected_names.insert(edge_data.name_id);
+
+                    const auto announce = [&](unsigned id) {
+                        return util::guidance::requiresNameAnnounced(
+                            name_table.GetNameForID(id),
+                            name_table.GetRefForID(id),
+                            name_table.GetNameForID(edge_data.name_id),
+                            name_table.GetRefForID(edge_data.name_id),
+                            street_name_suffix_table);
+                    };
+
+                    if (std::all_of(begin(roundabout_name_ids), end(roundabout_name_ids), announce))
+                        roundabout_name_ids.insert(edge_data.name_id);
                 }
+
+                continue_edge = edge;
             }
-            return continue_edge;
-        };
+            else if (!edge_data.roundabout)
+            {
+                // remember all connected road names
+                connected_names.insert(edge_data.name_id);
+            }
+        }
+        return continue_edge;
+    };
     // the roundabout radius has to be the same for all locations we look at it from
     // to guarantee this, we search the full roundabout for its vertices
-    // and select the three smalles ids
+    // and select the three smallest ids
     std::set<NodeID> roundabout_nodes; // needs to be sorted
 
     // this value is a hard abort to deal with potential self-loops
+    const auto countRoundaboutFlags = [&](const NodeID at_node) {
+        // FIXME: this would be nicer as boost::count_if, but our integer range does not support
+        // these range based handlers
+        std::size_t count = 0;
+        for (const auto edge : node_based_graph.GetAdjacentEdgeRange(at_node))
+        {
+            const auto &edge_data = node_based_graph.GetEdgeData(edge);
+            if (edge_data.roundabout)
+                count++;
+        }
+        return count;
+    };
+
     NodeID last_node = nid;
     while (0 == roundabout_nodes.count(last_node))
     {
@@ -268,6 +288,10 @@ RoundaboutType RoundaboutHandler::getRoundaboutType(const NodeID nid) const
         if (node_based_graph.GetOutDegree(last_node) > 2)
             roundabout_nodes.insert(last_node);
 
+        // detect invalid/complex roundabout taggings
+        if (countRoundaboutFlags(last_node) != 2)
+            return RoundaboutType::None;
+
         const auto eid = getNextOnRoundabout(last_node);
 
         if (eid == SPECIAL_EDGEID)
@@ -317,37 +341,26 @@ RoundaboutType RoundaboutHandler::getRoundaboutType(const NodeID nid) const
     if (std::isinf(radius))
         return RoundaboutType::Roundabout;
 
-    // not within the dedicated radii for special roundabouts
-    if (radius > MAX_ROUNDABOUT_INTERSECTION_RADIUS && radius <= MAX_ROUNDABOUT_RADIUS)
-        return RoundaboutType::Roundabout;
+    // Looks like a rotary: large roundabout with dedicated name
+    // do we have a dedicated name for the rotary, if not its a roundabout
+    // This function can theoretically fail if the roundabout name is partly
+    // used with a reference and without. This will be fixed automatically
+    // when we handle references separately or if the useage is more consistent
+    const auto is_rotary = 1 == roundabout_name_ids.size() &&                          //
+                           0 == connected_names.count(*roundabout_name_ids.begin()) && //
+                           radius > MAX_ROUNDABOUT_RADIUS;
 
-    if (radius > MAX_ROUNDABOUT_RADIUS)
-    {
-        // do we have a dedicated name for the rotary, if not its a roundabout
-        // This function can theoretically fail if the roundabout name is partly
-        // used with a reference and without. This will be fixed automatically
-        // when we handle references separately or if the useage is more consistent
-
-        if (1 == roundabout_name_ids.size() &&
-            0 == connected_names.count(*roundabout_name_ids.begin()))
-            return RoundaboutType::Rotary;
-        else
-            return RoundaboutType::Roundabout;
-    }
+    if (is_rotary)
+        return RoundaboutType::Rotary;
 
-    if (radius <= MAX_ROUNDABOUT_INTERSECTION_RADIUS)
-    {
-        const bool qualifies_as_roundabout_nitersection =
-            qualifiesAsRoundaboutIntersection(roundabout_nodes);
-        if (qualifies_as_roundabout_nitersection)
-        {
-            return RoundaboutType::RoundaboutIntersection;
-        }
-        else
-        {
-            return RoundaboutType::Roundabout;
-        }
-    }
+    // Looks like an intersection: four ways and turn angles are easy to distinguish
+    const auto is_roundabout_intersection = qualifiesAsRoundaboutIntersection(roundabout_nodes) &&
+                                            radius < MAX_ROUNDABOUT_INTERSECTION_RADIUS;
+
+    if (is_roundabout_intersection)
+        return RoundaboutType::RoundaboutIntersection;
+
+    // Not a special case, just a normal roundabout
     return RoundaboutType::Roundabout;
 }
 
@@ -359,12 +372,19 @@ Intersection RoundaboutHandler::handleRoundabouts(const RoundaboutType roundabou
 {
     // detect via radius (get via circle through three vertices)
     NodeID node_v = node_based_graph.GetTarget(via_eid);
+
+    const bool lhs = profile_properties.left_hand_driving;
+    const int step = lhs ? -1 : 1;
+
     if (on_roundabout)
     {
         // Shoule hopefully have only a single exit and continue
         // at least for cars. How about bikes?
-        for (auto &road : intersection)
+        for (std::size_t cnt = 0, idx = lhs ? intersection.size() - 1 : 0;
+             cnt < intersection.size();
+             ++cnt, idx += step)
         {
+            auto &road = intersection[idx];
             auto &turn = road.turn;
             const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
             if (out_data.roundabout)
@@ -397,8 +417,12 @@ Intersection RoundaboutHandler::handleRoundabouts(const RoundaboutType roundabou
         return intersection;
     }
     else
-        for (auto &road : intersection)
+    {
+        for (std::size_t cnt = 0, idx = lhs ? intersection.size() - 1 : 0;
+             cnt < intersection.size();
+             ++cnt, idx += step)
         {
+            auto &road = intersection[idx];
             if (!road.entry_allowed)
                 continue;
             auto &turn = road.turn;
@@ -418,6 +442,7 @@ Intersection RoundaboutHandler::handleRoundabouts(const RoundaboutType roundabou
                     roundabout_type, getTurnDirection(turn.angle));
             }
         }
+    }
     return intersection;
 }
 
diff --git a/src/extractor/guidance/sliproad_handler.cpp b/src/extractor/guidance/sliproad_handler.cpp
new file mode 100644
index 0000000..16a40fa
--- /dev/null
+++ b/src/extractor/guidance/sliproad_handler.cpp
@@ -0,0 +1,282 @@
+#include "extractor/guidance/constants.hpp"
+#include "extractor/guidance/intersection_scenario_three_way.hpp"
+#include "extractor/guidance/sliproad_handler.hpp"
+#include "extractor/guidance/toolkit.hpp"
+
+#include "util/guidance/toolkit.hpp"
+
+#include <limits>
+#include <utility>
+
+#include <boost/assert.hpp>
+
+using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
+using osrm::util::guidance::getTurnDirection;
+using osrm::util::guidance::angularDeviation;
+
+namespace osrm
+{
+namespace extractor
+{
+namespace guidance
+{
+
+SliproadHandler::SliproadHandler(const IntersectionGenerator &intersection_generator,
+                                 const util::NodeBasedDynamicGraph &node_based_graph,
+                                 const std::vector<QueryNode> &node_info_list,
+                                 const util::NameTable &name_table,
+                                 const SuffixTable &street_name_suffix_table)
+    : IntersectionHandler(node_based_graph,
+                          node_info_list,
+                          name_table,
+                          street_name_suffix_table,
+                          intersection_generator)
+{
+}
+
+// included for interface reasons only
+bool SliproadHandler::canProcess(const NodeID /*nid*/,
+                                 const EdgeID /*via_eid*/,
+                                 const Intersection &intersection) const
+{
+    return intersection.size() > 2;
+}
+
+Intersection SliproadHandler::
+operator()(const NodeID, const EdgeID source_edge_id, Intersection intersection) const
+{
+    auto intersection_node_id = node_based_graph.GetTarget(source_edge_id);
+
+    // if there is no turn, there is no sliproad
+    if (intersection.size() <= 2)
+        return intersection;
+
+    const auto findNextIntersectionForRoad =
+        [&](const NodeID at_node, const ConnectedRoad &road, NodeID &output_node) {
+            auto intersection = intersection_generator(at_node, road.turn.eid);
+            auto in_edge = road.turn.eid;
+            // skip over traffic lights
+            // to prevent ending up in an endless loop, we remember all visited nodes. This is
+            // necessary, since merging of roads can actually create enterable loops of degree two
+            std::unordered_set<NodeID> visited_nodes;
+            auto node = at_node;
+            while (intersection.size() == 2 && visited_nodes.count(node) == 0)
+            {
+                visited_nodes.insert(node);
+                node = node_based_graph.GetTarget(in_edge);
+                if (node == at_node)
+                {
+                    // we ended up in a loop without exit
+                    output_node = SPECIAL_NODEID;
+                    intersection.clear();
+                    return intersection;
+                }
+                in_edge = intersection[1].turn.eid;
+                output_node = node_based_graph.GetTarget(in_edge);
+                intersection = intersection_generator(node, in_edge);
+            }
+            if (intersection.size() <= 2)
+            {
+                output_node = SPECIAL_NODEID;
+                intersection.clear();
+            }
+            return intersection;
+        };
+
+    const std::size_t obvious_turn_index = [&]() -> std::size_t {
+        const auto index = findObviousTurn(source_edge_id, intersection);
+        if (index != 0)
+            return index;
+        else if (intersection.size() == 3 &&
+                 intersection[1].turn.instruction.type == TurnType::Fork)
+        {
+            // Forks themselves do not contain a `obvious` turn index. If we look at a fork that has
+            // a one-sided sliproad, however, the non-sliproad can be considered `obvious`. Here we
+            // assume that this could be the case and check for a potential sliproad/non-sliproad
+            // situation.
+            NodeID intersection_node_one = SPECIAL_NODEID, intersection_node_two = SPECIAL_NODEID;
+            const auto intersection_following_index_one = findNextIntersectionForRoad(
+                intersection_node_id, intersection[1], intersection_node_one);
+            const auto intersection_following_index_two = findNextIntersectionForRoad(
+                intersection_node_id, intersection[2], intersection_node_two);
+            // in case of broken roads, we return
+            if (intersection_following_index_one.empty() ||
+                intersection_following_index_two.empty())
+                return 0;
+
+            // In case of loops at the end of the road, we will arrive back at the intersection
+            // itself. If that is the case, the road is obviously not a sliproad.
+
+            // a sliproad has to enter a road without choice
+            const auto couldBeSliproad = [](const Intersection &intersection) {
+                if (intersection.size() != 3)
+                    return false;
+                if ((intersection[1].entry_allowed && intersection[2].entry_allowed) ||
+                    intersection[0].entry_allowed)
+                    return false;
+                return true;
+            };
+
+            if (couldBeSliproad(intersection_following_index_one) &&
+                intersection_node_id != intersection_node_two)
+                return 2;
+            else if (couldBeSliproad(intersection_following_index_two) &&
+                     intersection_node_id != intersection_node_one)
+                return 1;
+            else
+                return 0;
+        }
+        else
+            return 0;
+    }();
+
+    if (obvious_turn_index == 0)
+        return intersection;
+
+    const auto &next_road = intersection[obvious_turn_index];
+
+    const auto linkTest = [this, next_road](const ConnectedRoad &road) {
+        return !node_based_graph.GetEdgeData(road.turn.eid).roundabout && road.entry_allowed &&
+               angularDeviation(road.turn.angle, STRAIGHT_ANGLE) <= 2 * NARROW_TURN_ANGLE &&
+               !hasRoundaboutType(road.turn.instruction) &&
+               angularDeviation(next_road.turn.angle, road.turn.angle) >
+                   std::numeric_limits<double>::epsilon();
+    };
+
+    bool hasNarrow =
+        std::find_if(intersection.begin(), intersection.end(), linkTest) != intersection.end();
+    if (!hasNarrow)
+        return intersection;
+
+    const auto source_edge_data = node_based_graph.GetEdgeData(source_edge_id);
+    // check whether the continue road is valid
+    const auto check_valid = [this, source_edge_data](const ConnectedRoad &road) {
+        const auto road_edge_data = node_based_graph.GetEdgeData(road.turn.eid);
+        // Test to see if the source edge and the one we're looking at are the same road
+        return road_edge_data.road_classification == source_edge_data.road_classification &&
+               road_edge_data.name_id != EMPTY_NAMEID &&
+               road_edge_data.name_id == source_edge_data.name_id && road.entry_allowed;
+    };
+
+    if (!check_valid(next_road))
+        return intersection;
+
+    // Threshold check, if the intersection is too far away, don't bother continuing
+    const auto &next_road_data = node_based_graph.GetEdgeData(next_road.turn.eid);
+    if (next_road_data.distance > MAX_SLIPROAD_THRESHOLD)
+    {
+        return intersection;
+    }
+    auto next_intersection_node = node_based_graph.GetTarget(next_road.turn.eid);
+
+    const auto next_road_next_intersection =
+        findNextIntersectionForRoad(intersection_node_id, next_road, next_intersection_node);
+
+    if (next_road_next_intersection.empty())
+        return intersection;
+
+    // If we are at a traffic loop at the end of a road, don't consider it a sliproad
+    if (intersection_node_id == next_intersection_node)
+        return intersection;
+
+    std::unordered_set<NameID> target_road_names;
+
+    for (const auto &road : next_road_next_intersection)
+    {
+        const auto &target_data = node_based_graph.GetEdgeData(road.turn.eid);
+        target_road_names.insert(target_data.name_id);
+    }
+
+    for (auto &road : intersection)
+    {
+        if (linkTest(road))
+        {
+            EdgeID candidate_in = road.turn.eid;
+            const auto target_intersection = [&](NodeID node) {
+                auto intersection = intersection_generator(node, candidate_in);
+                // skip over traffic lights
+                if (intersection.size() == 2)
+                {
+                    node = node_based_graph.GetTarget(candidate_in);
+                    candidate_in = intersection[1].turn.eid;
+                    intersection = intersection_generator(node, candidate_in);
+                }
+                return intersection;
+            }(intersection_node_id);
+
+            const auto link_data = node_based_graph.GetEdgeData(road.turn.eid);
+            // Check if the road continues here
+            const bool is_through_street =
+                !target_intersection.empty() &&
+                target_intersection.end() !=
+                    std::find_if(target_intersection.begin() + 1,
+                                 target_intersection.end(),
+                                 [this, &link_data](const ConnectedRoad &road) {
+                                     return node_based_graph.GetEdgeData(road.turn.eid).name_id ==
+                                            link_data.name_id;
+                                 });
+
+            // if the sliproad candidate is a through street, we cannot handle it as a sliproad
+            if (is_through_street)
+                continue;
+
+            for (const auto &candidate_road : target_intersection)
+            {
+                const auto &candidate_data = node_based_graph.GetEdgeData(candidate_road.turn.eid);
+                if (target_road_names.count(candidate_data.name_id) > 0)
+                {
+                    if (node_based_graph.GetTarget(candidate_road.turn.eid) ==
+                        next_intersection_node)
+                    {
+                        road.turn.instruction.type = TurnType::Sliproad;
+                        break;
+                    }
+                    else
+                    {
+                        const auto skip_traffic_light_intersection = intersection_generator(
+                            node_based_graph.GetTarget(candidate_in), candidate_road.turn.eid);
+                        if (skip_traffic_light_intersection.size() == 2 &&
+                            node_based_graph.GetTarget(
+                                skip_traffic_light_intersection[1].turn.eid) ==
+                                next_intersection_node)
+                        {
+
+                            road.turn.instruction.type = TurnType::Sliproad;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (next_road.turn.instruction.type == TurnType::Fork)
+    {
+        const auto &next_data = node_based_graph.GetEdgeData(next_road.turn.eid);
+        if (next_data.name_id == source_edge_data.name_id)
+        {
+            if (angularDeviation(next_road.turn.angle, STRAIGHT_ANGLE) < 5)
+                intersection[obvious_turn_index].turn.instruction.type = TurnType::Suppressed;
+            else
+                intersection[obvious_turn_index].turn.instruction.type = TurnType::Continue;
+            intersection[obvious_turn_index].turn.instruction.direction_modifier =
+                getTurnDirection(intersection[obvious_turn_index].turn.angle);
+        }
+        else if (next_data.name_id != EMPTY_NAMEID)
+        {
+            intersection[obvious_turn_index].turn.instruction.type = TurnType::NewName;
+            intersection[obvious_turn_index].turn.instruction.direction_modifier =
+                getTurnDirection(intersection[obvious_turn_index].turn.angle);
+        }
+        else
+        {
+            intersection[obvious_turn_index].turn.instruction.type = TurnType::Suppressed;
+        }
+    }
+
+    return intersection;
+}
+
+} // namespace guidance
+} // namespace extractor
+} // namespace osrm
diff --git a/src/extractor/guidance/turn_analysis.cpp b/src/extractor/guidance/turn_analysis.cpp
index a95ff90..50b496c 100644
--- a/src/extractor/guidance/turn_analysis.cpp
+++ b/src/extractor/guidance/turn_analysis.cpp
@@ -1,6 +1,6 @@
-#include "extractor/guidance/classification_data.hpp"
-#include "extractor/guidance/constants.hpp"
 #include "extractor/guidance/turn_analysis.hpp"
+#include "extractor/guidance/constants.hpp"
+#include "extractor/guidance/road_classification.hpp"
 
 #include "util/coordinate.hpp"
 #include "util/coordinate_calculation.hpp"
@@ -37,7 +37,8 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph,
                            const std::unordered_set<NodeID> &barrier_nodes,
                            const CompressedEdgeContainer &compressed_edge_container,
                            const util::NameTable &name_table,
-                           const SuffixTable &street_name_suffix_table)
+                           const SuffixTable &street_name_suffix_table,
+                           const ProfileProperties &profile_properties)
     : node_based_graph(node_based_graph), intersection_generator(node_based_graph,
                                                                  restriction_map,
                                                                  barrier_nodes,
@@ -47,9 +48,24 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph,
                          node_info_list,
                          compressed_edge_container,
                          name_table,
-                         street_name_suffix_table),
-      motorway_handler(node_based_graph, node_info_list, name_table, street_name_suffix_table),
-      turn_handler(node_based_graph, node_info_list, name_table, street_name_suffix_table)
+                         street_name_suffix_table,
+                         profile_properties,
+                         intersection_generator),
+      motorway_handler(node_based_graph,
+                       node_info_list,
+                       name_table,
+                       street_name_suffix_table,
+                       intersection_generator),
+      turn_handler(node_based_graph,
+                   node_info_list,
+                   name_table,
+                   street_name_suffix_table,
+                   intersection_generator),
+      sliproad_handler(intersection_generator,
+                       node_based_graph,
+                       node_info_list,
+                       name_table,
+                       street_name_suffix_table)
 {
 }
 
@@ -78,17 +94,17 @@ Intersection TurnAnalysis::assignTurnTypes(const NodeID from_nid,
         }
     }
     // Handle sliproads
-    intersection = handleSliproads(via_eid, std::move(intersection));
+    if (sliproad_handler.canProcess(from_nid, via_eid, intersection))
+        intersection = sliproad_handler(from_nid, via_eid, std::move(intersection));
 
     // Turn On Ramps Into Off Ramps, if we come from a motorway-like road
-    if (isMotorwayClass(node_based_graph.GetEdgeData(via_eid).road_classification.road_class))
+    if (node_based_graph.GetEdgeData(via_eid).road_classification.IsMotorwayClass())
     {
         std::for_each(intersection.begin(), intersection.end(), [](ConnectedRoad &road) {
             if (road.turn.instruction.type == TurnType::OnRamp)
                 road.turn.instruction.type = TurnType::OffRamp;
         });
     }
-
     return intersection;
 }
 
@@ -127,135 +143,6 @@ TurnAnalysis::setTurnTypes(const NodeID from_nid, const EdgeID, Intersection int
     return intersection;
 }
 
-// "Sliproads" occur when we've got a link between two roads (MOTORWAY_LINK, etc), but
-// the two roads are *also* directly connected shortly afterwards.
-// In these cases, we tag the turn-type as "sliproad", and then in post-processing
-// we emit a "turn", instead of "take the ramp"+"merge"
-Intersection TurnAnalysis::handleSliproads(const EdgeID source_edge_id,
-                                           Intersection intersection) const
-{
-    auto intersection_node_id = node_based_graph.GetTarget(source_edge_id);
-
-    const auto linkTest = [this](const ConnectedRoad &road) {
-        return !node_based_graph.GetEdgeData(road.turn.eid).roundabout && road.entry_allowed &&
-               angularDeviation(road.turn.angle, STRAIGHT_ANGLE) <= 2 * NARROW_TURN_ANGLE &&
-               !hasRoundaboutType(road.turn.instruction);
-    };
-
-    bool hasNarrow =
-        std::find_if(intersection.begin(), intersection.end(), linkTest) != intersection.end();
-    if (!hasNarrow)
-        return intersection;
-
-    const auto source_edge_data = node_based_graph.GetEdgeData(source_edge_id);
-
-    // Find the continuation of the intersection we're on
-    auto next_road = std::find_if(
-        intersection.begin(),
-        intersection.end(),
-        [this, source_edge_data](const ConnectedRoad &road) {
-            const auto road_edge_data = node_based_graph.GetEdgeData(road.turn.eid);
-            // Test to see if the source edge and the one we're looking at are the same road
-            return road_edge_data.road_classification.road_class ==
-                       source_edge_data.road_classification.road_class &&
-                   road_edge_data.name_id != EMPTY_NAMEID &&
-                   road_edge_data.name_id == source_edge_data.name_id && road.entry_allowed &&
-                   angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE;
-        });
-
-    const bool hasNext = next_road != intersection.end();
-    if (!hasNext)
-    {
-        return intersection;
-    }
-
-    // Threshold check, if the intersection is too far away, don't bother continuing
-    const auto &next_road_data = node_based_graph.GetEdgeData(next_road->turn.eid);
-    if (next_road_data.distance > MAX_SLIPROAD_THRESHOLD)
-    {
-        return intersection;
-    }
-
-    auto next_intersection_node = node_based_graph.GetTarget(next_road->turn.eid);
-
-    const auto next_road_next_intersection = [&]() {
-        auto intersection = intersection_generator(intersection_node_id, next_road->turn.eid);
-        auto in_edge = next_road->turn.eid;
-        //skip over traffic lights
-        if(intersection.size() == 2)
-        {
-            const auto node = node_based_graph.GetTarget(in_edge);
-            in_edge = intersection[1].turn.eid;
-            next_intersection_node = node_based_graph.GetTarget(in_edge);
-            intersection = intersection_generator(node, in_edge);
-        }
-        return intersection;
-    }();
-
-    std::unordered_set<NameID> target_road_names;
-
-    for (const auto &road : next_road_next_intersection)
-    {
-        const auto &target_data = node_based_graph.GetEdgeData(road.turn.eid);
-        target_road_names.insert(target_data.name_id);
-    }
-
-    for (auto &road : intersection)
-    {
-        if (linkTest(road))
-        {
-            const auto target_intersection = [&](NodeID node, EdgeID eid) {
-                auto intersection = intersection_generator(node, eid);
-                //skip over traffic lights
-                if(intersection.size() == 2)
-                {
-                    node = node_based_graph.GetTarget(eid);
-                    eid = intersection[1].turn.eid;
-                    intersection = intersection_generator(node, eid);
-                }
-                return intersection;
-            }(intersection_node_id, road.turn.eid);
-
-            for (const auto &candidate_road : target_intersection)
-            {
-                const auto &candidate_data = node_based_graph.GetEdgeData(candidate_road.turn.eid);
-                if (target_road_names.count(candidate_data.name_id) > 0 &&
-                    node_based_graph.GetTarget(candidate_road.turn.eid) == next_intersection_node)
-                {
-                    road.turn.instruction.type = TurnType::Sliproad;
-                    break;
-                }
-            }
-        }
-    }
-
-    if (next_road->turn.instruction.type == TurnType::Fork)
-    {
-        const auto &next_data = node_based_graph.GetEdgeData(next_road->turn.eid);
-        if (next_data.name_id == source_edge_data.name_id)
-        {
-            if (angularDeviation(next_road->turn.angle, STRAIGHT_ANGLE) < 5)
-                next_road->turn.instruction.type = TurnType::Suppressed;
-            else
-                next_road->turn.instruction.type = TurnType::Continue;
-            next_road->turn.instruction.direction_modifier =
-                getTurnDirection(next_road->turn.angle);
-        }
-        else if (next_data.name_id != EMPTY_NAMEID)
-        {
-            next_road->turn.instruction.type = TurnType::NewName;
-            next_road->turn.instruction.direction_modifier =
-                getTurnDirection(next_road->turn.angle);
-        }
-        else
-        {
-            next_road->turn.instruction.type = TurnType::Suppressed;
-        }
-    }
-
-    return intersection;
-}
-
 const IntersectionGenerator &TurnAnalysis::getGenerator() const { return intersection_generator; }
 
 } // namespace guidance
diff --git a/src/extractor/guidance/turn_discovery.cpp b/src/extractor/guidance/turn_discovery.cpp
index 0bd2728..1f77951 100644
--- a/src/extractor/guidance/turn_discovery.cpp
+++ b/src/extractor/guidance/turn_discovery.cpp
@@ -50,8 +50,10 @@ bool findPreviousIntersection(const NodeID node_v,
     // previous intersection.
     const auto straightmost_at_v_in_reverse =
         findClosestTurn(node_v_reverse_intersection, STRAIGHT_ANGLE);
-    if (angularDeviation(straightmost_at_v_in_reverse->turn.angle, STRAIGHT_ANGLE) >
-        FUZZY_ANGLE_DIFFERENCE)
+
+    // TODO evaluate if narrow turn is the right criterion here... Might be that other angles are
+    // valid
+    if (angularDeviation(straightmost_at_v_in_reverse->turn.angle, STRAIGHT_ANGLE) > GROUP_ANGLE)
         return false;
 
     const auto node_u = node_based_graph.GetTarget(straightmost_at_v_in_reverse->turn.eid);
@@ -66,12 +68,25 @@ bool findPreviousIntersection(const NodeID node_v,
     // if the edge is not traversable, we obviously don't have a previous intersection or couldn't
     // find it.
     if (node_based_graph.GetEdgeData(result_via_edge).reversed)
+    {
+        result_via_edge = SPECIAL_EDGEID;
+        result_node = SPECIAL_NODEID;
         return false;
+    }
 
     result_intersection = turn_analysis.getIntersection(node_u, result_via_edge);
-    const auto check_via_edge = findClosestTurn(result_intersection, STRAIGHT_ANGLE)->turn.eid;
-    if (check_via_edge != via_edge)
+    const auto check_via_edge =
+        result_intersection.end() !=
+        std::find_if(result_intersection.begin(),
+                     result_intersection.end(),
+                     [via_edge](const ConnectedRoad &road) { return road.turn.eid == via_edge; });
+
+    if (!check_via_edge)
+    {
+        result_via_edge = SPECIAL_EDGEID;
+        result_node = SPECIAL_NODEID;
         return false;
+    }
 
     result_intersection =
         turn_analysis.assignTurnTypes(node_u, result_via_edge, std::move(result_intersection));
diff --git a/src/extractor/guidance/turn_handler.cpp b/src/extractor/guidance/turn_handler.cpp
index ebfa044..d867c82 100644
--- a/src/extractor/guidance/turn_handler.cpp
+++ b/src/extractor/guidance/turn_handler.cpp
@@ -1,7 +1,7 @@
+#include "extractor/guidance/turn_handler.hpp"
 #include "extractor/guidance/constants.hpp"
 #include "extractor/guidance/intersection_scenario_three_way.hpp"
 #include "extractor/guidance/toolkit.hpp"
-#include "extractor/guidance/turn_handler.hpp"
 
 #include "util/guidance/toolkit.hpp"
 
@@ -24,37 +24,40 @@ namespace guidance
 TurnHandler::TurnHandler(const util::NodeBasedDynamicGraph &node_based_graph,
                          const std::vector<QueryNode> &node_info_list,
                          const util::NameTable &name_table,
-                         const SuffixTable &street_name_suffix_table)
-    : IntersectionHandler(node_based_graph, node_info_list, name_table, street_name_suffix_table)
+                         const SuffixTable &street_name_suffix_table,
+                         const IntersectionGenerator &intersection_generator)
+    : IntersectionHandler(node_based_graph,
+                          node_info_list,
+                          name_table,
+                          street_name_suffix_table,
+                          intersection_generator)
 {
 }
 
-TurnHandler::~TurnHandler() = default;
-
 bool TurnHandler::canProcess(const NodeID, const EdgeID, const Intersection &) const
 {
     return true;
 }
 
 Intersection TurnHandler::
-operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const
+operator()(const NodeID, const EdgeID via_edge, Intersection intersection) const
 {
     if (intersection.size() == 1)
         return handleOneWayTurn(std::move(intersection));
 
     if (intersection[0].entry_allowed)
     {
-        intersection[0].turn.instruction = {findBasicTurnType(via_eid, intersection[0]),
+        intersection[0].turn.instruction = {findBasicTurnType(via_edge, intersection[0]),
                                             DirectionModifier::UTurn};
     }
 
     if (intersection.size() == 2)
-        return handleTwoWayTurn(via_eid, std::move(intersection));
+        return handleTwoWayTurn(via_edge, std::move(intersection));
 
     if (intersection.size() == 3)
-        return handleThreeWayTurn(via_eid, std::move(intersection));
+        return handleThreeWayTurn(via_edge, std::move(intersection));
 
-    return handleComplexTurn(via_eid, std::move(intersection));
+    return handleComplexTurn(via_edge, std::move(intersection));
 }
 
 Intersection TurnHandler::handleOneWayTurn(Intersection intersection) const
@@ -72,56 +75,59 @@ Intersection TurnHandler::handleTwoWayTurn(const EdgeID via_edge, Intersection i
     return intersection;
 }
 
-Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection intersection) const
+bool TurnHandler::isObviousOfTwo(const EdgeID via_edge,
+                                 const ConnectedRoad &road,
+                                 const ConnectedRoad &other) const
 {
     const auto &in_data = node_based_graph.GetEdgeData(via_edge);
-    const auto &first_data = node_based_graph.GetEdgeData(intersection[1].turn.eid);
-    const auto &second_data = node_based_graph.GetEdgeData(intersection[2].turn.eid);
-    BOOST_ASSERT(intersection[0].turn.angle < 0.001);
-    const auto isObviousOfTwo = [this, in_data](const ConnectedRoad road,
-                                                const ConnectedRoad other) {
-        const auto first_class =
-            node_based_graph.GetEdgeData(road.turn.eid).road_classification.road_class;
-
-        const auto second_class =
-            node_based_graph.GetEdgeData(other.turn.eid).road_classification.road_class;
 
-        const bool is_ramp = isRampClass(first_class);
-        const bool is_obvious_by_road_class =
-            (!is_ramp && (2 * getPriority(first_class) < getPriority(second_class)) &&
-             in_data.road_classification.road_class == first_class) ||
-            (!isLowPriorityRoadClass(first_class) && isLowPriorityRoadClass(second_class));
-
-        if (is_obvious_by_road_class)
-            return true;
-
-        const bool other_is_obvious_by_road_class =
-            (!isRampClass(second_class) &&
-             (2 * getPriority(second_class) < getPriority(first_class)) &&
-             in_data.road_classification.road_class == second_class) ||
-            (!isLowPriorityRoadClass(second_class) && isLowPriorityRoadClass(first_class));
-
-        if (other_is_obvious_by_road_class)
-            return false;
+    const auto &first_data = node_based_graph.GetEdgeData(road.turn.eid);
+    const auto &second_data = node_based_graph.GetEdgeData(other.turn.eid);
+    const auto &first_classification = first_data.road_classification;
+    const auto &second_classification = second_data.road_classification;
+    const bool is_ramp = first_classification.IsRampClass();
+    const bool is_obvious_by_road_class =
+        (!is_ramp &&
+         (2 * first_classification.GetPriority() < second_classification.GetPriority()) &&
+         in_data.road_classification == first_classification) ||
+        (!first_classification.IsLowPriorityRoadClass() &&
+         second_classification.IsLowPriorityRoadClass());
+
+    if (is_obvious_by_road_class)
+        return true;
+
+    const bool other_is_obvious_by_road_class =
+        (!second_classification.IsRampClass() &&
+         (2 * second_classification.GetPriority() < first_classification.GetPriority()) &&
+         in_data.road_classification == second_classification) ||
+        (!second_classification.IsLowPriorityRoadClass() &&
+         first_classification.IsLowPriorityRoadClass());
+
+    if (other_is_obvious_by_road_class)
+        return false;
 
-        const bool turn_is_perfectly_straight = angularDeviation(road.turn.angle, STRAIGHT_ANGLE) <
-                                                std::numeric_limits<double>::epsilon();
+    const bool turn_is_perfectly_straight =
+        angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < std::numeric_limits<double>::epsilon();
 
-        if (turn_is_perfectly_straight && in_data.name_id != EMPTY_NAMEID &&
-            in_data.name_id == node_based_graph.GetEdgeData(road.turn.eid).name_id)
-            return true;
+    if (turn_is_perfectly_straight && in_data.name_id != EMPTY_NAMEID &&
+        in_data.name_id == node_based_graph.GetEdgeData(road.turn.eid).name_id)
+        return true;
 
-        const bool is_much_narrower_than_other =
-            angularDeviation(other.turn.angle, STRAIGHT_ANGLE) /
-                    angularDeviation(road.turn.angle, STRAIGHT_ANGLE) >
-                INCREASES_BY_FOURTY_PERCENT &&
-            angularDeviation(angularDeviation(other.turn.angle, STRAIGHT_ANGLE),
-                             angularDeviation(road.turn.angle, STRAIGHT_ANGLE)) >
-                FUZZY_ANGLE_DIFFERENCE;
+    const bool is_much_narrower_than_other =
+        angularDeviation(other.turn.angle, STRAIGHT_ANGLE) /
+                angularDeviation(road.turn.angle, STRAIGHT_ANGLE) >
+            INCREASES_BY_FOURTY_PERCENT &&
+        angularDeviation(angularDeviation(other.turn.angle, STRAIGHT_ANGLE),
+                         angularDeviation(road.turn.angle, STRAIGHT_ANGLE)) >
+            FUZZY_ANGLE_DIFFERENCE;
 
-        return is_much_narrower_than_other;
-    };
+    return is_much_narrower_than_other;
+}
 
+Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection intersection) const
+{
+    const auto obvious_index = findObviousTurn(via_edge, intersection);
+    BOOST_ASSERT(intersection[0].turn.angle < 0.001);
     /* Two nearly straight turns -> FORK
                OOOOOOO
              /
@@ -129,54 +135,10 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection
              \
                OOOOOOO
      */
-    if (isFork(intersection[0], intersection[1], intersection[2]))
-    {
-        if (intersection[1].entry_allowed && intersection[2].entry_allowed)
-        {
-            const auto left_class = node_based_graph.GetEdgeData(intersection[2].turn.eid)
-                                        .road_classification.road_class;
-            const auto right_class = node_based_graph.GetEdgeData(intersection[1].turn.eid)
-                                         .road_classification.road_class;
-            if (isObviousOfTwo(intersection[1], intersection[2]) &&
-                (second_data.name_id != in_data.name_id ||
-                 first_data.name_id == second_data.name_id))
-            {
-                intersection[1].turn.instruction =
-                    getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]);
-                intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]),
-                                                    DirectionModifier::SlightLeft};
-            }
-            else if (isObviousOfTwo(intersection[2], intersection[1]) &&
-                     (first_data.name_id != in_data.name_id ||
-                      first_data.name_id == second_data.name_id))
-            {
-                intersection[2].turn.instruction =
-                    getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]);
-                intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]),
-                                                    DirectionModifier::SlightRight};
-            }
-            else if (canBeSeenAsFork(left_class, right_class))
-            {
-                assignFork(via_edge, intersection[2], intersection[1]);
-            }
-            else
-            {
-                intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]),
-                                                    DirectionModifier::SlightRight};
-                intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]),
-                                                    DirectionModifier::SlightLeft};
-            }
-        }
-        else
-        {
-            if (intersection[1].entry_allowed)
-                intersection[1].turn.instruction =
-                    getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]);
-            if (intersection[2].entry_allowed)
-                intersection[2].turn.instruction =
-                    getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]);
-        }
-    }
+    const auto fork_range = findFork(via_edge, intersection);
+    if (fork_range.first == 1 && fork_range.second == 2)
+        assignFork(via_edge, intersection[2], intersection[1]);
+
     /*  T Intersection
 
         OOOOOOO T OOOOOOOO
@@ -184,9 +146,7 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection
                 I
                 I
      */
-    else if (isEndOfRoad(intersection[0], intersection[1], intersection[2]) &&
-             !isObviousOfTwo(intersection[1], intersection[2]) &&
-             !isObviousOfTwo(intersection[2], intersection[1]))
+    else if (isEndOfRoad(intersection[0], intersection[1], intersection[2]) && obvious_index == 0)
     {
         if (intersection[1].entry_allowed)
         {
@@ -206,8 +166,7 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection
     }
     else
     {
-        if (isObviousOfTwo(intersection[1], intersection[2]) &&
-            (in_data.name_id != second_data.name_id || first_data.name_id == second_data.name_id))
+        if (obvious_index == 1)
         {
             intersection[1].turn.instruction = getInstructionForObvious(
                 3, via_edge, isThroughStreet(1, intersection), intersection[1]);
@@ -218,8 +177,7 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection
                                                 getTurnDirection(intersection[1].turn.angle)};
         }
 
-        if (isObviousOfTwo(intersection[2], intersection[1]) &&
-            (in_data.name_id != first_data.name_id || first_data.name_id == second_data.name_id))
+        if (obvious_index == 2)
         {
             intersection[2].turn.instruction = getInstructionForObvious(
                 3, via_edge, isThroughStreet(2, intersection), intersection[2]);
@@ -236,7 +194,7 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection
 Intersection TurnHandler::handleComplexTurn(const EdgeID via_edge, Intersection intersection) const
 {
     const std::size_t obvious_index = findObviousTurn(via_edge, intersection);
-    const auto fork_range = findFork(intersection);
+    const auto fork_range = findFork(via_edge, intersection);
     std::size_t straightmost_turn = 0;
     double straightmost_deviation = 180;
     for (std::size_t i = 0; i < intersection.size(); ++i)
@@ -300,13 +258,13 @@ Intersection TurnHandler::handleComplexTurn(const EdgeID via_edge, Intersection
         {
             auto &left = intersection[fork_range.second];
             auto &right = intersection[fork_range.first];
-            const auto left_class =
-                node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class;
-            const auto right_class =
-                node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class;
-            if (canBeSeenAsFork(left_class, right_class))
+            const auto left_classification =
+                node_based_graph.GetEdgeData(left.turn.eid).road_classification;
+            const auto right_classification =
+                node_based_graph.GetEdgeData(right.turn.eid).road_classification;
+            if (canBeSeenAsFork(left_classification, right_classification))
                 assignFork(via_edge, left, right);
-            else if (getPriority(left_class) > getPriority(right_class))
+            else if (left_classification.GetPriority() > right_classification.GetPriority())
             {
                 right.turn.instruction =
                     getInstructionForObvious(intersection.size(), via_edge, false, right);
@@ -358,119 +316,6 @@ Intersection TurnHandler::handleComplexTurn(const EdgeID via_edge, Intersection
     return intersection;
 }
 
-std::size_t TurnHandler::findObviousTurn(const EdgeID via_edge,
-                                         const Intersection &intersection) const
-{
-    // no obvious road
-    if (intersection.size() == 1)
-        return 0;
-
-    // a single non u-turn is obvious
-    if (intersection.size() == 2)
-        return 1;
-
-    // at least three roads
-    std::size_t best = 0;
-    double best_deviation = 180;
-
-    std::size_t best_continue = 0;
-    double best_continue_deviation = 180;
-
-    const EdgeData &in_data = node_based_graph.GetEdgeData(via_edge);
-    const auto in_class = in_data.road_classification.road_class;
-    for (std::size_t i = 1; i < intersection.size(); ++i)
-    {
-        const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE);
-        if (intersection[i].entry_allowed && deviation < best_deviation)
-        {
-            best_deviation = deviation;
-            best = i;
-        }
-
-        const auto out_data = node_based_graph.GetEdgeData(intersection[i].turn.eid);
-        auto continue_class = node_based_graph.GetEdgeData(intersection[best_continue].turn.eid)
-                                  .road_classification.road_class;
-        if (intersection[i].entry_allowed && out_data.name_id == in_data.name_id &&
-            (best_continue == 0 || (continue_class > out_data.road_classification.road_class &&
-                                    in_class != continue_class) ||
-             (deviation < best_continue_deviation &&
-              (out_data.road_classification.road_class == continue_class ||
-               in_class == out_data.road_classification.road_class))))
-        {
-            best_continue_deviation = deviation;
-            best_continue = i;
-        }
-    }
-
-    if (best == 0)
-        return 0;
-
-    if (best_deviation >= 2 * NARROW_TURN_ANGLE)
-        return 0;
-
-    // has no obvious continued road
-    if (best_continue == 0 || best_continue_deviation >= 2 * NARROW_TURN_ANGLE ||
-        (node_based_graph.GetEdgeData(intersection[best_continue].turn.eid)
-                 .road_classification.road_class ==
-             node_based_graph.GetEdgeData(intersection[best].turn.eid)
-                 .road_classification.road_class &&
-         std::abs(best_continue_deviation) > 1 && best_deviation / best_continue_deviation < 0.75))
-    {
-        // Find left/right deviation
-        const double left_deviation = angularDeviation(
-            intersection[(best + 1) % intersection.size()].turn.angle, STRAIGHT_ANGLE);
-        const double right_deviation =
-            angularDeviation(intersection[best - 1].turn.angle, STRAIGHT_ANGLE);
-
-        if (best_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
-            std::min(left_deviation, right_deviation) > FUZZY_ANGLE_DIFFERENCE)
-            return best;
-
-        // other narrow turns?
-        if (angularDeviation(intersection[best - 1].turn.angle, STRAIGHT_ANGLE) <=
-            FUZZY_ANGLE_DIFFERENCE)
-            return 0;
-        if (angularDeviation(intersection[(best + 1) % intersection.size()].turn.angle,
-                             STRAIGHT_ANGLE) <= FUZZY_ANGLE_DIFFERENCE)
-            return 0;
-
-        // Well distinct turn that is nearly straight
-        if ((left_deviation / best_deviation >= DISTINCTION_RATIO ||
-             (left_deviation > best_deviation &&
-              !intersection[(best + 1) % intersection.size()].entry_allowed)) &&
-            (right_deviation / best_deviation >= DISTINCTION_RATIO ||
-             (right_deviation > best_deviation && !intersection[best - 1].entry_allowed)))
-        {
-            return best;
-        }
-    }
-    else
-    {
-        const double deviation =
-            angularDeviation(intersection[best_continue].turn.angle, STRAIGHT_ANGLE);
-        const auto &continue_data =
-            node_based_graph.GetEdgeData(intersection[best_continue].turn.eid);
-        if (std::abs(deviation) < 1)
-            return best_continue;
-
-        // check if any other similar best continues exist
-        for (std::size_t i = 1; i < intersection.size(); ++i)
-        {
-            if (i == best_continue || !intersection[i].entry_allowed)
-                continue;
-
-            if (angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE) / deviation < 1.1 &&
-                continue_data.road_classification.road_class ==
-                    node_based_graph.GetEdgeData(intersection[i].turn.eid)
-                        .road_classification.road_class)
-                return 0;
-        }
-        return best_continue; // no obvious turn
-    }
-
-    return 0;
-}
-
 // Assignment of left turns hands of to right turns.
 // To do so, we mirror every road segment and reverse the order.
 // After the mirror and reversal / we assign right turns and
@@ -631,7 +476,8 @@ Intersection TurnHandler::assignRightTurns(const EdgeID via_edge,
     return intersection;
 }
 
-std::pair<std::size_t, std::size_t> TurnHandler::findFork(const Intersection &intersection) const
+std::pair<std::size_t, std::size_t> TurnHandler::findFork(const EdgeID via_edge,
+                                                          const Intersection &intersection) const
 {
 
     std::size_t best = 0;
@@ -650,39 +496,66 @@ std::pair<std::size_t, std::size_t> TurnHandler::findFork(const Intersection &in
     if (best_deviation <= NARROW_TURN_ANGLE)
     {
         std::size_t left = best, right = best;
-        if (intersection[best].turn.angle >= 180)
-        {
-            // due to best > 1, we can safely decrement right
-            --right;
-            if (angularDeviation(intersection[right].turn.angle, STRAIGHT_ANGLE) >
-                NARROW_TURN_ANGLE)
-                return std::make_pair(std::size_t{0}, std::size_t{0});
-        }
-        else
-        {
-            ++left;
-            if (left >= intersection.size() ||
-                angularDeviation(intersection[left].turn.angle, STRAIGHT_ANGLE) > NARROW_TURN_ANGLE)
-                return std::make_pair(std::size_t{0}, std::size_t{0});
-        }
         while (left + 1 < intersection.size() &&
-               angularDeviation(intersection[left].turn.angle, intersection[left + 1].turn.angle) <
-                   NARROW_TURN_ANGLE &&
-               angularDeviation(intersection[left].turn.angle, STRAIGHT_ANGLE) <= GROUP_ANGLE)
+               (angularDeviation(intersection[left + 1].turn.angle, STRAIGHT_ANGLE) <=
+                    NARROW_TURN_ANGLE ||
+                (angularDeviation(intersection[left].turn.angle,
+                                  intersection[left + 1].turn.angle) <= NARROW_TURN_ANGLE &&
+                 angularDeviation(intersection[left].turn.angle, STRAIGHT_ANGLE) <= GROUP_ANGLE)))
             ++left;
-        while (right > 1 &&
-               angularDeviation(intersection[right].turn.angle,
-                                intersection[right - 1].turn.angle) < NARROW_TURN_ANGLE &&
-               angularDeviation(intersection[right - 1].turn.angle, STRAIGHT_ANGLE) <= GROUP_ANGLE)
+        while (
+            right > 1 &&
+            (angularDeviation(intersection[right - 1].turn.angle, STRAIGHT_ANGLE) <=
+                 NARROW_TURN_ANGLE ||
+             (angularDeviation(intersection[right].turn.angle, intersection[right - 1].turn.angle) <
+                  NARROW_TURN_ANGLE &&
+              angularDeviation(intersection[right - 1].turn.angle, STRAIGHT_ANGLE) <= GROUP_ANGLE)))
             --right;
 
-        // TODO check whether 2*NARROW_TURN is too large
-        if (0 < right && right < left &&
+        if (left == right)
+            return std::make_pair(std::size_t{0}, std::size_t{0});
+
+        const bool valid_indices = 0 < right && right < left;
+        const bool separated_at_left_side =
             angularDeviation(intersection[left].turn.angle,
                              intersection[(left + 1) % intersection.size()].turn.angle) >=
-                GROUP_ANGLE &&
+            GROUP_ANGLE;
+        const bool separated_at_right_side =
+            right > 0 &&
             angularDeviation(intersection[right].turn.angle, intersection[right - 1].turn.angle) >=
-                GROUP_ANGLE)
+                GROUP_ANGLE;
+
+        const bool not_more_than_three = (left - right) <= 2;
+        const bool has_obvious = [&]() {
+            if (left - right == 1)
+            {
+                return isObviousOfTwo(via_edge, intersection[left], intersection[right]) ||
+                       isObviousOfTwo(via_edge, intersection[right], intersection[left]);
+            }
+            else if (left - right == 2)
+            {
+                return isObviousOfTwo(via_edge, intersection[right + 1], intersection[right]) ||
+                       isObviousOfTwo(via_edge, intersection[right], intersection[right + 1]) ||
+                       isObviousOfTwo(via_edge, intersection[left], intersection[right + 1]) ||
+                       isObviousOfTwo(via_edge, intersection[right + 1], intersection[left]);
+            }
+            return false;
+        }();
+
+        const bool has_compatible_classes = [&]() {
+            const bool ramp_class = node_based_graph.GetEdgeData(intersection[right].turn.eid)
+                                        .road_classification.IsLinkClass();
+            for (std::size_t index = right + 1; index <= left; ++index)
+                if (ramp_class !=
+                    node_based_graph.GetEdgeData(intersection[index].turn.eid)
+                        .road_classification.IsLinkClass())
+                    return false;
+            return true;
+        }();
+
+        // TODO check whether 2*NARROW_TURN is too large
+        if (valid_indices && separated_at_left_side && separated_at_right_side &&
+            not_more_than_three && !has_obvious && has_compatible_classes)
             return std::make_pair(right, left);
     }
     return std::make_pair(std::size_t{0}, std::size_t{0});
@@ -709,13 +582,13 @@ void TurnHandler::handleDistinctConflict(const EdgeID via_edge,
         getTurnDirection(left.turn.angle) == DirectionModifier::SlightLeft ||
         getTurnDirection(right.turn.angle) == DirectionModifier::SlightRight)
     {
-        const auto left_class =
-            node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class;
-        const auto right_class =
-            node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class;
-        if (canBeSeenAsFork(left_class, right_class))
+        const auto left_classification =
+            node_based_graph.GetEdgeData(left.turn.eid).road_classification;
+        const auto right_classification =
+            node_based_graph.GetEdgeData(right.turn.eid).road_classification;
+        if (canBeSeenAsFork(left_classification, right_classification))
             assignFork(via_edge, left, right);
-        else if (getPriority(left_class) > getPriority(right_class))
+        else if (left_classification.GetPriority() > right_classification.GetPriority())
         {
             // FIXME this should possibly know about the actual roads?
             // here we don't know about the intersection size. To be on the save side,
diff --git a/src/extractor/guidance/turn_lane_augmentation.cpp b/src/extractor/guidance/turn_lane_augmentation.cpp
index 28eda4f..3939489 100644
--- a/src/extractor/guidance/turn_lane_augmentation.cpp
+++ b/src/extractor/guidance/turn_lane_augmentation.cpp
@@ -116,7 +116,8 @@ LaneDataVector augmentMultiple(const std::size_t none_index,
             lane_data.push_back({tag_by_modifier[intersection[intersection_index]
                                                      .turn.instruction.direction_modifier],
                                  lane_data[none_index].from,
-                                 lane_data[none_index].to});
+                                 lane_data[none_index].to,
+                                 false});
         }
     }
     lane_data.erase(lane_data.begin() + none_index);
@@ -268,7 +269,8 @@ LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data,
         ((intersection[0].entry_allowed && lane_data.back().tag != TurnLaneType::uturn) ? 1 : 0);
 
     // TODO check for impossible turns to see whether the turn lane is at the correct place
-    const std::size_t none_index = std::distance(lane_data.begin(), findTag(TurnLaneType::none, lane_data));
+    const std::size_t none_index =
+        std::distance(lane_data.begin(), findTag(TurnLaneType::none, lane_data));
     BOOST_ASSERT(none_index != lane_data.size());
     // we have to create multiple turns
     if (connection_count > lane_data.size())
@@ -282,16 +284,16 @@ LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data,
         // a pgerequisite is simple turns. Larger differences should not end up here
         // an additional line at the side is only reasonable if it is targeting public
         // service vehicles. Otherwise, we should not have it
-        //
-        // TODO(mokob): #2730 have a look please
-        // BOOST_ASSERT(connection_count + 1 == lane_data.size());
-        //
-        if (connection_count + 1 != lane_data.size())
+        if (connection_count + 1 == lane_data.size())
         {
-            goto these_intersections_are_clearly_broken_at_the_moment;
+            lane_data = mergeNoneTag(none_index, std::move(lane_data));
+        }
+        else
+        {
+            // This represents a currently unhandled case. It should not even get here, but to be
+            // sure we return nevertheless.
+            return lane_data;
         }
-
-        lane_data = mergeNoneTag(none_index, std::move(lane_data));
     }
     // we have to rename and possibly augment existing ones. The pure count remains the
     // same.
@@ -300,8 +302,6 @@ LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data,
         lane_data = handleRenamingSituations(none_index, std::move(lane_data), intersection);
     }
 
-these_intersections_are_clearly_broken_at_the_moment:
-
     // finally make sure we are still sorted
     std::sort(lane_data.begin(), lane_data.end());
     return lane_data;
diff --git a/src/extractor/guidance/turn_lane_data.cpp b/src/extractor/guidance/turn_lane_data.cpp
index 84a1c4f..11c287d 100644
--- a/src/extractor/guidance/turn_lane_data.cpp
+++ b/src/extractor/guidance/turn_lane_data.cpp
@@ -30,6 +30,7 @@ bool TurnLaneData::operator<(const TurnLaneData &other) const
     if (to > other.to)
         return false;
 
+    // the suppress-assignment flag is ignored, since it does not influence the order
     const constexpr TurnLaneType::Mask tag_by_modifier[] = {TurnLaneType::sharp_right,
                                                             TurnLaneType::right,
                                                             TurnLaneType::slight_right,
@@ -64,10 +65,12 @@ bool TurnLaneData::operator<(const TurnLaneData &other) const
            std::find(tag_by_modifier, tag_by_modifier + 8, other.tag);
 }
 
-LaneDataVector laneDataFromDescription(const TurnLaneDescription &turn_lane_description)
+LaneDataVector laneDataFromDescription(TurnLaneDescription turn_lane_description)
 {
     typedef std::unordered_map<TurnLaneType::Mask, std::pair<LaneID, LaneID>> LaneMap;
+    // TODO need to handle cases that have none-in between two identical values
     const auto num_lanes = boost::numeric_cast<LaneID>(turn_lane_description.size());
+
     const auto setLaneData = [&](
         LaneMap &map, TurnLaneType::Mask full_mask, const LaneID current_lane) {
         const auto isSet = [&](const TurnLaneType::Mask test_mask) -> bool {
@@ -104,9 +107,7 @@ LaneDataVector laneDataFromDescription(const TurnLaneDescription &turn_lane_desc
     // transform the map into the lane data vector
     LaneDataVector lane_data;
     for (const auto tag : lane_map)
-    {
-        lane_data.push_back({tag.first, tag.second.first, tag.second.second});
-    }
+        lane_data.push_back({tag.first, tag.second.first, tag.second.second, false});
 
     std::sort(lane_data.begin(), lane_data.end());
 
@@ -116,6 +117,13 @@ LaneDataVector laneDataFromDescription(const TurnLaneDescription &turn_lane_desc
         // which is invalid
         for (std::size_t index = 1; index < lane_data.size(); ++index)
         {
+            // u-turn located somewhere in the middle
+            // Right now we can only handle u-turns at the sides. If we find a u-turn somewhere in
+            // the middle of the tags, we abort the handling right here.
+            if (index + 1 < lane_data.size() &&
+                ((lane_data[index].tag & TurnLaneType::uturn) != TurnLaneType::empty))
+                return false;
+
             if (lane_data[index - 1].to > lane_data[index].from)
                 return false;
         }
diff --git a/src/extractor/guidance/turn_lane_handler.cpp b/src/extractor/guidance/turn_lane_handler.cpp
index cf68e91..c0f12b2 100644
--- a/src/extractor/guidance/turn_lane_handler.cpp
+++ b/src/extractor/guidance/turn_lane_handler.cpp
@@ -1,11 +1,12 @@
+#include "extractor/guidance/turn_lane_handler.hpp"
 #include "extractor/guidance/constants.hpp"
 #include "extractor/guidance/turn_discovery.hpp"
 #include "extractor/guidance/turn_lane_augmentation.hpp"
-#include "extractor/guidance/turn_lane_handler.hpp"
 #include "extractor/guidance/turn_lane_matcher.hpp"
 #include "util/simple_logger.hpp"
 #include "util/typedefs.hpp"
 
+#include <cstddef>
 #include <cstdint>
 
 #include <boost/algorithm/string/predicate.hpp>
@@ -31,13 +32,23 @@ std::size_t getNumberOfTurns(const Intersection &intersection)
 } // namespace
 
 TurnLaneHandler::TurnLaneHandler(const util::NodeBasedDynamicGraph &node_based_graph,
-                                 const std::vector<std::uint32_t> &turn_lane_offsets,
-                                 const std::vector<TurnLaneType::Mask> &turn_lane_masks,
+                                 std::vector<std::uint32_t> &turn_lane_offsets,
+                                 std::vector<TurnLaneType::Mask> &turn_lane_masks,
+                                 LaneDescriptionMap &lane_description_map,
                                  const std::vector<QueryNode> &node_info_list,
-                                 const TurnAnalysis &turn_analysis)
+                                 const TurnAnalysis &turn_analysis,
+                                 LaneDataIdMap &id_map)
     : node_based_graph(node_based_graph), turn_lane_offsets(turn_lane_offsets),
-      turn_lane_masks(turn_lane_masks), node_info_list(node_info_list), turn_analysis(turn_analysis)
+      turn_lane_masks(turn_lane_masks), lane_description_map(lane_description_map),
+      node_info_list(node_info_list), turn_analysis(turn_analysis), id_map(id_map)
 {
+    count_handled = count_called = 0;
+}
+
+TurnLaneHandler::~TurnLaneHandler()
+{
+    std::cout << "Handled: " << count_handled << " of " << count_called
+              << " lanes: " << (double)(count_handled * 100) / (count_called) << " %." << std::endl;
 }
 
 /*
@@ -58,52 +69,177 @@ TurnLaneHandler::TurnLaneHandler(const util::NodeBasedDynamicGraph &node_based_g
     assignment onto the turns.
     For example: (130, turn slight right), (180, ramp straight), (320, turn sharp left).
  */
-Intersection TurnLaneHandler::assignTurnLanes(const NodeID at,
-                                              const EdgeID via_edge,
-                                              Intersection intersection,
-                                              LaneDataIdMap &id_map) const
+Intersection
+TurnLaneHandler::assignTurnLanes(const NodeID at, const EdgeID via_edge, Intersection intersection)
 {
-    //if only a uturn exists, there is nothing we can do
-    if( intersection.size() == 1 )
+    // if only a uturn exists, there is nothing we can do
+    if (intersection.size() == 1)
         return intersection;
 
-    const auto &data = node_based_graph.GetEdgeData(via_edge);
-    // Extract a lane description for the ID
-
-    const auto turn_lane_description =
-        data.lane_description_id != INVALID_LANE_DESCRIPTIONID
-            ? TurnLaneDescription(
-                  turn_lane_masks.begin() + turn_lane_offsets[data.lane_description_id],
-                  turn_lane_masks.begin() + turn_lane_offsets[data.lane_description_id + 1])
-            : TurnLaneDescription();
-
-    BOOST_ASSERT( turn_lane_description.empty() || turn_lane_description.size() == (turn_lane_offsets[data.lane_description_id+1] - turn_lane_offsets[data.lane_description_id]));
+    // A list of output parameters to be filled in during deduceScenario.
+    // Data for the current intersection
+    LaneDescriptionID lane_description_id = INVALID_LANE_DESCRIPTIONID;
+    LaneDataVector lane_data;
+    // Data for the previous intersection
+    NodeID previous_node = SPECIAL_NODEID;
+    EdgeID previous_via_edge = SPECIAL_EDGEID;
+    Intersection previous_intersection;
+    LaneDataVector previous_lane_data;
+    LaneDescriptionID previous_description_id = INVALID_LANE_DESCRIPTIONID;
+
+    const auto scenario = deduceScenario(at,
+                                         via_edge,
+                                         intersection,
+                                         lane_description_id,
+                                         lane_data,
+                                         previous_node,
+                                         previous_via_edge,
+                                         previous_intersection,
+                                         previous_lane_data,
+                                         previous_description_id);
+
+    if (scenario != TurnLaneScenario::NONE)
+        count_called++;
+
+    switch (scenario)
+    {
+    // A turn based on current lane data
+    case TurnLaneScenario::SIMPLE:
+    case TurnLaneScenario::PARTITION_LOCAL:
+        lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
+        return simpleMatchTuplesToTurns(std::move(intersection), lane_data, lane_description_id);
 
-    // going straight, due to traffic signals, we can have uncompressed geometry
-    if (intersection.size() == 2 &&
-        ((data.lane_description_id != INVALID_LANE_DESCRIPTIONID &&
-          data.lane_description_id ==
-              node_based_graph.GetEdgeData(intersection[1].turn.eid).lane_description_id) ||
-         angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE))
+    // Cases operating on data carried over from a previous lane
+    case TurnLaneScenario::SIMPLE_PREVIOUS:
+    case TurnLaneScenario::PARTITION_PREVIOUS:
+        previous_lane_data =
+            handleNoneValueAtSimpleTurn(std::move(previous_lane_data), intersection);
+        return simpleMatchTuplesToTurns(
+            std::move(intersection), previous_lane_data, previous_description_id);
+
+    // Sliproads-turns that are to be handled as a single entity
+    case TurnLaneScenario::SLIPROAD:
+        return handleSliproadTurn(std::move(intersection),
+                                  lane_description_id,
+                                  std::move(lane_data),
+                                  previous_intersection);
+    case TurnLaneScenario::MERGE:
+        return intersection;
+    default:
+        // All different values that we cannot handle. For some me might want to print debug output
+        // later on, when the handling is actually improved to work in close to all cases.
+        // case TurnLaneScenario::UNKNOWN:
+        // case TurnLaneScenario::NONE:
+        // case TurnLaneScenario::INVALID:
         return intersection;
+    }
+}
 
-    auto lane_data = laneDataFromDescription(turn_lane_description);
+// Find out which scenario we have to handle
+TurnLaneScenario TurnLaneHandler::deduceScenario(const NodeID at,
+                                                 const EdgeID via_edge,
+                                                 const Intersection &intersection,
+                                                 // Output Variables
+                                                 LaneDescriptionID &lane_description_id,
+                                                 LaneDataVector &lane_data,
+                                                 NodeID &previous_node,
+                                                 EdgeID &previous_via_edge,
+                                                 Intersection &previous_intersection,
+                                                 LaneDataVector &previous_lane_data,
+                                                 LaneDescriptionID &previous_description_id)
+{
+    // if only a uturn exists, there is nothing we can do
+    if (intersection.size() == 1)
+        return TurnLaneScenario::NONE;
+
+    extractLaneData(via_edge, lane_description_id, lane_data);
+
+    // traffic lights are not compressed during our preprocessing. Due to this *shortcoming*, we can
+    // get to the following situation:
+    //
+    //         d
+    // a - b - c
+    //         e
+    //
+    // with a traffic light at b and a-b as well as b-c offering the same turn lanes.
+    // In such a situation, we don't need to handle the lanes at a-b, since we will get the same
+    // information at b-c, where the actual turns are taking place.
+    const bool is_going_straight_and_turns_continue =
+        (intersection.size() == 2 &&
+         ((lane_description_id != INVALID_LANE_DESCRIPTIONID &&
+           lane_description_id ==
+               node_based_graph.GetEdgeData(intersection[1].turn.eid).lane_description_id) ||
+          angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE));
+
+    if (is_going_straight_and_turns_continue)
+        return TurnLaneScenario::NONE;
 
     // if we see an invalid conversion, we stop immediately
-    if (!turn_lane_description.empty() && lane_data.empty())
-        return intersection;
+    if (lane_description_id != INVALID_LANE_DESCRIPTIONID && lane_data.empty())
+        return TurnLaneScenario::INVALID;
 
     // might be reasonable to handle multiple turns, if we know of a sequence of lanes
     // e.g. one direction per lane, if three lanes and right, through, left available
-    if (!turn_lane_description.empty() && lane_data.size() == 1 &&
+    if (lane_description_id != INVALID_LANE_DESCRIPTIONID && lane_data.size() == 1 &&
         lane_data[0].tag == TurnLaneType::none)
-        return intersection;
+        return TurnLaneScenario::NONE;
+
+    // Due to sliproads, we might need access to the previous intersection at this point already;
+    previous_node = SPECIAL_NODEID;
+    previous_via_edge = SPECIAL_EDGEID;
+    if (findPreviousIntersection(at,
+                                 via_edge,
+                                 intersection,
+                                 turn_analysis,
+                                 node_based_graph,
+                                 previous_node,
+                                 previous_via_edge,
+                                 previous_intersection))
+    {
+        extractLaneData(previous_via_edge, previous_description_id, previous_lane_data);
+        for (std::size_t road_index = 0; road_index < previous_intersection.size(); ++road_index)
+        {
+            const auto &road = previous_intersection[road_index];
+            // in case of a sliproad that is connected to road of simlar angle, we handle the
+            // turn as a combined turn
+            if (road.turn.instruction.type == TurnType::Sliproad)
+            {
+                if (via_edge == road.turn.eid)
+                    return TurnLaneScenario::SLIPROAD;
+
+                const auto &closest_road = [&]() {
+                    if (road_index + 1 == previous_intersection.size())
+                    {
+                        BOOST_ASSERT(road_index > 1);
+                        return previous_intersection[road_index - 1];
+                    }
+                    else if (road_index == 1)
+                    {
+                        BOOST_ASSERT(road_index + 1 < previous_intersection.size());
+                        return previous_intersection[road_index + 1];
+                    }
+                    else if (angularDeviation(road.turn.angle,
+                                              previous_intersection.at(road_index - 1).turn.angle) <
+                             angularDeviation(road.turn.angle,
+                                              previous_intersection.at(road_index + 1).turn.angle))
+                        return previous_intersection[road_index - 1];
+                    else
+                        return previous_intersection[road_index + 1];
+                }();
+
+                if (via_edge == closest_road.turn.eid)
+                    return TurnLaneScenario::SLIPROAD;
+            }
+        }
+    }
 
     const std::size_t possible_entries = getNumberOfTurns(intersection);
 
     // merge does not justify an instruction
     const bool has_merge_lane =
         hasTag(TurnLaneType::merge_to_left | TurnLaneType::merge_to_right, lane_data);
+    if (has_merge_lane)
+        return TurnLaneScenario::MERGE;
 
     // Dead end streets that don't have any left-tag. This can happen due to the fallbacks for
     // broken data/barriers.
@@ -113,30 +249,48 @@ Intersection TurnLaneHandler::assignTurnLanes(const NodeID at,
                                                 lane_data) &&
                                         lane_data.size() + 1 == possible_entries);
 
-    if (has_merge_lane || has_non_usable_u_turn)
-        return intersection;
+    if (has_non_usable_u_turn)
+        return TurnLaneScenario::INVALID;
 
-    if (!lane_data.empty() && canMatchTrivially(intersection, lane_data) &&
+    // check if a u-turn is allowed (for some reason) and is missing from the list of tags (u-turn
+    // is often allowed from the `left` lane without an additional indication dedicated to u-turns).
+    const bool is_missing_valid_u_turn =
+        !lane_data.empty() && canMatchTrivially(intersection, lane_data) &&
         lane_data.size() !=
             static_cast<std::size_t>((
                 !hasTag(TurnLaneType::uturn, lane_data) && intersection[0].entry_allowed ? 1 : 0)) +
                 possible_entries &&
-        intersection[0].entry_allowed && !hasTag(TurnLaneType::none, lane_data))
-        lane_data.push_back({TurnLaneType::uturn, lane_data.back().to, lane_data.back().to});
+        intersection[0].entry_allowed;
+
+    // FIXME the lane to add depends on the side of driving/u-turn rules in the country
+    if (!lane_data.empty() && canMatchTrivially(intersection, lane_data) &&
+        is_missing_valid_u_turn && !hasTag(TurnLaneType::none, lane_data))
+        lane_data.push_back({TurnLaneType::uturn, lane_data.back().to, lane_data.back().to, false});
 
     bool is_simple = isSimpleIntersection(lane_data, intersection);
-    // simple intersections can be assigned directly
+
     if (is_simple)
-    {
-        lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
-        return simpleMatchTuplesToTurns(
-            std::move(intersection), lane_data, data.lane_description_id, id_map);
-    }
-    // if the intersection is not simple but we have lane data, we check for intersections with
-    // middle islands. We have two cases. The first one is providing lane data on the current
-    // segment and we only need to consider the part of the current segment. In this case we
-    // partition the data and only consider the first part.
-    else if (!lane_data.empty())
+        return TurnLaneScenario::SIMPLE;
+
+    // In case of intersections that don't offer all turns right away, we have to account for
+    // *delayed* turns. Consider the following example:
+    //
+    //         e
+    // a - b - c - f
+    //     d
+    //
+    // With lanes on a-b indicating: | left | through | right |.
+    // While right obviously refers to a-b-d, through and left refer to a-b-c-f and a-b-c-e
+    // respectively. While we are at a-b, we have to consider the right turn only.
+    // The turn a-b-c gets assigned the lanes of both *left* and *through*.
+    // At b-c, we get access to either a new set of lanes, or -- via the previous intersection
+    // -- to
+    // the second part of | left | through | right |. Lane anticipation can then deduce which
+    // lanes correspond to what and suppress unnecessary instructions.
+    //
+    // For our initial case, we consider only the turns that are available at the current
+    // location, which are given by partitioning the lane data and selecting the first part.
+    if (!lane_data.empty())
     {
         if (lane_data.size() >= possible_entries)
         {
@@ -148,115 +302,74 @@ Intersection TurnLaneHandler::assignTurnLanes(const NodeID at,
             // check if we were successfull in trimming
             if (lane_data.size() == possible_entries &&
                 isSimpleIntersection(lane_data, intersection))
-            {
-                lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
-                return simpleMatchTuplesToTurns(
-                    std::move(intersection), lane_data, data.lane_description_id, id_map);
-            }
+                return TurnLaneScenario::PARTITION_LOCAL;
         }
-    }
-    // The second part does not provide lane data on the current segment, but on the segment prior
-    // to the turn. We try to partition the data and only consider the second part.
-    else if (turn_lane_description.empty())
-    {
-        // acquire the lane data of a previous segment and, if possible, use it for the current
-        // intersection.
-        return handleTurnAtPreviousIntersection(at, via_edge, std::move(intersection), id_map);
+        // If partitioning doesn't solve the problem, we don't know how to handle it right now
+        return TurnLaneScenario::UNKNOWN;
     }
 
-    return intersection;
-}
+    if (lane_description_id != INVALID_LANE_DESCRIPTIONID)
+        return TurnLaneScenario::UNKNOWN;
 
-// At segregated intersections, turn lanes will often only be specified up until the first turn. To
-// actually take the turn, we need to look back to the edge we drove onto the intersection with.
-Intersection TurnLaneHandler::handleTurnAtPreviousIntersection(const NodeID at,
-                                                               const EdgeID via_edge,
-                                                               Intersection intersection,
-                                                               LaneDataIdMap &id_map) const
-{
-    NodeID previous_node = SPECIAL_NODEID;
-    Intersection previous_intersection;
-    EdgeID previous_id = SPECIAL_EDGEID;
-    LaneDataVector lane_data;
+    // acquire the lane data of a previous segment and, if possible, use it for the current
+    // intersection.
+    if (previous_via_edge == SPECIAL_EDGEID)
+        return TurnLaneScenario::NONE;
 
-    // Get the previous lane string. We only accept strings that stem from a not-simple intersection
-    // and are not empty.
-    const auto previous_lane_description = [&]() -> TurnLaneDescription {
-        if (!findPreviousIntersection(at,
-                                      via_edge,
-                                      intersection,
-                                      turn_analysis,
-                                      node_based_graph,
-                                      previous_node,
-                                      previous_id,
-                                      previous_intersection))
-            return {};
-
-        BOOST_ASSERT(previous_id != SPECIAL_EDGEID);
-
-        const auto &previous_edge_data = node_based_graph.GetEdgeData(previous_id);
-        // TODO access correct data
-        const auto previous_description =
-            previous_edge_data.lane_description_id != INVALID_LANE_DESCRIPTIONID
-                ? TurnLaneDescription(
-                      turn_lane_masks.begin() +
-                          turn_lane_offsets[previous_edge_data.lane_description_id],
-                      turn_lane_masks.begin() +
-                          turn_lane_offsets[previous_edge_data.lane_description_id + 1])
-                : TurnLaneDescription();
-        if (previous_description.empty())
-            return previous_description;
-
-        previous_intersection = turn_analysis.assignTurnTypes(
-            previous_node, previous_id, std::move(previous_intersection));
-
-        lane_data = laneDataFromDescription(previous_description);
-
-        if (isSimpleIntersection(lane_data, previous_intersection))
-            return {};
-        else
-            return previous_description;
-    }();
+    if (previous_lane_data.empty())
+        return TurnLaneScenario::NONE;
 
-    // no lane string, no problems
-    if (previous_lane_description.empty())
-        return intersection;
+    const bool previous_has_merge_lane =
+        hasTag(TurnLaneType::merge_to_left | TurnLaneType::merge_to_right, previous_lane_data);
+    if (previous_has_merge_lane)
+        return TurnLaneScenario::MERGE;
 
-    // stop on invalid lane data conversion
-    if (lane_data.empty())
-        return intersection;
+    const auto is_simple_previous =
+        isSimpleIntersection(previous_lane_data, intersection) && previous_intersection.size() == 2;
+    if (is_simple_previous)
+        return TurnLaneScenario::SIMPLE_PREVIOUS;
 
-    const auto &previous_data = node_based_graph.GetEdgeData(previous_id);
-    const auto is_simple = isSimpleIntersection(lane_data, intersection);
-    if (is_simple)
+    // This is the second part of the previously described partitioning scenario.
+    if (previous_lane_data.size() >= getNumberOfTurns(previous_intersection) &&
+        previous_intersection.size() != 2)
     {
-        lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
-        return simpleMatchTuplesToTurns(
-            std::move(intersection), lane_data, previous_data.lane_description_id, id_map);
+        previous_lane_data = partitionLaneData(node_based_graph.GetTarget(previous_via_edge),
+                                               std::move(previous_lane_data),
+                                               previous_intersection)
+                                 .second;
+
+        std::sort(previous_lane_data.begin(), previous_lane_data.end());
+
+        // check if we were successfull in trimming
+        if ((previous_lane_data.size() == possible_entries) &&
+            isSimpleIntersection(previous_lane_data, intersection))
+            return TurnLaneScenario::PARTITION_PREVIOUS;
     }
-    else
-    {
-        if (lane_data.size() >= getNumberOfTurns(previous_intersection) &&
-            previous_intersection.size() != 2)
-        {
-            lane_data = partitionLaneData(node_based_graph.GetTarget(previous_id),
-                                          std::move(lane_data),
-                                          previous_intersection)
-                            .second;
 
-            std::sort(lane_data.begin(), lane_data.end());
+    return TurnLaneScenario::UNKNOWN;
+}
 
-            // check if we were successfull in trimming
-            if (lane_data.size() == getNumberOfTurns(intersection) &&
-                isSimpleIntersection(lane_data, intersection))
-            {
-                lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
-                return simpleMatchTuplesToTurns(
-                    std::move(intersection), lane_data, previous_data.lane_description_id, id_map);
-            }
-        }
+void TurnLaneHandler::extractLaneData(const EdgeID via_edge,
+                                      LaneDescriptionID &lane_description_id,
+                                      LaneDataVector &lane_data) const
+{
+    const auto &edge_data = node_based_graph.GetEdgeData(via_edge);
+    lane_description_id = edge_data.lane_description_id;
+    // create an empty lane data
+    if (INVALID_LANE_DESCRIPTIONID != lane_description_id)
+    {
+        const auto lane_description = TurnLaneDescription(
+            turn_lane_masks.begin() + turn_lane_offsets[lane_description_id],
+            turn_lane_masks.begin() + turn_lane_offsets[lane_description_id + 1]);
+
+        lane_data = laneDataFromDescription(lane_description);
+        BOOST_ASSERT(lane_description.size() == (turn_lane_offsets[lane_description_id + 1] -
+                                                 turn_lane_offsets[lane_description_id]));
+    }
+    else
+    {
+        lane_data.clear();
     }
-    return intersection;
 }
 
 /* A simple intersection does not depend on the next intersection coming up. This is important
@@ -288,8 +401,7 @@ bool TurnLaneHandler::isSimpleIntersection(const LaneDataVector &lane_data,
     }
 
     // in case an intersection offers far more lane data items than actual turns, some of them
-    // have
-    // to be for another intersection. A single additional item can be for an invalid bus lane.
+    // have to be for another intersection. A single additional item can be for an invalid bus lane.
     const auto num_turns = [&]() {
         auto count = getNumberOfTurns(intersection);
         if (count < lane_data.size() && !intersection[0].entry_allowed &&
@@ -334,30 +446,44 @@ bool TurnLaneHandler::isSimpleIntersection(const LaneDataVector &lane_data,
     bool all_simple = true;
     bool has_none = false;
     std::unordered_set<std::size_t> matched_indices;
-    for (const auto &data : lane_data)
+    for (std::size_t data_index = 0; data_index < lane_data.size(); ++data_index)
     {
+        const auto &data = lane_data[data_index];
         if (data.tag == TurnLaneType::none)
         {
             has_none = true;
             continue;
         }
 
+        // u-turn tags are at the outside of the lane-tags and require special handling, since
+        // locating their best match requires knowledge on the neighboring tag. (see documentation
+        // on findBestMatch/findBestMatchForReverse
         const auto best_match = [&]() {
+            // normal tag or u-turn as only choice (no other tag present)
             if (data.tag != TurnLaneType::uturn || lane_data.size() == 1)
                 return findBestMatch(data.tag, intersection);
 
-            // lane_data.size() > 1
-            if (lane_data.back().tag == TurnLaneType::uturn)
-                return findBestMatchForReverse(lane_data[lane_data.size() - 2].tag, intersection);
+            BOOST_ASSERT(data.tag == TurnLaneType::uturn);
+            // u-turn at the very left, leftmost turn at data_index - 1
+            if (data_index + 1 == lane_data.size())
+                return findBestMatchForReverse(lane_data[data_index - 1].tag, intersection);
 
-            BOOST_ASSERT(lane_data.front().tag == TurnLaneType::uturn);
-            return findBestMatchForReverse(lane_data[1].tag, intersection);
+            // u-turn to the right (left-handed driving) -> rightmost turn to the left (data_index +
+            // 1)
+            if (data_index == 0)
+                return findBestMatchForReverse(lane_data[data_index + 1].tag, intersection);
+
+            return intersection.begin();
         }();
+        BOOST_ASSERT(best_match != intersection.end());
         std::size_t match_index = std::distance(intersection.begin(), best_match);
         all_simple &= (matched_indices.count(match_index) == 0);
         matched_indices.insert(match_index);
         // in case of u-turns, we might need to activate them first
-        all_simple &= (best_match->entry_allowed || match_index == 0);
+        all_simple &= (best_match->entry_allowed ||
+                       // check for possible u-turn match on non-reversed edge
+                       ((match_index == 0 || match_index + 1 == intersection.size()) &&
+                        !node_based_graph.GetEdgeData(best_match->turn.eid).reversed));
         all_simple &= isValidMatch(data.tag, best_match->turn.instruction);
     }
 
@@ -394,9 +520,9 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
      * case left) turn lane as if it were to continue straight onto the intersection and
      * look back between (1) and (2) to make sure we find the correct lane for the left-turn.
      *
-     * Intersections like these have two parts. Turns that can be made at the first intersection and
-     * turns that have to be made at the second. The partitioning returns the lane data split into
-     * two parts, one for the first and one for the second intersection.
+     * Intersections like these have two parts. Turns that can be made at the first intersection
+     * and turns that have to be made at the second. The partitioning returns the lane data split
+     * into two parts, one for the first and one for the second intersection.
      */
 
     // Try and maitch lanes to available turns. For Turns that are not directly matchable, check
@@ -413,7 +539,8 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
     std::vector<bool> matched_at_first(turn_lane_data.size(), false);
     std::vector<bool> matched_at_second(turn_lane_data.size(), false);
 
-    // find out about the next intersection. To check for valid matches, we also need the turn types
+    // find out about the next intersection. To check for valid matches, we also need the turn
+    // types
     auto next_intersection = turn_analysis.getIntersection(at, straightmost->turn.eid);
     next_intersection =
         turn_analysis.assignTurnTypes(at, straightmost->turn.eid, std::move(next_intersection));
@@ -427,7 +554,8 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
             continue;
 
         const auto best_match = findBestMatch(turn_lane_data[lane].tag, intersection);
-        if (isValidMatch(turn_lane_data[lane].tag, best_match->turn.instruction))
+        if (best_match->entry_allowed &&
+            isValidMatch(turn_lane_data[lane].tag, best_match->turn.instruction))
         {
             matched_at_first[lane] = true;
 
@@ -437,9 +565,20 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
 
         const auto best_match_at_next_intersection =
             findBestMatch(turn_lane_data[lane].tag, next_intersection);
-        if (isValidMatch(turn_lane_data[lane].tag,
+        if (best_match_at_next_intersection->entry_allowed &&
+            isValidMatch(turn_lane_data[lane].tag,
                          best_match_at_next_intersection->turn.instruction))
-            matched_at_second[lane] = true;
+        {
+            if (!matched_at_first[lane] || turn_lane_data[lane].tag == TurnLaneType::straight ||
+                getMatchingQuality(turn_lane_data[lane].tag, *best_match) >
+                    getMatchingQuality(turn_lane_data[lane].tag, *best_match_at_next_intersection))
+            {
+                if (turn_lane_data[lane].tag != TurnLaneType::straight)
+                    matched_at_first[lane] = false;
+
+                matched_at_second[lane] = true;
+            }
+        }
 
         // we need to match all items to either the current or the next intersection
         if (!(matched_at_first[lane] || matched_at_second[lane]))
@@ -457,8 +596,6 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
             straightmost_tag_index = none_index;
     }
 
-    // TODO handle reverse
-
     // handle none values
     if (none_index != turn_lane_data.size())
     {
@@ -486,7 +623,6 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
     LaneDataVector first, second;
     for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane)
     {
-
         if (matched_at_second[lane])
             second.push_back(turn_lane_data[lane]);
 
@@ -494,6 +630,8 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
         if (lane == straightmost_tag_index)
         {
             augmentEntry(turn_lane_data[straightmost_tag_index]);
+            // disable this turn for assignment if it is a -use lane only
+            turn_lane_data[straightmost_tag_index].suppress_assignment = true;
         }
 
         if (matched_at_first[lane])
@@ -505,7 +643,7 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
             std::count(matched_at_second.begin(), matched_at_second.end(), true)) ==
             getNumberOfTurns(next_intersection))
     {
-        TurnLaneData data = {TurnLaneType::straight, 255, 0};
+        TurnLaneData data = {TurnLaneType::straight, 255, 0, true};
         augmentEntry(data);
         first.push_back(data);
         std::sort(first.begin(), first.end());
@@ -517,8 +655,7 @@ std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
 
 Intersection TurnLaneHandler::simpleMatchTuplesToTurns(Intersection intersection,
                                                        const LaneDataVector &lane_data,
-                                                       const LaneDescriptionID lane_description_id,
-                                                       LaneDataIdMap &id_map) const
+                                                       const LaneDescriptionID lane_description_id)
 {
     if (lane_data.empty() || !canMatchTrivially(intersection, lane_data))
         return intersection;
@@ -527,10 +664,124 @@ Intersection TurnLaneHandler::simpleMatchTuplesToTurns(Intersection intersection
         !hasTag(TurnLaneType::none | TurnLaneType::merge_to_left | TurnLaneType::merge_to_right,
                 lane_data));
 
+    count_handled++;
+
     return triviallyMatchLanesToTurns(
         std::move(intersection), lane_data, node_based_graph, lane_description_id, id_map);
 }
 
+Intersection TurnLaneHandler::handleSliproadTurn(Intersection intersection,
+                                                 const LaneDescriptionID lane_description_id,
+                                                 LaneDataVector lane_data,
+                                                 const Intersection &previous_intersection)
+{
+    const std::size_t sliproad_index =
+        std::distance(previous_intersection.begin(),
+                      std::find_if(previous_intersection.begin(),
+                                   previous_intersection.end(),
+                                   [](const ConnectedRoad &road) {
+                                       return road.turn.instruction.type == TurnType::Sliproad;
+                                   }));
+
+    BOOST_ASSERT(sliproad_index <= previous_intersection.size());
+    const auto &sliproad = previous_intersection[sliproad_index];
+
+    // code duplicatino with deduceScenario: TODO refactor
+    const auto &main_road = [&]() {
+        if (sliproad_index + 1 == previous_intersection.size())
+        {
+            BOOST_ASSERT(sliproad_index > 1);
+            return previous_intersection[sliproad_index - 1];
+        }
+        else if (sliproad_index == 1)
+        {
+            BOOST_ASSERT(sliproad_index + 1 < previous_intersection.size());
+            return previous_intersection[sliproad_index + 1];
+        }
+        else if (angularDeviation(sliproad.turn.angle,
+                                  previous_intersection.at(sliproad_index - 1).turn.angle) <
+                 angularDeviation(sliproad.turn.angle,
+                                  previous_intersection.at(sliproad_index + 1).turn.angle))
+            return previous_intersection[sliproad_index - 1];
+        else
+            return previous_intersection[sliproad_index + 1];
+    }();
+    const auto main_description_id =
+        node_based_graph.GetEdgeData(main_road.turn.eid).lane_description_id;
+    const auto sliproad_description_id =
+        node_based_graph.GetEdgeData(sliproad.turn.eid).lane_description_id;
+
+    if (main_description_id == INVALID_LANE_DESCRIPTIONID ||
+        sliproad_description_id == INVALID_LANE_DESCRIPTIONID)
+        return intersection;
+
+    TurnLaneDescription combined_description;
+    // is the sliproad going off to the right?
+    if (main_road.turn.angle > sliproad.turn.angle)
+    {
+        combined_description.insert(
+            combined_description.end(),
+            turn_lane_masks.begin() + turn_lane_offsets[main_description_id],
+            turn_lane_masks.begin() + turn_lane_offsets[main_description_id + 1]);
+
+        combined_description.insert(
+            combined_description.end(),
+            turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id],
+            turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id + 1]);
+
+        // if we handle the main road, we have to adjust the lane-data
+        if (main_description_id == lane_description_id)
+        {
+            const auto offset = turn_lane_offsets[sliproad_description_id + 1] -
+                                turn_lane_offsets[sliproad_description_id];
+            for (auto &item : lane_data)
+            {
+                item.from += offset;
+                item.to += offset;
+            }
+        }
+    }
+    // or to the left?
+    else
+    {
+        combined_description.insert(
+            combined_description.end(),
+            turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id],
+            turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id + 1]);
+        combined_description.insert(
+            combined_description.end(),
+            turn_lane_masks.begin() + turn_lane_offsets[main_description_id],
+            turn_lane_masks.begin() + turn_lane_offsets[main_description_id + 1]);
+
+        // if we are handling the sliproad, we have to adjust its lane data
+        if (sliproad_description_id == lane_description_id)
+        {
+            const auto offset =
+                turn_lane_offsets[main_description_id + 1] - turn_lane_offsets[main_description_id];
+            for (auto &item : lane_data)
+            {
+                item.from += offset;
+                item.to += offset;
+            }
+        }
+    }
+
+    const auto combined_id = [&]() {
+        auto itr = lane_description_map.find(combined_description);
+        if (lane_description_map.find(combined_description) == lane_description_map.end())
+        {
+            const auto new_id = boost::numeric_cast<LaneDescriptionID>(lane_description_map.size());
+            lane_description_map[combined_description] = new_id;
+            return new_id;
+        }
+        else
+        {
+            return itr->second;
+        }
+    }();
+    return simpleMatchTuplesToTurns(std::move(intersection), lane_data, combined_id);
+}
+
 } // namespace lanes
 } // namespace guidance
 } // namespace extractor
diff --git a/src/extractor/guidance/turn_lane_matcher.cpp b/src/extractor/guidance/turn_lane_matcher.cpp
index c89bd23..5df9448 100644
--- a/src/extractor/guidance/turn_lane_matcher.cpp
+++ b/src/extractor/guidance/turn_lane_matcher.cpp
@@ -1,5 +1,5 @@
-#include "extractor/guidance/toolkit.hpp"
 #include "extractor/guidance/turn_lane_matcher.hpp"
+#include "extractor/guidance/toolkit.hpp"
 #include "util/guidance/toolkit.hpp"
 
 #include <boost/assert.hpp>
@@ -17,7 +17,7 @@ namespace lanes
 {
 
 // Translate Turn Tags into a Matching Direction Modifier
-DirectionModifier::Enum getMatchingModifier(const TurnLaneType::Mask &tag)
+DirectionModifier::Enum getMatchingModifier(const TurnLaneType::Mask tag)
 {
     const constexpr TurnLaneType::Mask tag_by_modifier[] = {TurnLaneType::uturn,
                                                             TurnLaneType::sharp_right,
@@ -51,7 +51,7 @@ DirectionModifier::Enum getMatchingModifier(const TurnLaneType::Mask &tag)
 }
 
 // check whether a match of a given tag and a turn instruction can be seen as valid
-bool isValidMatch(const TurnLaneType::Mask &tag, const TurnInstruction instruction)
+bool isValidMatch(const TurnLaneType::Mask tag, const TurnInstruction instruction)
 {
     using util::guidance::hasLeftModifier;
     using util::guidance::hasRightModifier;
@@ -102,30 +102,38 @@ bool isValidMatch(const TurnLaneType::Mask &tag, const TurnInstruction instructi
     return false;
 }
 
+double getMatchingQuality(const TurnLaneType::Mask tag, const ConnectedRoad &road)
+{
+    const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315};
+    const auto modifier = getMatchingModifier(tag);
+    BOOST_ASSERT(static_cast<std::size_t>(modifier) <
+                 sizeof(idealized_turn_angles) / sizeof(*idealized_turn_angles));
+    const auto idealized_angle = idealized_turn_angles[modifier];
+    return angularDeviation(idealized_angle, road.turn.angle);
+}
+
 // Every tag is somewhat idealized in form of the expected angle. A through lane should go straight
 // (or follow a 180 degree turn angle between in/out segments.) The following function tries to find
 // the best possible match for every tag in a given intersection, considering a few corner cases
 // introduced to OSRM handling u-turns
-typename Intersection::const_iterator findBestMatch(const TurnLaneType::Mask &tag,
+typename Intersection::const_iterator findBestMatch(const TurnLaneType::Mask tag,
                                                     const Intersection &intersection)
 {
-    const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315};
-    const auto idealized_angle = idealized_turn_angles[getMatchingModifier(tag)];
-    return std::min_element(
-        intersection.begin(),
-        intersection.end(),
-        [idealized_angle, &tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
-            // prefer valid matches
-            if (isValidMatch(tag, lhs.turn.instruction) != isValidMatch(tag, rhs.turn.instruction))
-                return isValidMatch(tag, lhs.turn.instruction);
-            // if the entry allowed flags don't match, we select the one with
-            // entry allowed set to true
-            if (lhs.entry_allowed != rhs.entry_allowed)
-                return lhs.entry_allowed;
+    return std::min_element(intersection.begin(),
+                            intersection.end(),
+                            [tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
+                                // prefer valid matches
+                                if (isValidMatch(tag, lhs.turn.instruction) !=
+                                    isValidMatch(tag, rhs.turn.instruction))
+                                    return isValidMatch(tag, lhs.turn.instruction);
 
-            return angularDeviation(idealized_angle, lhs.turn.angle) <
-                   angularDeviation(idealized_angle, rhs.turn.angle);
-        });
+                                // if the entry allowed flags don't match, we select the one with
+                                // entry allowed set to true
+                                if (lhs.entry_allowed != rhs.entry_allowed)
+                                    return lhs.entry_allowed;
+
+                                return getMatchingQuality(tag, lhs) < getMatchingQuality(tag, rhs);
+                            });
 }
 
 // Reverse is a special case, because it requires access to the leftmost tag. It has its own
@@ -133,30 +141,28 @@ typename Intersection::const_iterator findBestMatch(const TurnLaneType::Mask &ta
 // by default in OSRM. Therefor we cannot check whether a turn is allowed, since it could be
 // possible that it is forbidden. In addition, the best u-turn angle does not necessarily represent
 // the u-turn, since it could be a sharp-left turn instead on a road with a middle island.
-typename Intersection::const_iterator
-findBestMatchForReverse(const TurnLaneType::Mask &neighbor_tag, const Intersection &intersection)
+typename Intersection::const_iterator findBestMatchForReverse(const TurnLaneType::Mask neighbor_tag,
+                                                              const Intersection &intersection)
 {
     const auto neighbor_itr = findBestMatch(neighbor_tag, intersection);
-    if ((neighbor_itr + 1 == intersection.cend()) || (neighbor_itr == intersection.cbegin() + 1))
+    if (neighbor_itr + 1 == intersection.cend())
         return intersection.begin();
 
-    const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315};
     const TurnLaneType::Mask tag = TurnLaneType::uturn;
-    const auto idealized_angle = idealized_turn_angles[getMatchingModifier(tag)];
     return std::min_element(
         intersection.begin() + std::distance(intersection.begin(), neighbor_itr),
         intersection.end(),
-        [idealized_angle, &tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
+        [tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
             // prefer valid matches
             if (isValidMatch(tag, lhs.turn.instruction) != isValidMatch(tag, rhs.turn.instruction))
                 return isValidMatch(tag, lhs.turn.instruction);
+
             // if the entry allowed flags don't match, we select the one with
             // entry allowed set to true
             if (lhs.entry_allowed != rhs.entry_allowed)
                 return lhs.entry_allowed;
 
-            return angularDeviation(idealized_angle, lhs.turn.angle) <
-                   angularDeviation(idealized_angle, rhs.turn.angle);
+            return getMatchingQuality(tag, lhs) < getMatchingQuality(tag, rhs);
         });
 }
 
@@ -239,6 +245,8 @@ Intersection triviallyMatchLanesToTurns(Intersection intersection,
             // continue with the first lane
             lane = 1;
         }
+        else
+            return intersection;
     }
 
     for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index)
@@ -251,7 +259,8 @@ Intersection triviallyMatchLanesToTurns(Intersection intersection,
             BOOST_ASSERT(findBestMatch(lane_data[lane].tag, intersection) ==
                          intersection.begin() + road_index);
 
-            if (TurnType::Suppressed == intersection[road_index].turn.instruction.type)
+            if (TurnType::Suppressed == intersection[road_index].turn.instruction.type &&
+                !lane_data[lane].suppress_assignment)
                 intersection[road_index].turn.instruction.type = TurnType::UseLane;
 
             matchRoad(intersection[road_index], lane_data[lane]);
diff --git a/src/extractor/raster_source.cpp b/src/extractor/raster_source.cpp
index ee54b4a..9570ca4 100644
--- a/src/extractor/raster_source.cpp
+++ b/src/extractor/raster_source.cpp
@@ -154,8 +154,9 @@ SourceContainer::GetRasterInterpolateFromSource(unsigned int source_id, double l
     BOOST_ASSERT(lon > -180);
 
     const auto &found = LoadedSources[source_id];
-    return found.GetRasterInterpolate(static_cast<std::int32_t>(util::toFixed(util::FloatLongitude{lon})),
-                                      static_cast<std::int32_t>(util::toFixed(util::FloatLatitude{lat})));
+    return found.GetRasterInterpolate(
+        static_cast<std::int32_t>(util::toFixed(util::FloatLongitude{lon})),
+        static_cast<std::int32_t>(util::toFixed(util::FloatLatitude{lat})));
 }
 }
 }
diff --git a/src/extractor/restriction_map.cpp b/src/extractor/restriction_map.cpp
index 8cba4b6..183e95d 100644
--- a/src/extractor/restriction_map.cpp
+++ b/src/extractor/restriction_map.cpp
@@ -143,11 +143,9 @@ bool RestrictionMap::CheckIfTurnIsRestricted(const NodeID node_u,
         {
             return true;
         }
-        if (node_w != restriction_target.target_node && // target not found
-            restriction_target.is_only)                 // and is an only restriction
-        {
-            return true;
-        }
+        // We could be tempted to check for `only` restrictions here as well. However, that check is
+        // actually perfomed in intersection generation where we can also verify if the only
+        // restriction is valid at all.
     }
     return false;
 }
diff --git a/src/extractor/restriction_parser.cpp b/src/extractor/restriction_parser.cpp
index a6cc0e7..23e176f 100644
--- a/src/extractor/restriction_parser.cpp
+++ b/src/extractor/restriction_parser.cpp
@@ -1,9 +1,9 @@
 #include "extractor/restriction_parser.hpp"
 #include "extractor/profile_properties.hpp"
+#include "extractor/scripting_environment.hpp"
 
 #include "extractor/external_memory_node.hpp"
-#include "util/exception.hpp"
-#include "util/lua_util.hpp"
+
 #include "util/simple_logger.hpp"
 
 #include <boost/algorithm/string.hpp>
@@ -24,49 +24,32 @@ namespace osrm
 namespace extractor
 {
 
-namespace
-{
-int luaErrorCallback(lua_State *lua_state)
-{
-    std::string error_msg = lua_tostring(lua_state, -1);
-    throw util::exception("ERROR occurred in profile script:\n" + error_msg);
-}
-}
-
-RestrictionParser::RestrictionParser(lua_State *lua_state, const ProfileProperties &properties)
-    : use_turn_restrictions(properties.use_turn_restrictions)
+RestrictionParser::RestrictionParser(ScriptingEnvironment &scripting_environment)
+    : use_turn_restrictions(scripting_environment.GetProfileProperties().use_turn_restrictions)
 {
     if (use_turn_restrictions)
     {
-        ReadRestrictionExceptions(lua_state);
-    }
-}
-
-void RestrictionParser::ReadRestrictionExceptions(lua_State *lua_state)
-{
-    if (util::luaFunctionExists(lua_state, "get_exceptions"))
-    {
-        luabind::set_pcall_callback(&luaErrorCallback);
-        // get list of turn restriction exceptions
-        luabind::call_function<void>(
-            lua_state, "get_exceptions", boost::ref(restriction_exceptions));
+        restriction_exceptions = scripting_environment.GetExceptions();
         const unsigned exception_count = restriction_exceptions.size();
-        util::SimpleLogger().Write() << "Found " << exception_count
-                                     << " exceptions to turn restrictions:";
-        for (const std::string &str : restriction_exceptions)
+        if (exception_count)
         {
-            util::SimpleLogger().Write() << "  " << str;
+            util::SimpleLogger().Write() << "Found " << exception_count
+                                         << " exceptions to turn restrictions:";
+            for (const std::string &str : restriction_exceptions)
+            {
+                util::SimpleLogger().Write() << "  " << str;
+            }
+        }
+        else
+        {
+            util::SimpleLogger().Write() << "Found no exceptions to turn restrictions";
         }
-    }
-    else
-    {
-        util::SimpleLogger().Write() << "Found no exceptions to turn restrictions";
     }
 }
 
 /**
- * Tries to parse an relation as turn restriction. This can fail for a number of
- * reasons, this the return type is a boost::optional<T>.
+ * Tries to parse a relation as a turn restriction. This can fail for a number of
+ * reasons. The return type is a boost::optional<T>.
  *
  * Some restrictions can also be ignored: See the ```get_exceptions``` function
  * in the corresponding profile.
@@ -88,7 +71,7 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const
     osmium::tags::KeyPrefixFilter::iterator fi_begin(filter, tag_list.begin(), tag_list.end());
     osmium::tags::KeyPrefixFilter::iterator fi_end(filter, tag_list.end(), tag_list.end());
 
-    // if it's a restriction, continue;
+    // if it's not a restriction, continue;
     if (std::distance(fi_begin, fi_end) == 0)
     {
         return {};
@@ -108,10 +91,20 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const
         const std::string key(fi_begin->key());
         const std::string value(fi_begin->value());
 
+        // documented OSM restriction tags start either with only_* or no_*;
+        // check and return on these values, and ignore unrecognized values
         if (value.find("only_") == 0)
         {
             is_only_restriction = true;
         }
+        else if (value.find("no_") == 0)
+        {
+            is_only_restriction = false;
+        }
+        else // unrecognized value type
+        {
+            return {};
+        }
 
         // if the "restriction*" key is longer than 11 chars, it is a conditional exception (i.e.
         // "restriction:<transportation_type>")
diff --git a/src/extractor/scripting_environment.cpp b/src/extractor/scripting_environment.cpp
deleted file mode 100644
index 899537e..0000000
--- a/src/extractor/scripting_environment.cpp
+++ /dev/null
@@ -1,207 +0,0 @@
-#include "extractor/scripting_environment.hpp"
-
-#include "extractor/external_memory_node.hpp"
-#include "extractor/extraction_helper_functions.hpp"
-#include "extractor/extraction_node.hpp"
-#include "extractor/extraction_way.hpp"
-#include "extractor/internal_extractor_edge.hpp"
-#include "extractor/profile_properties.hpp"
-#include "extractor/raster_source.hpp"
-#include "util/exception.hpp"
-#include "util/lua_util.hpp"
-#include "util/make_unique.hpp"
-#include "util/simple_logger.hpp"
-#include "util/typedefs.hpp"
-
-#include <luabind/iterator_policy.hpp>
-#include <luabind/operator.hpp>
-#include <luabind/tag_function.hpp>
-
-#include <osmium/osm.hpp>
-
-#include <sstream>
-
-namespace osrm
-{
-namespace extractor
-{
-namespace
-{
-// wrapper method as luabind doesn't automatically overload funcs w/ default parameters
-template <class T>
-auto get_value_by_key(T const &object, const char *key) -> decltype(object.get_value_by_key(key))
-{
-    return object.get_value_by_key(key, "");
-}
-
-template <class T> double latToDouble(T const &object)
-{
-    return static_cast<double>(util::toFloating(object.lat));
-}
-
-template <class T> double lonToDouble(T const &object)
-{
-    return static_cast<double>(util::toFloating(object.lon));
-}
-
-// Luabind does not like memr funs: instead of casting to the function's signature (mem fun ptr) we
-// simply wrap it
-auto get_nodes_for_way(const osmium::Way &way) -> decltype(way.nodes()) { return way.nodes(); }
-
-// Error handler
-int luaErrorCallback(lua_State *state)
-{
-    std::string error_msg = lua_tostring(state, -1);
-    std::ostringstream error_stream;
-    error_stream << error_msg;
-    throw util::exception("ERROR occurred in profile script:\n" + error_stream.str());
-}
-}
-
-ScriptingEnvironment::ScriptingEnvironment(const std::string &file_name) : file_name(file_name)
-{
-    util::SimpleLogger().Write() << "Using script " << file_name;
-}
-
-void ScriptingEnvironment::InitContext(ScriptingEnvironment::Context &context)
-{
-    typedef double (osmium::Location::*location_member_ptr_type)() const;
-
-    luabind::open(context.state);
-    // open utility libraries string library;
-    luaL_openlibs(context.state);
-
-    util::luaAddScriptFolderToLoadPath(context.state, file_name.c_str());
-
-    // Add our function to the state's global scope
-    luabind::module(context.state)
-        [luabind::def("durationIsValid", durationIsValid),
-         luabind::def("parseDuration", parseDuration),
-         luabind::def("trimLaneString", trimLaneString),
-         luabind::class_<TravelMode>("mode").enum_(
-             "enums")[luabind::value("inaccessible", TRAVEL_MODE_INACCESSIBLE),
-                      luabind::value("driving", TRAVEL_MODE_DRIVING),
-                      luabind::value("cycling", TRAVEL_MODE_CYCLING),
-                      luabind::value("walking", TRAVEL_MODE_WALKING),
-                      luabind::value("ferry", TRAVEL_MODE_FERRY),
-                      luabind::value("train", TRAVEL_MODE_TRAIN),
-                      luabind::value("pushing_bike", TRAVEL_MODE_PUSHING_BIKE),
-                      luabind::value("steps_up", TRAVEL_MODE_STEPS_UP),
-                      luabind::value("steps_down", TRAVEL_MODE_STEPS_DOWN),
-                      luabind::value("river_up", TRAVEL_MODE_RIVER_UP),
-                      luabind::value("river_down", TRAVEL_MODE_RIVER_DOWN),
-                      luabind::value("route", TRAVEL_MODE_ROUTE)],
-         luabind::class_<SourceContainer>("sources")
-             .def(luabind::constructor<>())
-             .def("load", &SourceContainer::LoadRasterSource)
-             .def("query", &SourceContainer::GetRasterDataFromSource)
-             .def("interpolate", &SourceContainer::GetRasterInterpolateFromSource),
-         luabind::class_<const float>("constants")
-             .enum_("enums")[luabind::value("precision", COORDINATE_PRECISION)],
-
-         luabind::class_<ProfileProperties>("ProfileProperties")
-             .def(luabind::constructor<>())
-             .property("traffic_signal_penalty",
-                       &ProfileProperties::GetTrafficSignalPenalty,
-                       &ProfileProperties::SetTrafficSignalPenalty)
-             .property("u_turn_penalty",
-                       &ProfileProperties::GetUturnPenalty,
-                       &ProfileProperties::SetUturnPenalty)
-             .def_readwrite("use_turn_restrictions", &ProfileProperties::use_turn_restrictions)
-             .def_readwrite("continue_straight_at_waypoint",
-                            &ProfileProperties::continue_straight_at_waypoint),
-
-         luabind::class_<std::vector<std::string>>("vector").def(
-             "Add",
-             static_cast<void (std::vector<std::string>::*)(const std::string &)>(
-                 &std::vector<std::string>::push_back)),
-
-         luabind::class_<osmium::Location>("Location")
-             .def<location_member_ptr_type>("lat", &osmium::Location::lat)
-             .def<location_member_ptr_type>("lon", &osmium::Location::lon),
-
-         luabind::class_<osmium::Node>("Node")
-             // .def<node_member_ptr_type>("tags", &osmium::Node::tags)
-             .def("location", &osmium::Node::location)
-             .def("get_value_by_key", &osmium::Node::get_value_by_key)
-             .def("get_value_by_key", &get_value_by_key<osmium::Node>)
-             .def("id", &osmium::Node::id),
-
-         luabind::class_<ExtractionNode>("ResultNode")
-             .def_readwrite("traffic_lights", &ExtractionNode::traffic_lights)
-             .def_readwrite("barrier", &ExtractionNode::barrier),
-
-         luabind::class_<ExtractionWay>("ResultWay")
-             // .def(luabind::constructor<>())
-             .def_readwrite("forward_speed", &ExtractionWay::forward_speed)
-             .def_readwrite("backward_speed", &ExtractionWay::backward_speed)
-             .def_readwrite("name", &ExtractionWay::name)
-             .def_readwrite("pronunciation", &ExtractionWay::pronunciation)
-             .def_readwrite("destinations", &ExtractionWay::destinations)
-             .def_readwrite("roundabout", &ExtractionWay::roundabout)
-             .def_readwrite("is_access_restricted", &ExtractionWay::is_access_restricted)
-             .def_readwrite("is_startpoint", &ExtractionWay::is_startpoint)
-             .def_readwrite("duration", &ExtractionWay::duration)
-             .def_readwrite("turn_lanes_forward", &ExtractionWay::turn_lanes_forward)
-             .def_readwrite("turn_lanes_backward", &ExtractionWay::turn_lanes_backward)
-             .property(
-                 "forward_mode", &ExtractionWay::get_forward_mode, &ExtractionWay::set_forward_mode)
-             .property("backward_mode",
-                       &ExtractionWay::get_backward_mode,
-                       &ExtractionWay::set_backward_mode),
-         luabind::class_<osmium::WayNodeList>("WayNodeList").def(luabind::constructor<>()),
-         luabind::class_<osmium::NodeRef>("NodeRef")
-             .def(luabind::constructor<>())
-             // Dear ambitious reader: registering .location() as in:
-             // .def("location", +[](const osmium::NodeRef& nref){ return nref.location(); })
-             // will crash at runtime, since we're not (yet?) using libosnmium's
-             // NodeLocationsForWays cache
-             .def("id", &osmium::NodeRef::ref),
-         luabind::class_<osmium::Way>("Way")
-             .def("get_value_by_key", &osmium::Way::get_value_by_key)
-             .def("get_value_by_key", &get_value_by_key<osmium::Way>)
-             .def("id", &osmium::Way::id)
-             .def("get_nodes", get_nodes_for_way, luabind::return_stl_iterator),
-         luabind::class_<InternalExtractorEdge>("EdgeSource")
-             .def_readonly("source_coordinate", &InternalExtractorEdge::source_coordinate)
-             .def_readwrite("weight_data", &InternalExtractorEdge::weight_data),
-         luabind::class_<InternalExtractorEdge::WeightData>("WeightData")
-             .def_readwrite("speed", &InternalExtractorEdge::WeightData::speed),
-         luabind::class_<ExternalMemoryNode>("EdgeTarget")
-             .property("lon", &lonToDouble<ExternalMemoryNode>)
-             .property("lat", &latToDouble<ExternalMemoryNode>),
-         luabind::class_<util::Coordinate>("Coordinate")
-             .property("lon", &lonToDouble<util::Coordinate>)
-             .property("lat", &latToDouble<util::Coordinate>),
-         luabind::class_<RasterDatum>("RasterDatum")
-             .def_readonly("datum", &RasterDatum::datum)
-             .def("invalid_data", &RasterDatum::get_invalid)];
-
-    luabind::globals(context.state)["properties"] = &context.properties;
-    luabind::globals(context.state)["sources"] = &context.sources;
-
-    if (0 != luaL_dofile(context.state, file_name.c_str()))
-    {
-        luabind::object error_msg(luabind::from_stack(context.state, -1));
-        std::ostringstream error_stream;
-        error_stream << error_msg;
-        throw util::exception("ERROR occurred in profile script:\n" + error_stream.str());
-    }
-}
-
-ScriptingEnvironment::Context &ScriptingEnvironment::GetContex()
-{
-    std::lock_guard<std::mutex> lock(init_mutex);
-    bool initialized = false;
-    auto &ref = script_contexts.local(initialized);
-    if (!initialized)
-    {
-        ref = util::make_unique<Context>();
-        InitContext(*ref);
-    }
-    luabind::set_pcall_callback(&luaErrorCallback);
-
-    return *ref;
-}
-}
-}
diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp
new file mode 100644
index 0000000..b0aadf9
--- /dev/null
+++ b/src/extractor/scripting_environment_lua.cpp
@@ -0,0 +1,407 @@
+#include "extractor/scripting_environment_lua.hpp"
+
+#include "extractor/external_memory_node.hpp"
+#include "extractor/extraction_helper_functions.hpp"
+#include "extractor/extraction_node.hpp"
+#include "extractor/extraction_way.hpp"
+#include "extractor/internal_extractor_edge.hpp"
+#include "extractor/profile_properties.hpp"
+#include "extractor/raster_source.hpp"
+#include "extractor/restriction_parser.hpp"
+#include "util/exception.hpp"
+#include "util/lua_util.hpp"
+#include "util/make_unique.hpp"
+#include "util/simple_logger.hpp"
+#include "util/typedefs.hpp"
+
+#include <luabind/iterator_policy.hpp>
+#include <luabind/operator.hpp>
+#include <luabind/tag_function.hpp>
+
+#include <osmium/osm.hpp>
+
+#include <tbb/parallel_for.h>
+
+#include <sstream>
+
+namespace osrm
+{
+namespace extractor
+{
+namespace
+{
+// wrapper method as luabind doesn't automatically overload funcs w/ default parameters
+template <class T>
+auto get_value_by_key(T const &object, const char *key) -> decltype(object.get_value_by_key(key))
+{
+    return object.get_value_by_key(key, "");
+}
+
+template <class T> double latToDouble(T const &object)
+{
+    return static_cast<double>(util::toFloating(object.lat));
+}
+
+template <class T> double lonToDouble(T const &object)
+{
+    return static_cast<double>(util::toFloating(object.lon));
+}
+
+// Luabind does not like memr funs: instead of casting to the function's signature (mem fun ptr) we
+// simply wrap it
+auto get_nodes_for_way(const osmium::Way &way) -> decltype(way.nodes()) { return way.nodes(); }
+
+// Error handler
+int luaErrorCallback(lua_State *state)
+{
+    std::string error_msg = lua_tostring(state, -1);
+    std::ostringstream error_stream;
+    error_stream << error_msg;
+    throw util::exception("ERROR occurred in profile script:\n" + error_stream.str());
+}
+}
+
+LuaScriptingEnvironment::LuaScriptingEnvironment(const std::string &file_name)
+    : file_name(file_name)
+{
+    util::SimpleLogger().Write() << "Using script " << file_name;
+}
+
+void LuaScriptingEnvironment::InitContext(LuaScriptingContext &context)
+{
+    typedef double (osmium::Location::*location_member_ptr_type)() const;
+
+    luabind::open(context.state);
+    // open utility libraries string library;
+    luaL_openlibs(context.state);
+
+    util::luaAddScriptFolderToLoadPath(context.state, file_name.c_str());
+
+    // Add our function to the state's global scope
+    luabind::module(context.state)
+        [luabind::def("durationIsValid", durationIsValid),
+         luabind::def("parseDuration", parseDuration),
+         luabind::def("trimLaneString", trimLaneString),
+         luabind::def("applyAccessTokens", applyAccessTokens),
+         luabind::class_<TravelMode>("mode").enum_(
+             "enums")[luabind::value("inaccessible", TRAVEL_MODE_INACCESSIBLE),
+                      luabind::value("driving", TRAVEL_MODE_DRIVING),
+                      luabind::value("cycling", TRAVEL_MODE_CYCLING),
+                      luabind::value("walking", TRAVEL_MODE_WALKING),
+                      luabind::value("ferry", TRAVEL_MODE_FERRY),
+                      luabind::value("train", TRAVEL_MODE_TRAIN),
+                      luabind::value("pushing_bike", TRAVEL_MODE_PUSHING_BIKE),
+                      luabind::value("steps_up", TRAVEL_MODE_STEPS_UP),
+                      luabind::value("steps_down", TRAVEL_MODE_STEPS_DOWN),
+                      luabind::value("river_up", TRAVEL_MODE_RIVER_UP),
+                      luabind::value("river_down", TRAVEL_MODE_RIVER_DOWN),
+                      luabind::value("route", TRAVEL_MODE_ROUTE)],
+
+         luabind::class_<extractor::guidance::RoadPriorityClass::Enum>("road_priority_class")
+             .enum_("enums")
+                 [luabind::value("motorway", extractor::guidance::RoadPriorityClass::MOTORWAY),
+                  luabind::value("trunk", extractor::guidance::RoadPriorityClass::TRUNK),
+                  luabind::value("primary", extractor::guidance::RoadPriorityClass::PRIMARY),
+                  luabind::value("secondary", extractor::guidance::RoadPriorityClass::SECONDARY),
+                  luabind::value("tertiary", extractor::guidance::RoadPriorityClass::TERTIARY),
+                  luabind::value("main_residential",
+                                 extractor::guidance::RoadPriorityClass::MAIN_RESIDENTIAL),
+                  luabind::value("side_residential",
+                                 extractor::guidance::RoadPriorityClass::SIDE_RESIDENTIAL),
+                  luabind::value("link_road", extractor::guidance::RoadPriorityClass::LINK_ROAD),
+                  luabind::value("bike_path", extractor::guidance::RoadPriorityClass::BIKE_PATH),
+                  luabind::value("foot_path", extractor::guidance::RoadPriorityClass::FOOT_PATH),
+                  luabind::value("connectivity",
+                                 extractor::guidance::RoadPriorityClass::CONNECTIVITY)],
+
+         luabind::class_<SourceContainer>("sources")
+             .def(luabind::constructor<>())
+             .def("load", &SourceContainer::LoadRasterSource)
+             .def("query", &SourceContainer::GetRasterDataFromSource)
+             .def("interpolate", &SourceContainer::GetRasterInterpolateFromSource),
+         luabind::class_<const float>("constants")
+             .enum_("enums")[luabind::value("precision", COORDINATE_PRECISION)],
+
+         luabind::class_<ProfileProperties>("ProfileProperties")
+             .def(luabind::constructor<>())
+             .property("traffic_signal_penalty",
+                       &ProfileProperties::GetTrafficSignalPenalty,
+                       &ProfileProperties::SetTrafficSignalPenalty)
+             .property("u_turn_penalty",
+                       &ProfileProperties::GetUturnPenalty,
+                       &ProfileProperties::SetUturnPenalty)
+             .def_readwrite("use_turn_restrictions", &ProfileProperties::use_turn_restrictions)
+             .def_readwrite("continue_straight_at_waypoint",
+                            &ProfileProperties::continue_straight_at_waypoint)
+             .def_readwrite("left_hand_driving", &ProfileProperties::left_hand_driving),
+
+         luabind::class_<std::vector<std::string>>("vector").def(
+             "Add",
+             static_cast<void (std::vector<std::string>::*)(const std::string &)>(
+                 &std::vector<std::string>::push_back)),
+
+         luabind::class_<osmium::Location>("Location")
+             .def<location_member_ptr_type>("lat", &osmium::Location::lat)
+             .def<location_member_ptr_type>("lon", &osmium::Location::lon),
+
+         luabind::class_<osmium::Node>("Node")
+             // .def<node_member_ptr_type>("tags", &osmium::Node::tags)
+             .def("location", &osmium::Node::location)
+             .def("get_value_by_key", &osmium::Node::get_value_by_key)
+             .def("get_value_by_key", &get_value_by_key<osmium::Node>)
+             .def("id", &osmium::Node::id),
+
+         luabind::class_<ExtractionNode>("ResultNode")
+             .def_readwrite("traffic_lights", &ExtractionNode::traffic_lights)
+             .def_readwrite("barrier", &ExtractionNode::barrier),
+
+         // road classification to be set in profile
+         luabind::class_<guidance::RoadClassification>("RoadClassification")
+             .property("motorway_class",
+                       &guidance::RoadClassification::IsMotorwayClass,
+                       &guidance::RoadClassification::SetMotorwayFlag)
+             .property("link_class",
+                       &guidance::RoadClassification::IsLinkClass,
+                       &guidance::RoadClassification::SetLinkClass)
+             .property("may_be_ignored",
+                       &guidance::RoadClassification::IsLowPriorityRoadClass,
+                       &guidance::RoadClassification::SetLowPriorityFlag)
+             .property("road_priority_class",
+                       &guidance::RoadClassification::GetClass,
+                       &guidance::RoadClassification::SetClass),
+
+         luabind::class_<ExtractionWay>("ResultWay")
+             // .def(luabind::constructor<>())
+             .def_readwrite("forward_speed", &ExtractionWay::forward_speed)
+             .def_readwrite("backward_speed", &ExtractionWay::backward_speed)
+             .def_readwrite("name", &ExtractionWay::name)
+             .def_readwrite("ref", &ExtractionWay::ref)
+             .def_readwrite("pronunciation", &ExtractionWay::pronunciation)
+             .def_readwrite("destinations", &ExtractionWay::destinations)
+             .def_readwrite("roundabout", &ExtractionWay::roundabout)
+             .def_readwrite("is_access_restricted", &ExtractionWay::is_access_restricted)
+             .def_readwrite("is_startpoint", &ExtractionWay::is_startpoint)
+             .def_readwrite("duration", &ExtractionWay::duration)
+             .def_readwrite("turn_lanes_forward", &ExtractionWay::turn_lanes_forward)
+             .def_readwrite("turn_lanes_backward", &ExtractionWay::turn_lanes_backward)
+             .def_readwrite("road_classification", &ExtractionWay::road_classification)
+             .property(
+                 "forward_mode", &ExtractionWay::get_forward_mode, &ExtractionWay::set_forward_mode)
+             .property("backward_mode",
+                       &ExtractionWay::get_backward_mode,
+                       &ExtractionWay::set_backward_mode),
+         luabind::class_<osmium::WayNodeList>("WayNodeList").def(luabind::constructor<>()),
+         luabind::class_<osmium::NodeRef>("NodeRef")
+             .def(luabind::constructor<>())
+             // Dear ambitious reader: registering .location() as in:
+             // .def("location", +[](const osmium::NodeRef& nref){ return nref.location(); })
+             // will crash at runtime, since we're not (yet?) using libosnmium's
+             // NodeLocationsForWays cache
+             .def("id", &osmium::NodeRef::ref),
+         luabind::class_<osmium::Way>("Way")
+             .def("get_value_by_key", &osmium::Way::get_value_by_key)
+             .def("get_value_by_key", &get_value_by_key<osmium::Way>)
+             .def("id", &osmium::Way::id)
+             .def("get_nodes", get_nodes_for_way, luabind::return_stl_iterator),
+         luabind::class_<InternalExtractorEdge>("EdgeSource")
+             .def_readonly("source_coordinate", &InternalExtractorEdge::source_coordinate)
+             .def_readwrite("weight_data", &InternalExtractorEdge::weight_data),
+         luabind::class_<InternalExtractorEdge::WeightData>("WeightData")
+             .def_readwrite("speed", &InternalExtractorEdge::WeightData::speed),
+         luabind::class_<ExternalMemoryNode>("EdgeTarget")
+             .property("lon", &lonToDouble<ExternalMemoryNode>)
+             .property("lat", &latToDouble<ExternalMemoryNode>),
+         luabind::class_<util::Coordinate>("Coordinate")
+             .property("lon", &lonToDouble<util::Coordinate>)
+             .property("lat", &latToDouble<util::Coordinate>),
+         luabind::class_<RasterDatum>("RasterDatum")
+             .def_readonly("datum", &RasterDatum::datum)
+             .def("invalid_data", &RasterDatum::get_invalid)];
+
+    luabind::globals(context.state)["properties"] = &context.properties;
+    luabind::globals(context.state)["sources"] = &context.sources;
+
+    if (0 != luaL_dofile(context.state, file_name.c_str()))
+    {
+        luabind::object error_msg(luabind::from_stack(context.state, -1));
+        std::ostringstream error_stream;
+        error_stream << error_msg;
+        throw util::exception("ERROR occurred in profile script:\n" + error_stream.str());
+    }
+
+    context.has_turn_penalty_function = util::luaFunctionExists(context.state, "turn_function");
+    context.has_node_function = util::luaFunctionExists(context.state, "node_function");
+    context.has_way_function = util::luaFunctionExists(context.state, "way_function");
+    context.has_segment_function = util::luaFunctionExists(context.state, "segment_function");
+}
+
+const ProfileProperties &LuaScriptingEnvironment::GetProfileProperties()
+{
+    return GetLuaContext().properties;
+}
+
+LuaScriptingContext &LuaScriptingEnvironment::GetLuaContext()
+{
+    std::lock_guard<std::mutex> lock(init_mutex);
+    bool initialized = false;
+    auto &ref = script_contexts.local(initialized);
+    if (!initialized)
+    {
+        ref = util::make_unique<LuaScriptingContext>();
+        InitContext(*ref);
+    }
+    luabind::set_pcall_callback(&luaErrorCallback);
+
+    return *ref;
+}
+
+void LuaScriptingEnvironment::ProcessElements(
+    const std::vector<osmium::memory::Buffer::const_iterator> &osm_elements,
+    const RestrictionParser &restriction_parser,
+    tbb::concurrent_vector<std::pair<std::size_t, ExtractionNode>> &resulting_nodes,
+    tbb::concurrent_vector<std::pair<std::size_t, ExtractionWay>> &resulting_ways,
+    tbb::concurrent_vector<boost::optional<InputRestrictionContainer>> &resulting_restrictions)
+{
+    // parse OSM entities in parallel, store in resulting vectors
+    tbb::parallel_for(
+        tbb::blocked_range<std::size_t>(0, osm_elements.size()),
+        [&](const tbb::blocked_range<std::size_t> &range) {
+            ExtractionNode result_node;
+            ExtractionWay result_way;
+            auto &local_context = this->GetLuaContext();
+
+            for (auto x = range.begin(), end = range.end(); x != end; ++x)
+            {
+                const auto entity = osm_elements[x];
+
+                switch (entity->type())
+                {
+                case osmium::item_type::node:
+                    result_node.clear();
+                    if (local_context.has_node_function)
+                    {
+                        local_context.processNode(static_cast<const osmium::Node &>(*entity),
+                                                  result_node);
+                    }
+                    resulting_nodes.push_back(std::make_pair(x, std::move(result_node)));
+                    break;
+                case osmium::item_type::way:
+                    result_way.clear();
+                    if (local_context.has_way_function)
+                    {
+                        local_context.processWay(static_cast<const osmium::Way &>(*entity),
+                                                 result_way);
+                    }
+                    resulting_ways.push_back(std::make_pair(x, std::move(result_way)));
+                    break;
+                case osmium::item_type::relation:
+                    resulting_restrictions.push_back(restriction_parser.TryParse(
+                        static_cast<const osmium::Relation &>(*entity)));
+                    break;
+                default:
+                    break;
+                }
+            }
+        });
+}
+
+std::vector<std::string> LuaScriptingEnvironment::GetNameSuffixList()
+{
+    auto &context = GetLuaContext();
+    BOOST_ASSERT(context.state != nullptr);
+    if (!util::luaFunctionExists(context.state, "get_name_suffix_list"))
+        return {};
+
+    std::vector<std::string> suffixes_vector;
+    try
+    {
+        // call lua profile to compute turn penalty
+        luabind::call_function<void>(
+            context.state, "get_name_suffix_list", boost::ref(suffixes_vector));
+    }
+    catch (const luabind::error &er)
+    {
+        util::SimpleLogger().Write(logWARNING) << er.what();
+    }
+
+    return suffixes_vector;
+}
+
+std::vector<std::string> LuaScriptingEnvironment::GetExceptions()
+{
+    auto &context = GetLuaContext();
+    BOOST_ASSERT(context.state != nullptr);
+    std::vector<std::string> restriction_exceptions;
+    if (util::luaFunctionExists(context.state, "get_exceptions"))
+    {
+        // get list of turn restriction exceptions
+        luabind::call_function<void>(
+            context.state, "get_exceptions", boost::ref(restriction_exceptions));
+    }
+    return restriction_exceptions;
+}
+
+void LuaScriptingEnvironment::SetupSources()
+{
+    auto &context = GetLuaContext();
+    BOOST_ASSERT(context.state != nullptr);
+    if (util::luaFunctionExists(context.state, "source_function"))
+    {
+        luabind::call_function<void>(context.state, "source_function");
+    }
+}
+
+int32_t LuaScriptingEnvironment::GetTurnPenalty(const double angle)
+{
+    auto &context = GetLuaContext();
+    if (context.has_turn_penalty_function)
+    {
+        BOOST_ASSERT(context.state != nullptr);
+        try
+        {
+            // call lua profile to compute turn penalty
+            const double penalty =
+                luabind::call_function<double>(context.state, "turn_function", angle);
+            BOOST_ASSERT(penalty < std::numeric_limits<int32_t>::max());
+            BOOST_ASSERT(penalty > std::numeric_limits<int32_t>::min());
+            return boost::numeric_cast<int32_t>(penalty);
+        }
+        catch (const luabind::error &er)
+        {
+            util::SimpleLogger().Write(logWARNING) << er.what();
+        }
+    }
+    return 0;
+}
+
+void LuaScriptingEnvironment::ProcessSegment(const osrm::util::Coordinate &source,
+                                             const osrm::util::Coordinate &target,
+                                             double distance,
+                                             InternalExtractorEdge::WeightData &weight)
+{
+    auto &context = GetLuaContext();
+    if (context.has_segment_function)
+    {
+        BOOST_ASSERT(context.state != nullptr);
+        luabind::call_function<void>(context.state,
+                                     "segment_function",
+                                     boost::cref(source),
+                                     boost::cref(target),
+                                     distance,
+                                     boost::ref(weight));
+    }
+}
+
+void LuaScriptingContext::processNode(const osmium::Node &node, ExtractionNode &result)
+{
+    BOOST_ASSERT(state != nullptr);
+    luabind::call_function<void>(state, "node_function", boost::cref(node), boost::ref(result));
+}
+
+void LuaScriptingContext::processWay(const osmium::Way &way, ExtractionWay &result)
+{
+    BOOST_ASSERT(state != nullptr);
+    luabind::call_function<void>(state, "way_function", boost::cref(way), boost::ref(result));
+}
+}
+}
diff --git a/src/extractor/suffix_table.cpp b/src/extractor/suffix_table.cpp
index 10d54bb..f160fcd 100644
--- a/src/extractor/suffix_table.cpp
+++ b/src/extractor/suffix_table.cpp
@@ -1,38 +1,17 @@
 #include "extractor/suffix_table.hpp"
 
-#include "util/lua_util.hpp"
-#include "util/simple_logger.hpp"
+#include "extractor/scripting_environment.hpp"
 
 #include <boost/algorithm/string.hpp>
-#include <boost/assert.hpp>
-#include <boost/ref.hpp>
-
-#include <iterator>
-#include <vector>
 
 namespace osrm
 {
 namespace extractor
 {
 
-SuffixTable::SuffixTable(lua_State *lua_state)
+SuffixTable::SuffixTable(ScriptingEnvironment &scripting_environment)
 {
-    BOOST_ASSERT(lua_state != nullptr);
-    if (!util::luaFunctionExists(lua_state, "get_name_suffix_list"))
-        return;
-
-    std::vector<std::string> suffixes_vector;
-    try
-    {
-        // call lua profile to compute turn penalty
-        luabind::call_function<void>(
-            lua_state, "get_name_suffix_list", boost::ref(suffixes_vector));
-    }
-    catch (const luabind::error &er)
-    {
-        util::SimpleLogger().Write(logWARNING) << er.what();
-    }
-
+    std::vector<std::string> suffixes_vector = scripting_environment.GetNameSuffixList();
     for (auto &suffix : suffixes_vector)
         boost::algorithm::to_lower(suffix);
     suffix_set.insert(std::begin(suffixes_vector), std::end(suffixes_vector));
diff --git a/src/osrm/osrm.cpp b/src/osrm/osrm.cpp
index f9add44..a474eb1 100644
--- a/src/osrm/osrm.cpp
+++ b/src/osrm/osrm.cpp
@@ -21,32 +21,32 @@ OSRM &OSRM::operator=(OSRM &&) noexcept = default;
 
 // Forward to implementation
 
-engine::Status OSRM::Route(const engine::api::RouteParameters &params, util::json::Object &result)
+engine::Status OSRM::Route(const engine::api::RouteParameters &params, util::json::Object &result) const
 {
     return engine_->Route(params, result);
 }
 
-engine::Status OSRM::Table(const engine::api::TableParameters &params, json::Object &result)
+engine::Status OSRM::Table(const engine::api::TableParameters &params, json::Object &result) const
 {
     return engine_->Table(params, result);
 }
 
-engine::Status OSRM::Nearest(const engine::api::NearestParameters &params, json::Object &result)
+engine::Status OSRM::Nearest(const engine::api::NearestParameters &params, json::Object &result) const
 {
     return engine_->Nearest(params, result);
 }
 
-engine::Status OSRM::Trip(const engine::api::TripParameters &params, json::Object &result)
+engine::Status OSRM::Trip(const engine::api::TripParameters &params, json::Object &result) const
 {
     return engine_->Trip(params, result);
 }
 
-engine::Status OSRM::Match(const engine::api::MatchParameters &params, json::Object &result)
+engine::Status OSRM::Match(const engine::api::MatchParameters &params, json::Object &result) const
 {
     return engine_->Match(params, result);
 }
 
-engine::Status OSRM::Tile(const engine::api::TileParameters &params, std::string &result)
+engine::Status OSRM::Tile(const engine::api::TileParameters &params, std::string &result) const
 {
     return engine_->Tile(params, result);
 }
diff --git a/src/storage/storage.cpp b/src/storage/storage.cpp
index 119549d..c89ee63 100644
--- a/src/storage/storage.cpp
+++ b/src/storage/storage.cpp
@@ -1,3 +1,4 @@
+#include "storage/storage.hpp"
 #include "contractor/query_edge.hpp"
 #include "extractor/compressed_edge_container.hpp"
 #include "extractor/guidance/turn_instruction.hpp"
@@ -8,7 +9,6 @@
 #include "storage/shared_barriers.hpp"
 #include "storage/shared_datatype.hpp"
 #include "storage/shared_memory.hpp"
-#include "storage/storage.hpp"
 #include "engine/datafacade/datafacade_base.hpp"
 #include "util/coordinate.hpp"
 #include "util/exception.hpp"
@@ -293,11 +293,12 @@ int Storage::Run()
         throw util::exception("Could not open " + config.datasource_indexes_path.string() +
                               " for reading.");
     }
-    std::size_t number_of_compressed_datasources = 0;
+    std::uint64_t number_of_compressed_datasources = 0;
     if (geometry_datasource_input_stream)
     {
         geometry_datasource_input_stream.read(
-            reinterpret_cast<char *>(&number_of_compressed_datasources), sizeof(std::size_t));
+            reinterpret_cast<char *>(&number_of_compressed_datasources),
+            sizeof(number_of_compressed_datasources));
     }
     shared_layout_ptr->SetBlockSize<uint8_t>(SharedDataLayout::DATASOURCES_LIST,
                                              number_of_compressed_datasources);
@@ -375,7 +376,7 @@ int Storage::Run()
     }
 
     std::uint64_t num_bearings;
-    intersection_stream.read(reinterpret_cast<char*>(&num_bearings),sizeof(num_bearings));
+    intersection_stream.read(reinterpret_cast<char *>(&num_bearings), sizeof(num_bearings));
 
     std::vector<DiscreteBearing> bearing_class_table(num_bearings);
     intersection_stream.read(reinterpret_cast<char *>(&bearing_class_table[0]),
diff --git a/src/tools/components.cpp b/src/tools/components.cpp
index 71d59f2..f2e4cf7 100644
--- a/src/tools/components.cpp
+++ b/src/tools/components.cpp
@@ -102,7 +102,7 @@ std::size_t loadGraph(const char *path,
 }
 }
 
-int main(int argc, char *argv[]) try
+int main(int argc, char *argv[])
 {
     std::vector<osrm::extractor::QueryNode> coordinate_list;
     osrm::util::LogPolicy::GetInstance().Unmute();
@@ -228,8 +228,3 @@ int main(int argc, char *argv[]) try
     osrm::util::SimpleLogger().Write() << "finished component analysis";
     return EXIT_SUCCESS;
 }
-catch (const std::exception &e)
-{
-    osrm::util::SimpleLogger().Write(logWARNING) << "[exception] " << e.what();
-    return EXIT_FAILURE;
-}
diff --git a/src/tools/contract.cpp b/src/tools/contract.cpp
index c21af0c..e7caee2 100644
--- a/src/tools/contract.cpp
+++ b/src/tools/contract.cpp
@@ -77,11 +77,19 @@ return_code parseArguments(int argc, char *argv[], contractor::ContractorConfig
 
     // parse command line options
     boost::program_options::variables_map option_variables;
-    boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
-                                      .options(cmdline_options)
-                                      .positional(positional_options)
-                                      .run(),
-                                  option_variables);
+    try
+    {
+        boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
+                                          .options(cmdline_options)
+                                          .positional(positional_options)
+                                          .run(),
+                                      option_variables);
+    }
+    catch (const boost::program_options::error &e)
+    {
+        util::SimpleLogger().Write(logWARNING) << "[error] " << e.what();
+        return return_code::fail;
+    }
 
     if (option_variables.count("version"))
     {
@@ -162,8 +170,3 @@ catch (const std::bad_alloc &e)
         << "Please provide more memory or consider using a larger swapfile";
     return EXIT_FAILURE;
 }
-catch (const std::exception &e)
-{
-    util::SimpleLogger().Write(logWARNING) << "[exception] " << e.what();
-    return EXIT_FAILURE;
-}
diff --git a/src/tools/extract.cpp b/src/tools/extract.cpp
index cc5915c..6d0104a 100644
--- a/src/tools/extract.cpp
+++ b/src/tools/extract.cpp
@@ -1,5 +1,6 @@
 #include "extractor/extractor.hpp"
 #include "extractor/extractor_config.hpp"
+#include "extractor/scripting_environment_lua.hpp"
 #include "util/simple_logger.hpp"
 #include "util/version.hpp"
 
@@ -72,40 +73,41 @@ return_code parseArguments(int argc, char *argv[], extractor::ExtractorConfig &e
     visible_options.add(generic_options).add(config_options);
 
     // parse command line options
+    boost::program_options::variables_map option_variables;
     try
     {
-        boost::program_options::variables_map option_variables;
         boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
                                           .options(cmdline_options)
                                           .positional(positional_options)
                                           .run(),
                                       option_variables);
-        if (option_variables.count("version"))
-        {
-            util::SimpleLogger().Write() << OSRM_VERSION;
-            return return_code::exit;
-        }
-
-        if (option_variables.count("help"))
-        {
-            util::SimpleLogger().Write() << visible_options;
-            return return_code::exit;
-        }
-
-        boost::program_options::notify(option_variables);
-
-        if (!option_variables.count("input"))
-        {
-            util::SimpleLogger().Write() << visible_options;
-            return return_code::exit;
-        }
     }
-    catch (std::exception &e)
+    catch (const boost::program_options::error &e)
     {
-        util::SimpleLogger().Write(logWARNING) << e.what();
+        util::SimpleLogger().Write(logWARNING) << "[error] " << e.what();
         return return_code::fail;
     }
 
+    if (option_variables.count("version"))
+    {
+        util::SimpleLogger().Write() << OSRM_VERSION;
+        return return_code::exit;
+    }
+
+    if (option_variables.count("help"))
+    {
+        util::SimpleLogger().Write() << visible_options;
+        return return_code::exit;
+    }
+
+    boost::program_options::notify(option_variables);
+
+    if (!option_variables.count("input"))
+    {
+        util::SimpleLogger().Write() << visible_options;
+        return return_code::exit;
+    }
+
     return return_code::ok;
 }
 
@@ -147,7 +149,11 @@ int main(int argc, char *argv[]) try
             << "Profile " << extractor_config.profile_path.string() << " not found!";
         return EXIT_FAILURE;
     }
-    return extractor::Extractor(extractor_config).run();
+
+    // setup scripting environment
+    extractor::LuaScriptingEnvironment scripting_environment(
+        extractor_config.profile_path.string().c_str());
+    return extractor::Extractor(extractor_config).run(scripting_environment);
 }
 catch (const std::bad_alloc &e)
 {
@@ -156,8 +162,3 @@ catch (const std::bad_alloc &e)
         << "Please provide more memory or consider using a larger swapfile";
     return EXIT_FAILURE;
 }
-catch (const std::exception &e)
-{
-    util::SimpleLogger().Write(logWARNING) << "[exception] " << e.what();
-    return EXIT_FAILURE;
-}
diff --git a/src/tools/io-benchmark.cpp b/src/tools/io-benchmark.cpp
index 1cabd2b..ebbd27b 100644
--- a/src/tools/io-benchmark.cpp
+++ b/src/tools/io-benchmark.cpp
@@ -49,7 +49,7 @@ void runStatistics(std::vector<double> &timings_vector, Statistics &stats)
 
 boost::filesystem::path test_path;
 
-int main(int argc, char *argv[]) try
+int main(int argc, char *argv[])
 {
 
 #ifdef __FreeBSD__
@@ -312,14 +312,3 @@ int main(int argc, char *argv[]) try
     return EXIT_SUCCESS;
 #endif
 }
-catch (const std::exception &e)
-{
-    osrm::util::SimpleLogger().Write(logWARNING) << "caught exception: " << e.what();
-    osrm::util::SimpleLogger().Write(logWARNING) << "cleaning up, and exiting";
-    if (boost::filesystem::exists(test_path))
-    {
-        boost::filesystem::remove(test_path);
-        osrm::util::SimpleLogger().Write(logWARNING) << "removing temporary files";
-    }
-    return EXIT_FAILURE;
-}
diff --git a/src/tools/routed.cpp b/src/tools/routed.cpp
index 612f7dd..d087302 100644
--- a/src/tools/routed.cpp
+++ b/src/tools/routed.cpp
@@ -63,7 +63,8 @@ inline unsigned generateServerProgramOptions(const int argc,
                                              int &max_locations_trip,
                                              int &max_locations_viaroute,
                                              int &max_locations_distance_table,
-                                             int &max_locations_map_matching)
+                                             int &max_locations_map_matching,
+                                             int &max_results_nearest)
 {
     using boost::program_options::value;
     using boost::filesystem::path;
@@ -100,7 +101,10 @@ inline unsigned generateServerProgramOptions(const int argc,
          "Max. locations supported in distance table query") //
         ("max-matching-size",
          value<int>(&max_locations_map_matching)->default_value(100),
-         "Max. locations supported in map matching query");
+         "Max. locations supported in map matching query") //
+        ("max-nearest-size",
+         value<int>(&max_results_nearest)->default_value(100),
+         "Max. results supported in nearest query");
 
     // hidden options, will be allowed on command line, but will not be shown to the user
     boost::program_options::options_description hidden_options("Hidden options");
@@ -122,11 +126,19 @@ inline unsigned generateServerProgramOptions(const int argc,
 
     // parse command line options
     boost::program_options::variables_map option_variables;
-    boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
-                                      .options(cmdline_options)
-                                      .positional(positional_options)
-                                      .run(),
-                                  option_variables);
+    try
+    {
+        boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
+                                          .options(cmdline_options)
+                                          .positional(positional_options)
+                                          .run(),
+                                      option_variables);
+    }
+    catch (const boost::program_options::error &e)
+    {
+        util::SimpleLogger().Write(logWARNING) << "[error] " << e.what();
+        return INIT_FAILED;
+    }
 
     if (option_variables.count("version"))
     {
@@ -181,7 +193,8 @@ int main(int argc, const char *argv[]) try
                                                               config.max_locations_trip,
                                                               config.max_locations_viaroute,
                                                               config.max_locations_distance_table,
-                                                              config.max_locations_map_matching);
+                                                              config.max_locations_map_matching,
+                                                              config.max_results_nearest);
     if (init_result == INIT_OK_DO_NOT_START_ENGINE)
     {
         return EXIT_SUCCESS;
@@ -371,8 +384,3 @@ catch (const std::bad_alloc &e)
         << "Please provide more memory or consider using a larger swapfile";
     return EXIT_FAILURE;
 }
-catch (const std::exception &e)
-{
-    util::SimpleLogger().Write(logWARNING) << "[exception] " << e.what();
-    return EXIT_FAILURE;
-}
diff --git a/src/tools/springclean.cpp b/src/tools/springclean.cpp
index 536456b..cb5794e 100644
--- a/src/tools/springclean.cpp
+++ b/src/tools/springclean.cpp
@@ -53,7 +53,7 @@ void springclean()
 }
 }
 
-int main() try
+int main()
 {
     osrm::util::LogPolicy::GetInstance().Unmute();
     osrm::util::SimpleLogger().Write() << "Releasing all locks";
@@ -74,8 +74,3 @@ int main() try
     osrm::tools::springclean();
     return EXIT_SUCCESS;
 }
-catch (const std::exception &e)
-{
-    osrm::util::SimpleLogger().Write(logWARNING) << "[excpetion] " << e.what();
-    return EXIT_FAILURE;
-}
diff --git a/src/tools/store.cpp b/src/tools/store.cpp
index c249d88..1d4bbb1 100644
--- a/src/tools/store.cpp
+++ b/src/tools/store.cpp
@@ -50,11 +50,20 @@ bool generateDataStoreOptions(const int argc,
 
     // parse command line options
     boost::program_options::variables_map option_variables;
-    boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
-                                      .options(cmdline_options)
-                                      .positional(positional_options)
-                                      .run(),
-                                  option_variables);
+
+    try
+    {
+        boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
+                                          .options(cmdline_options)
+                                          .positional(positional_options)
+                                          .run(),
+                                      option_variables);
+    }
+    catch (const boost::program_options::error &e)
+    {
+        util::SimpleLogger().Write(logWARNING) << "[error] " << e.what();
+        return false;
+    }
 
     if (option_variables.count("version"))
     {
@@ -99,8 +108,3 @@ catch (const std::bad_alloc &e)
            "address space (note: this makes OSRM swap, i.e. slow)";
     return EXIT_FAILURE;
 }
-catch (const std::exception &e)
-{
-    util::SimpleLogger().Write(logWARNING) << "caught exception: " << e.what();
-    return EXIT_FAILURE;
-}
diff --git a/src/tools/unlock_all_mutexes.cpp b/src/tools/unlock_all_mutexes.cpp
index abb5d86..556f5b7 100644
--- a/src/tools/unlock_all_mutexes.cpp
+++ b/src/tools/unlock_all_mutexes.cpp
@@ -3,7 +3,7 @@
 
 #include <iostream>
 
-int main() try
+int main()
 {
     osrm::util::LogPolicy::GetInstance().Unmute();
     osrm::util::SimpleLogger().Write() << "Releasing all locks";
@@ -13,8 +13,3 @@ int main() try
     barrier.update_mutex.unlock();
     return 0;
 }
-catch (const std::exception &e)
-{
-    osrm::util::SimpleLogger().Write(logWARNING) << "[excpetion] " << e.what();
-    return EXIT_FAILURE;
-}
diff --git a/src/util/coordinate_calculation.cpp b/src/util/coordinate_calculation.cpp
index 029129b..9dff34a 100644
--- a/src/util/coordinate_calculation.cpp
+++ b/src/util/coordinate_calculation.cpp
@@ -284,8 +284,10 @@ Coordinate interpolateLinear(double factor, const Coordinate from, const Coordin
     const auto to_lon = static_cast<std::int32_t>(to.lon);
     const auto to_lat = static_cast<std::int32_t>(to.lat);
 
-    FixedLongitude interpolated_lon{static_cast<std::int32_t>(from_lon + factor * (to_lon - from_lon))};
-    FixedLatitude interpolated_lat{static_cast<std::int32_t>(from_lat + factor * (to_lat - from_lat))};
+    FixedLongitude interpolated_lon{
+        static_cast<std::int32_t>(from_lon + factor * (to_lon - from_lon))};
+    FixedLatitude interpolated_lat{
+        static_cast<std::int32_t>(from_lat + factor * (to_lat - from_lat))};
 
     return {std::move(interpolated_lon), std::move(interpolated_lat)};
 }
diff --git a/src/util/name_table.cpp b/src/util/name_table.cpp
index e15a35f..66a9bc4 100644
--- a/src/util/name_table.cpp
+++ b/src/util/name_table.cpp
@@ -22,6 +22,9 @@ NameTable::NameTable(const std::string &filename)
 
     name_stream >> m_name_table;
 
+    if (!name_stream)
+        throw exception("Unable to deserialize RangeTable for NameTable");
+
     unsigned number_of_chars = 0;
     name_stream.read(reinterpret_cast<char *>(&number_of_chars), sizeof(number_of_chars));
     if (!name_stream)
@@ -64,5 +67,23 @@ std::string NameTable::GetNameForID(const unsigned name_id) const
     }
     return result;
 }
+
+std::string NameTable::GetRefForID(const unsigned name_id) const
+{
+    // Way string data is stored in blocks based on `name_id` as follows:
+    //
+    // | name | destination | pronunciation | ref |
+    //                                      ^     ^
+    //                                      [range)
+    //                                       ^ name_id + 3
+    //
+    // `name_id + offset` gives us the range of chars.
+    //
+    // Offset 0 is name, 1 is destination, 2 is pronunciation, 3 is ref.
+    // See datafacades and extractor callbacks for details.
+    const constexpr auto OFFSET_REF = 3u;
+    return GetNameForID(name_id + OFFSET_REF);
+}
+
 } // namespace util
 } // namespace osrm
diff --git a/taginfo.json b/taginfo.json
index f83dc0d..ed64317 100644
--- a/taginfo.json
+++ b/taginfo.json
@@ -20,6 +20,16 @@
             "object_types": [ "way" ]
         },
         {
+            "key": "service",
+            "value": "alley",
+            "object_types": [ "way" ]
+        },
+        {
+            "key": "service",
+            "value": "emergency_access",
+            "object_types": [ "way" ]
+        },
+        {
             "key": "oneway",
             "value": "true",
             "object_types": [ "way" ]
@@ -47,6 +57,36 @@
             "description": "Roads with area=yes are ignored by default."
         },
         {
+            "key": "hov",
+            "value": "designated",
+            "object_types": [ "way" ],
+            "description": "Roads with hov=designated are ignored by default."
+        },
+        {
+            "key": "hov:lanes",
+            "value": "designated",
+            "object_types": [ "way" ],
+            "description": "Roads with hov:lanes all-designated are ignored by default."
+        },
+        {
+            "key": "hov:lanes:forward",
+            "value": "designated",
+            "object_types": [ "way" ],
+            "description": "Roads with hov:lanes:forward all-designated are ignored by default."
+        },
+        {
+            "key": "hov:lanes:backward",
+            "value": "designated",
+            "object_types": [ "way" ],
+            "description": "Roads with hov:lanes:backward all-designated are ignored by default."
+        },
+        {
+            "key": "toll",
+            "value": "yes",
+            "object_types": [ "way" ],
+            "description": "Roads with toll=yes are ignored by default."
+        },
+        {
             "key": "impassable",
             "description": "This is used by HOT."
         },
@@ -104,6 +144,8 @@
         {"key": "maxspeed", "value": "uk:nsl_single"},
         {"key": "maxspeed", "value": "uk:nsl_dual"},
         {"key": "maxspeed", "value": "uk:motorway"},
+        {"key": "maxspeed", "value": "nl:rural"},
+        {"key": "maxspeed", "value": "nl:trunk"},
         {"key": "smoothness", "value": "intermediate"},
         {"key": "smoothness", "value": "bad"},
         {"key": "smoothness", "value": "very_bad"},
@@ -206,16 +248,31 @@
             "description": "Turn Lanes for lane guidance."
         },
         {
+            "key": "vehicle:lanes",
+            "object_types": [ "way" ],
+            "description": "Access tags for turn lanes."
+        },
+        {
             "key": "turn:lanes:forward",
             "object_types": [ "way" ],
             "description": "Turn Lanes for lane guidance."
         },
         {
+            "key": "vehicle:lanes:forward",
+            "object_types": [ "way" ],
+            "description": "Access tags for turn lanes."
+        },
+        {
             "key": "turn:lanes:backward",
             "object_types": [ "way" ],
             "description": "Turn Lanes for lane guidance."
         },
-         {
+        {
+            "key": "vehicle:lanes:backward",
+            "object_types": [ "way" ],
+            "description": "Access tags for turn lanes."
+        },
+        {
             "key": "lanes:psv",
             "object_types": [ "way" ],
             "description": "Turn Lanes for lane guidance."
diff --git a/unit_tests/engine/json_factory.cpp b/unit_tests/engine/json_factory.cpp
new file mode 100644
index 0000000..a246af0
--- /dev/null
+++ b/unit_tests/engine/json_factory.cpp
@@ -0,0 +1,16 @@
+#include "engine/api/json_factory.hpp"
+
+#include <boost/test/test_case_template.hpp>
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_SUITE(json_factory)
+
+BOOST_AUTO_TEST_CASE(instructionTypeToString_test_size)
+{
+    using namespace osrm::engine::api::json::detail;
+    using namespace osrm::extractor::guidance;
+
+    BOOST_CHECK_EQUAL(instructionTypeToString(TurnType::Sliproad), "invalid");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/unit_tests/library/limits.cpp b/unit_tests/library/limits.cpp
index ad4b3fb..b8f721b 100644
--- a/unit_tests/library/limits.cpp
+++ b/unit_tests/library/limits.cpp
@@ -4,6 +4,7 @@
 #include "args.hpp"
 
 #include "osrm/match_parameters.hpp"
+#include "osrm/nearest_parameters.hpp"
 #include "osrm/route_parameters.hpp"
 #include "osrm/table_parameters.hpp"
 #include "osrm/trip_parameters.hpp"
@@ -136,4 +137,33 @@ BOOST_AUTO_TEST_CASE(test_match_limits)
     BOOST_CHECK(code == "TooBig"); // per the New-Server API spec
 }
 
+BOOST_AUTO_TEST_CASE(test_nearest_limits)
+{
+    const auto args = get_args();
+    BOOST_REQUIRE_EQUAL(args.size(), 1);
+
+    using namespace osrm;
+
+    EngineConfig config;
+    config.storage_config = {args[0]};
+    config.use_shared_memory = false;
+    config.max_results_nearest = 2;
+
+    OSRM osrm{config};
+
+    NearestParameters params;
+    params.coordinates.emplace_back(util::FloatLongitude{}, util::FloatLatitude{});
+    params.number_of_results = 10000;
+
+    json::Object result;
+
+    const auto rc = osrm.Nearest(params, result);
+
+    BOOST_CHECK(rc == Status::Error);
+
+    // Make sure we're not accidentally hitting a guard code path before
+    const auto code = result.values["code"].get<json::String>().value;
+    BOOST_CHECK(code == "TooBig"); // per the New-Server API spec
+}
+
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/unit_tests/mocks/mock_datafacade.hpp b/unit_tests/mocks/mock_datafacade.hpp
index f36276c..ebe00d1 100644
--- a/unit_tests/mocks/mock_datafacade.hpp
+++ b/unit_tests/mocks/mock_datafacade.hpp
@@ -62,8 +62,10 @@ class MockDataFacade final : public engine::datafacade::BaseDataFacade
     {
     }
     void GetUncompressedWeights(const EdgeID /* id */,
-                                std::vector<EdgeWeight> & /* result_weights */) const override
+                                std::vector<EdgeWeight> &result_weights) const override
     {
+        result_weights.resize(1);
+        result_weights[0] = 1;
     }
     void GetUncompressedDatasources(const EdgeID /*id*/,
                                     std::vector<uint8_t> & /*data_sources*/) const override
@@ -173,6 +175,7 @@ class MockDataFacade final : public engine::datafacade::BaseDataFacade
     bool IsCoreNode(const NodeID /* id */) const override { return false; }
     unsigned GetNameIndexFromEdgeID(const unsigned /* id */) const override { return 0; }
     std::string GetNameForID(const unsigned /* name_id */) const override { return ""; }
+    std::string GetRefForID(const unsigned /* name_id */) const override { return ""; }
     std::string GetPronunciationForID(const unsigned /* name_id */) const override { return ""; }
     std::string GetDestinationsForID(const unsigned /* name_id */) const override { return ""; }
     std::size_t GetCoreSize() const override { return 0; }
diff --git a/unit_tests/util/bearing.cpp b/unit_tests/util/bearing.cpp
index 4a953c9..2d7fc9f 100644
--- a/unit_tests/util/bearing.cpp
+++ b/unit_tests/util/bearing.cpp
@@ -42,6 +42,9 @@ BOOST_AUTO_TEST_CASE(bearing_range_test)
 
     BOOST_CHECK_EQUAL(true, bearing::CheckInBounds(-721, 5, 10));
     BOOST_CHECK_EQUAL(true, bearing::CheckInBounds(719, 5, 10));
+
+    BOOST_CHECK_EQUAL(false, bearing::CheckInBounds(1, 1, -1));
+    BOOST_CHECK_EQUAL(true, bearing::CheckInBounds(1, 1, 0));
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/unit_tests/util/packed_vector.cpp b/unit_tests/util/packed_vector.cpp
index e95bc03..adb58d4 100644
--- a/unit_tests/util/packed_vector.cpp
+++ b/unit_tests/util/packed_vector.cpp
@@ -19,7 +19,7 @@ BOOST_AUTO_TEST_CASE(insert_and_retrieve_packed_test)
 
     for (std::size_t i = 0; i < num_test_cases; i++)
     {
-        OSMNodeID r {static_cast<std::uint64_t>(rand() % 2147483647)}; // max 33-bit uint
+        OSMNodeID r{static_cast<std::uint64_t>(rand() % 2147483647)}; // max 33-bit uint
 
         packed_ids.push_back(r);
         original_ids.push_back(r);
diff --git a/unit_tests/util/static_rtree.cpp b/unit_tests/util/static_rtree.cpp
index 66b81c5..7024492 100644
--- a/unit_tests/util/static_rtree.cpp
+++ b/unit_tests/util/static_rtree.cpp
@@ -165,6 +165,9 @@ struct GraphFixture
             // to examine during tests.
             d.forward_segment_id = {pair.second, true};
             d.reverse_segment_id = {pair.first, true};
+            d.fwd_segment_position = 0;
+            d.forward_packed_geometry_id = 0;
+            d.reverse_packed_geometry_id = 0;
             edges.emplace_back(d);
         }
     }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/osrm.git



More information about the Pkg-grass-devel mailing list