diff --git a/.travis.yml b/.travis.yml index 43af0ba42..f88b488b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,7 +72,7 @@ matrix: apt: sources: ['ubuntu-toolchain-r-test'] packages: ['g++-6', '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-6' CXXCOMPILER='g++-6' BUILD_TYPE='Debug' ENABLE_COVERAGE=ON + env: CCOMPILER='gcc-6' CXXCOMPILER='g++-6' BUILD_TYPE='Debug' ENABLE_COVERAGE=ON CUCUMBER_TIMEOUT=20000 after_success: - bash <(curl -s https://codecov.io/bash) @@ -358,6 +358,9 @@ script: if [ -z "${ENABLE_SANITIZER}" ] && [ "$TARGET_ARCH" != "i686" ]; then npm run nodejs-tests fi + - | + if [ "${ENABLE_MASON}" == "ON" ]; then + npm run test-conditionals + fi - popd - yarn test - diff --git a/CHANGELOG.md b/CHANGELOG.md index cac2874ad..aacf890e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 5.8.0 + - Changes from 5.7 + - Features + - Added conditional restriction support with `parse-conditional-restrictions=true|false` to osrm-extract. This option saves conditional turn restrictions to the .restrictions file for parsing by contract later. Added `parse-conditionals-from-now=utc time stamp` and `--time-zone-file=/path/to/file` to osrm-contract + # 5.7.0 - Changes from 5.6 - Algorithm: diff --git a/CMakeLists.txt b/CMakeLists.txt index a96363c02..dae2a1c0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -448,8 +448,9 @@ if(ENABLE_MASON) set(TBB_LIBRARIES ${MASON_PACKAGE_tbb_LDFLAGS}) mason_use(libshp2 VERSION ${MASON_LIBSHP_VERSION}) - set(LIBSHAPEFILE_INCLUDE_DIR ${MASON_PACKAGE_libshp2_INCLUDE_DIRS}) - set(LIBSHAPEFILE_LIBRARY ${MASON_PACKAGE_libshp2_LDFLAGS}) + target_include_directories(UTIL PRIVATE ${MASON_PACKAGE_libshp2_INCLUDE_DIRS}) + set(MAYBE_SHAPEFILE ${MASON_PACKAGE_libshp2_STATIC_LIBS}) + target_compile_definitions(UTIL PRIVATE COMPILE_DEFINITIONS ENABLE_SHAPEFILE) if(NOT MASON_PACKAGE_tbb_LIBRARY_DIRS) message(FATAL_ERROR "MASON_PACKAGE_tbb_LIBRARY_DIRS is empty, rpath will not work") @@ -524,6 +525,15 @@ else() ENDIF() ENDIF() + find_package(Shapefile) # optional package libshp-dev + if (Shapefile_FOUND) + set(MAYBE_SHAPEFILE "${LIBSHAPEFILE_LIBRARY}") + target_include_directories(UTIL PRIVATE ${LIBSHAPEFILE_INCLUDE_DIR}) + target_compile_definitions(UTIL PRIVATE COMPILE_DEFINITIONS ENABLE_SHAPEFILE) + else() + set(MAYBE_SHAPEFILE "") + endif() + set(USED_LUA_LIBRARIES ${LUA_LIBRARIES}) add_dependency_includes(${LUA_INCLUDE_DIR}) @@ -600,12 +610,12 @@ set(BOOST_ENGINE_LIBRARIES ${BOOST_BASE_LIBRARIES}) # Binaries -target_link_libraries(osrm-datastore osrm_store ${Boost_PROGRAM_OPTIONS_LIBRARY}) +target_link_libraries(osrm-datastore osrm_store ${Boost_PROGRAM_OPTIONS_LIBRARY} ${MAYBE_SHAPEFILE}) target_link_libraries(osrm-extract osrm_extract ${Boost_PROGRAM_OPTIONS_LIBRARY}) target_link_libraries(osrm-partition osrm_partition ${Boost_PROGRAM_OPTIONS_LIBRARY}) target_link_libraries(osrm-customize osrm_customize ${Boost_PROGRAM_OPTIONS_LIBRARY}) target_link_libraries(osrm-contract osrm_contract ${Boost_PROGRAM_OPTIONS_LIBRARY}) -target_link_libraries(osrm-routed osrm ${Boost_PROGRAM_OPTIONS_LIBRARY} ${OPTIONAL_SOCKET_LIBS} ${ZLIB_LIBRARY}) +target_link_libraries(osrm-routed osrm ${Boost_PROGRAM_OPTIONS_LIBRARY} ${MAYBE_SHAPEFILE} ${OPTIONAL_SOCKET_LIBS} ${ZLIB_LIBRARY}) set(EXTRACTOR_LIBRARIES ${BZIP2_LIBRARIES} @@ -630,6 +640,7 @@ set(CUSTOMIZER_LIBRARIES ${BOOST_ENGINE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${TBB_LIBRARIES} + ${MAYBE_SHAPEFILE} ${MAYBE_RT_LIBRARY} ${MAYBE_COVERAGE_LIBRARIES}) set(UPDATER_LIBRARIES @@ -637,13 +648,16 @@ set(UPDATER_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} ${TBB_LIBRARIES} ${MAYBE_RT_LIBRARY} - ${MAYBE_COVERAGE_LIBRARIES}) + ${MAYBE_SHAPEFILE} + ${MAYBE_COVERAGE_LIBRARIES} + ${ZLIB_LIBRARY}) set(CONTRACTOR_LIBRARIES ${BOOST_BASE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${USED_LUA_LIBRARIES} ${STXXL_LIBRARY} ${TBB_LIBRARIES} + ${MAYBE_SHAPEFILE} ${MAYBE_RT_LIBRARY} ${MAYBE_COVERAGE_LIBRARIES}) set(ENGINE_LIBRARIES @@ -664,7 +678,9 @@ set(UTIL_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} ${STXXL_LIBRARY} ${TBB_LIBRARIES} + ${MAYBE_SHAPEFILE} ${MAYBE_COVERAGE_LIBRARIES}) + # Libraries target_link_libraries(osrm ${ENGINE_LIBRARIES}) target_link_libraries(osrm_update ${UPDATER_LIBRARIES}) @@ -676,22 +692,21 @@ target_link_libraries(osrm_store ${STORAGE_LIBRARIES}) # BUILD_COMPONENTS add_executable(osrm-components src/tools/components.cpp $) -target_link_libraries(osrm-components ${TBB_LIBRARIES} ${BOOST_BASE_LIBRARIES}) +target_link_libraries(osrm-components ${TBB_LIBRARIES} ${BOOST_BASE_LIBRARIES} ${UTIL_LIBRARIES}) install(TARGETS osrm-components DESTINATION bin) if(BUILD_TOOLS) message(STATUS "Activating OSRM internal tools") add_executable(osrm-io-benchmark src/tools/io-benchmark.cpp $) - target_link_libraries(osrm-io-benchmark ${BOOST_BASE_LIBRARIES}) + target_link_libraries(osrm-io-benchmark ${BOOST_BASE_LIBRARIES} ${MAYBE_SHAPEFILE}) install(TARGETS osrm-io-benchmark DESTINATION bin) - find_package(Shapefile) # package libshp-dev if(SHAPEFILE_FOUND AND (Boost_VERSION VERSION_GREATER 106000 OR ENABLE_MASON)) add_executable(osrm-extract-conditionals src/tools/extract-conditionals.cpp $) target_include_directories(osrm-extract-conditionals PRIVATE ${LIBSHAPEFILE_INCLUDE_DIR}) target_link_libraries(osrm-extract-conditionals ${OSMIUM_LIBRARIES} ${BOOST_BASE_LIBRARIES} ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${LIBSHAPEFILE_LIBRARY} ${BZIP2_LIBRARIES} ${ZLIB_LIBRARY} ${EXPAT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + ${UTIL_LIBRARIES} ${BZIP2_LIBRARIES} ${ZLIB_LIBRARY} ${EXPAT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) install(TARGETS osrm-extract-conditionals DESTINATION bin) endif() endif() diff --git a/cucumber.js b/cucumber.js index 101bf8e95..f12fc96fc 100644 --- a/cucumber.js +++ b/cucumber.js @@ -1,7 +1,9 @@ module.exports = { - 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', + default: '--strict --tags ~@stress --tags ~@todo --tags ~@shapelib --require features/support --require features/step_definitions', + verify: '--strict --tags ~@stress --tags ~@todo --tags ~@shapelib -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', - mld: '--strict --tags ~@stress --tags ~@todo --tags ~@alternative --tags ~@matrix --tags ~@trip --require features/support --require features/step_definitions -f progress' + mld: '--strict --tags ~@stress --tags ~@todo --tags ~@alternative --tags ~@matrix --tags ~@trip --tags ~@shapelib --require features/support --require features/step_definitions -f progress', + conditionals: '--strict --tags @conditionals --require features/support --require features/step_definitions', + mld_conditionals: '--strict --tags @conditionals --require features/support --require features/step_definitions -f progress' } diff --git a/features/car/conditional_restrictions.feature b/features/car/conditional_restrictions.feature new file mode 100644 index 000000000..fe316e547 --- /dev/null +++ b/features/car/conditional_restrictions.feature @@ -0,0 +1,702 @@ +@routing @car @restrictions +Feature: Car - Turn restrictions +# Handle turn restrictions as defined by http://wiki.openstreetmap.org/wiki/Relation:restriction +# Note that if u-turns are allowed, turn restrictions can lead to suprising, but correct, routes. + + Background: Use car routing + Given the profile "car" + Given a grid size of 200 meters + Given the origin -9.2972,10.3811 + # coordinate in Guinée, a country that observes GMT year round + + @no_turning @conditionals + Scenario: Car - ignores unrecognized restriction + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | only_right_turn @ (has_pygmies > 10 p) | + + When I route I should get + | from | to | route | + | e | s | ej,js,js | + | e | n | ej,nj,nj | + | e | p | ej,jp,jp | + + @no_turning @conditionals + Scenario: Car - Restriction would be on, but the restriction was badly tagged + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + + Given the node map + """ + n + p | + \ | + j + | \ + s m + """ + + And the ways + | nodes | + | nj | + | js | + | pjm | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | nj | pjm | j | no_left_turn @ (Mo-Fr 07:00-10:30) | + | restriction | js | pjm | j | no_right_turn @ (Mo-Fr 07:00-10:30) | + + When I route I should get + | from | to | route | + | n | m | nj,pjm,pjm | + | s | m | js,pjm,pjm | + + @no_turning @conditionals + Scenario: Car - ignores except restriction + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | no | + | jp | no | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | except | + | restriction | ej | nj | j | only_right_turn @ (Mo-Su 08:00-12:00) | motorcar | + | restriction | jp | nj | j | only_left_turn @ (Mo-Su 08:00-12:00) | bus | + + When I route I should get + | from | to | route | # | + | e | s | ej,js,js | | + | e | n | ej,nj,nj | restriction does not apply to cars | + | e | p | ej,jp,jp | | + | p | s | jp,nj,nj,js,js | restriction excepting busses still applies to cars | + + @no_turning @conditionals + Scenario: Car - only_right_turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | only_right_turn @ (Mo-Su 07:00-14:00) | + + When I route I should get + | from | to | route | + | e | s | ej,nj,nj,js,js | + | e | n | ej,nj,nj | + | e | p | ej,nj,nj,jp,jp | + + @no_turning @conditionals + Scenario: Car - No right turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | no_right_turn @ (Mo-Fr 07:00-13:00) | + + When I route I should get + | from | to | route | # | + | e | s | ej,js,js | normal turn | + | e | n | ej,js,js,nj,nj | avoids right turn | + | e | p | ej,jp,jp | normal maneuver | + + @only_turning @conditionals + Scenario: Car - only_left_turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | js | j | only_left_turn @ (Mo-Fr 07:00-16:00) | + + When I route I should get + | from | to | route | + | e | s | ej,js,js | + | e | n | ej,js,js,nj,nj | + | e | p | ej,js,js,jp,jp | + + @no_turning @conditionals + Scenario: Car - No left turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | js | j | no_left_turn @ (Mo-Su 00:00-23:59) | + + When I route I should get + | from | to | route | + | e | s | ej,nj,nj,js,js | + | e | n | ej,nj,nj | + | e | p | ej,jp,jp | + + @no_turning @conditionals + Scenario: Car - Conditional restriction is off + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | no_right_turn @ (Mo-Su 16:00-20:00) | + + When I route I should get + | from | to | route | + | e | s | ej,js,js | + | e | n | ej,nj,nj | + | e | p | ej,jp,jp | + + @no_turning @conditionals + Scenario: Car - Conditional restriction is on + Given the extract extra arguments "--parse-conditional-restrictions" + # 10am utc, wed + Given the contract extra arguments "--parse-conditionals-from-now=1493805600" + Given the customize extra arguments "--parse-conditionals-from-now=1493805600" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | no_right_turn @ (Mo-Fr 07:00-14:00) | + + When I route I should get + | from | to | route | + | e | s | ej,js,js | + | e | n | ej,js,js,nj,nj | + | e | p | ej,jp,jp | + + @no_turning @conditionals + Scenario: Car - Conditional restriction with multiple time windows + Given the extract extra arguments "--parse-conditional-restrictions" + # 5pm Wed 02 May, 2017 GMT + Given the contract extra arguments "--parse-conditionals-from-now=1493744400" + Given the customize extra arguments "--parse-conditionals-from-now=1493744400" + + Given the node map + """ + n + p | + \ | + j + | \ + s m + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | jp | yes | + | mj | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | nj | jp | j | no_right_turn @ (Mo-Fr 07:00-11:00,16:00-18:30) | + + When I route I should get + | from | to | route | + | n | p | nj,js,js,jp,jp | + | m | p | mj,jp,jp | + + @only_turning @conditionals + Scenario: Car - Somewhere in Liverpool, the UK, GMT timezone + Given the extract extra arguments "--parse-conditional-restrictions=1" + # 9am UTC, 10am BST + Given the contract extra arguments "--parse-conditionals-from-now=1493802000" + Given the customize extra arguments "--parse-conditionals-from-now=1493802000" + + # """ + # a + # e + # b + # d + # c + # """ + Given the node locations + | node | lat | lon | + | a | 51.5250 | -0.1166 | + | b | 51.5243 | -0.1159 | + | c | 51.5238 | -0.1152 | + | d | 51.5241 | -0.1167 | + | e | 51.5247 | -0.1153 | + + And the ways + | nodes | name | + | ab | albic | + | bc | albic | + | db | dobe | + | be | dobe | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ab | be | b | only_left_turn @ (Mo-Fr 07:00-11:00) | + + When I route I should get + | from | to | route | turns | + | a | c | albic,dobe,dobe,albic,albic | depart,turn left,turn uturn,turn left,arrive | + | a | e | albic,dobe,dobe | depart,turn left,arrive | + + @shapelib @no_turning @conditionals + Scenario: Car - only_right_turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | only_right_turn @ (Mo-Su 07:00-14:00) | + + When I route I should get + | from | to | route | + | e | s | ej,nj,nj,js,js | + | e | n | ej,nj,nj | + | e | p | ej,nj,nj,jp,jp | + + @shapelib @no_turning @conditionals + Scenario: Car - No right turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | no_right_turn @ (Mo-Fr 07:00-13:00) | + + When I route I should get + | from | to | route | # | + | e | s | ej,js,js | normal turn | + | e | n | ej,js,js,nj,nj | avoids right turn | + | e | p | ej,jp,jp | normal maneuver | + + @shapelib @only_turning @conditionals + Scenario: Car - only_left_turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | js | j | only_left_turn @ (Mo-Fr 07:00-16:00) | + + When I route I should get + | from | to | route | + | e | s | ej,js,js | + | e | n | ej,js,js,nj,nj | + | e | p | ej,js,js,jp,jp | + + @shapelib @no_turning @conditionals + Scenario: Car - No left turn + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | js | j | no_left_turn @ (Mo-Su 00:00-23:59) | + + When I route I should get + | from | to | route | + | e | s | ej,nj,nj,js,js | + | e | n | ej,nj,nj | + | e | p | ej,jp,jp | + + @shapelib @no_turning @conditionals + Scenario: Car - Conditional restriction is off + Given the extract extra arguments "--parse-conditional-restrictions" + # time stamp for 10am on Tues, 02 May 2017 GMT + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493719200" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | no_right_turn @ (Mo-Su 16:00-20:00) | + + When I route I should get + | from | to | route | + | e | s | ej,js,js | + | e | n | ej,nj,nj | + | e | p | ej,jp,jp | + + @shapelib @no_turning @conditionals + Scenario: Car - Conditional restriction is on + Given the extract extra arguments "--parse-conditional-restrictions" + # 10am utc, wed + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493805600" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493805600" + Given the node map + """ + n + p j e + s + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | ej | yes | + | jp | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ej | nj | j | no_right_turn @ (Mo-Fr 07:00-14:00) | + + When I route I should get + | from | to | route | + | e | s | ej,js,js | + | e | n | ej,js,js,nj,nj | + | e | p | ej,jp,jp | + + @shapelib @no_turning @conditionals + Scenario: Car - Conditional restriction with multiple time windows + Given the extract extra arguments "--parse-conditional-restrictions" + # 5pm Wed 02 May, 2017 GMT + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493744400" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493744400" + + Given the node map + """ + n + p | + \ | + j + | \ + s m + """ + + And the ways + | nodes | oneway | + | nj | no | + | js | no | + | jp | yes | + | mj | yes | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | nj | jp | j | no_right_turn @ (Mo-Fr 07:00-11:00,16:00-18:30) | + + When I route I should get + | from | to | route | + | n | p | nj,js,js,jp,jp | + | m | p | mj,jp,jp | + + # https://www.openstreetmap.org/#map=18/38.91099/-77.00888 + @shapelib @no_turning @conditionals + Scenario: Car - DC North capitol situation, two on one off + Given the extract extra arguments "--parse-conditional-restrictions=1" + # 9pm Wed 02 May, 2017 UTC, 5pm EST + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493845200" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493845200" + + # """ + # a h + # d + # b g + # e + # c f + # """ + Given the node locations + | node | lat | lon | + | a | 38.9113 | -77.0091 | + | b | 38.9108 | -77.0091 | + | c | 38.9104 | -77.0091 | + | d | 38.9110 | -77.0096 | + | e | 38.9106 | -77.0086 | + | f | 38.9105 | -77.0090 | + | g | 38.9108 | -77.0090 | + | h | 38.9113 | -77.0090 | + + And the ways + | nodes | oneway | name | + | ab | yes | cap south | + | bc | yes | cap south | + | fg | yes | cap north | + | gh | yes | cap north | + | db | no | florida nw | + | bg | no | florida | + | ge | no | florida ne | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ab | bg | b | no_left_turn @ (Mo-Fr 07:00-09:30,16:00-18:30) | + | restriction | fg | bg | g | no_left_turn @ (Mo-Fr 06:00-10:00) | + | restriction | bg | bc | b | no_left_turn @ (Mo-Fr 07:00-09:30,16:00-18:30) | + + When I route I should get + | from | to | route | turns | + | a | e | cap south,florida nw,florida nw,florida ne | depart,turn right,turn uturn,arrive | + | f | d | cap north,florida,florida nw | depart,turn left,arrive | + | e | c | florida ne,florida nw,cap south,cap south | depart,continue uturn,turn right,arrive | + + @shapelib @no_turning @conditionals + Scenario: Car - DC North capitol situation, one on two off + Given the extract extra arguments "--parse-conditional-restrictions=1" + # 10:30am utc, wed, 6:30am est + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493807400" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493807400" + + # """ + # a h + # d + # b g + # e + # c f + # """ + Given the node locations + | node | lat | lon | + | a | 38.9113 | -77.0091 | + | b | 38.9108 | -77.0091 | + | c | 38.9104 | -77.0091 | + | d | 38.9110 | -77.0096 | + | e | 38.9106 | -77.0086 | + | f | 38.9105 | -77.0090 | + | g | 38.9108 | -77.0090 | + | h | 38.9113 | -77.0090 | + + And the ways + | nodes | oneway | name | + | ab | yes | cap south | + | bc | yes | cap south | + | fg | yes | cap north | + | gh | yes | cap north | + | db | no | florida nw | + | bg | no | florida | + | ge | no | florida ne | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ab | bg | b | no_left_turn @ (Mo-Fr 07:00-09:30,16:00-18:30) | + | restriction | fg | bg | g | no_left_turn @ (Mo-Fr 06:00-10:00) | + | restriction | bg | bc | b | no_left_turn @ (Mo-Fr 07:00-09:30,16:00-18:30) | + + When I route I should get + | from | to | route | turns | + | a | e | cap south,florida,florida ne | depart,turn left,arrive | + | f | d | cap north,florida ne,florida ne,florida nw | depart,turn sharp right,turn uturn,arrive | + | e | c | florida ne,cap south,cap south | depart,turn left,arrive | + + @shapelib @only_turning @conditionals + Scenario: Car - Somewhere in Liverpool, the UK, GMT timezone + Given the extract extra arguments "--parse-conditional-restrictions=1" + # 9am UTC, 10am BST + Given the contract extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493802000" + Given the customize extra arguments "--time-zone-file=test/data/tz/OGRGeoJSON.shp --parse-conditionals-from-now=1493802000" + + # """ + # a + # e + # b + # d + # c + # """ + Given the node locations + | node | lat | lon | + | a | 51.5250 | -0.1166 | + | b | 51.5243 | -0.1159 | + | c | 51.5238 | -0.1152 | + | d | 51.5241 | -0.1167 | + | e | 51.5247 | -0.1153 | + + And the ways + | nodes | name | + | ab | albic | + | bc | albic | + | db | dobe | + | be | dobe | + + And the relations + | type | way:from | way:to | node:via | restriction:conditional | + | restriction | ab | be | b | only_left_turn @ (Mo-Fr 07:00-11:00) | + + When I route I should get + | from | to | route | turns | + | a | c | albic,dobe,dobe,albic,albic | depart,turn left,turn uturn,turn left,arrive | + | a | e | albic,dobe,dobe | depart,turn left,arrive | diff --git a/features/step_definitions/data.js b/features/step_definitions/data.js index 26f0ef82f..9ed80f4f3 100644 --- a/features/step_definitions/data.js +++ b/features/step_definitions/data.js @@ -38,7 +38,7 @@ module.exports = function () { callback(); }); - this.Given(/^the origin ([-+]?[0-9]*\.?[0-9]+),([-+]?[0-9]*\.?[0-9]+)$/, (lat, lon, callback) => { + this.Given(/^the origin ([-+]?[0-9]*\.?[0-9]+),([-+]?[0-9]*\.?[0-9]+)$/, (lon, lat, callback) => { this.setOrigin([parseFloat(lon), parseFloat(lat)]); callback(); }); diff --git a/features/support/env.js b/features/support/env.js index 8e1f8d474..36f8e065c 100644 --- a/features/support/env.js +++ b/features/support/env.js @@ -39,7 +39,7 @@ module.exports = function () { this.WAY_SPACING = 100; this.DEFAULT_GRID_SIZE = 100; // meters // get algorithm name from the command line profile argument - this.ROUTING_ALGORITHM = process.argv[process.argv.indexOf('-p') + 1] === 'mld' ? 'MLD' : 'CH'; + this.ROUTING_ALGORITHM = process.argv[process.argv.indexOf('-p') + 1].match('mld') ? 'MLD' : 'CH'; this.OSRM_PORT = process.env.OSRM_PORT && parseInt(process.env.OSRM_PORT) || 5000; this.HOST = 'http://127.0.0.1:' + this.OSRM_PORT; diff --git a/include/extractor/edge_based_graph_factory.hpp b/include/extractor/edge_based_graph_factory.hpp index de59f1b75..46c7a156b 100644 --- a/include/extractor/edge_based_graph_factory.hpp +++ b/include/extractor/edge_based_graph_factory.hpp @@ -52,13 +52,13 @@ namespace lookup #pragma pack(push, 1) struct TurnIndexBlock { - OSMNodeID from_id; - OSMNodeID via_id; - OSMNodeID to_id; + NodeID from_id; + NodeID via_id; + NodeID to_id; }; #pragma pack(pop) static_assert(std::is_trivial::value, "TurnIndexBlock is not trivial"); -static_assert(sizeof(TurnIndexBlock) == 24, "TurnIndexBlock is not packed correctly"); +static_assert(sizeof(TurnIndexBlock) == 12, "TurnIndexBlock is not packed correctly"); } // ns lookup struct NodeBasedGraphToEdgeBasedGraphMappingWriter; // fwd. decl diff --git a/include/extractor/extraction_containers.hpp b/include/extractor/extraction_containers.hpp index fad991bd4..b098b67fe 100644 --- a/include/extractor/extraction_containers.hpp +++ b/include/extractor/extraction_containers.hpp @@ -40,7 +40,7 @@ class ExtractionContainers void PrepareEdges(ScriptingEnvironment &scripting_environment); void WriteNodes(storage::io::FileWriter &file_out) const; - void WriteRestrictions(const std::string &restrictions_file_name) const; + void WriteRestrictions(const std::string &restrictions_file_name); void WriteEdges(storage::io::FileWriter &file_out) const; void WriteCharData(const std::string &file_name); @@ -48,7 +48,7 @@ class ExtractionContainers using STXXLNodeIDVector = stxxl::vector; using STXXLNodeVector = stxxl::vector; using STXXLEdgeVector = stxxl::vector; - using STXXLRestrictionsVector = stxxl::vector; + using RestrictionsVector = std::vector; using STXXLWayIDStartEndVector = stxxl::vector; using STXXLNameCharData = stxxl::vector; using STXXLNameOffsets = stxxl::vector; @@ -59,10 +59,11 @@ class ExtractionContainers STXXLNameCharData name_char_data; STXXLNameOffsets name_offsets; // an adjacency array containing all turn lane masks - STXXLRestrictionsVector restrictions_list; + RestrictionsVector restrictions_list; STXXLWayIDStartEndVector way_start_end_id_list; std::unordered_map external_to_internal_node_id_map; unsigned max_internal_node_id; + std::vector unconditional_turn_restrictions; ExtractionContainers(); diff --git a/include/extractor/extractor.hpp b/include/extractor/extractor.hpp index fc49caddb..95846a9d6 100644 --- a/include/extractor/extractor.hpp +++ b/include/extractor/extractor.hpp @@ -56,6 +56,9 @@ class Extractor private: ExtractorConfig config; + std::vector ParseOSMData(ScriptingEnvironment &scripting_environment, + const unsigned number_of_threads); + std::pair BuildEdgeExpandedGraph(ScriptingEnvironment &scripting_environment, std::vector &coordinates, @@ -64,7 +67,8 @@ class Extractor std::vector &node_is_startpoint, std::vector &edge_based_node_weights, util::DeallocatingVector &edge_based_edge_list, - const std::string &intersection_class_output_file); + const std::string &intersection_class_output_file, + std::vector &turn_restrictions); void WriteProfileProperties(const std::string &output_path, const ProfileProperties &properties) const; void FindComponents(unsigned max_edge_id, diff --git a/include/extractor/extractor_config.hpp b/include/extractor/extractor_config.hpp index 005334650..cf0f28b56 100644 --- a/include/extractor/extractor_config.hpp +++ b/include/extractor/extractor_config.hpp @@ -112,6 +112,7 @@ struct ExtractorConfig std::string turn_penalties_index_path; bool use_metadata; + bool parse_conditionals; }; } } diff --git a/include/extractor/restriction.hpp b/include/extractor/restriction.hpp index dd7630012..12e77f446 100644 --- a/include/extractor/restriction.hpp +++ b/include/extractor/restriction.hpp @@ -1,6 +1,8 @@ #ifndef RESTRICTION_HPP #define RESTRICTION_HPP +#include "util/coordinate.hpp" +#include "util/opening_hours.hpp" #include "util/typedefs.hpp" #include @@ -20,6 +22,8 @@ struct TurnRestriction WayOrNode from; WayOrNode to; + std::vector condition; + struct Bits { // mostly unused Bits() diff --git a/include/extractor/restriction_map.hpp b/include/extractor/restriction_map.hpp index 02c7191bc..2fba93ba0 100644 --- a/include/extractor/restriction_map.hpp +++ b/include/extractor/restriction_map.hpp @@ -95,7 +95,7 @@ class RestrictionMap return; } - // find all potential start edges. It is more efficent to get a (small) list + // find all potential start edges. It is more efficient to get a (small) list // of potential start edges than iterating over all buckets std::vector predecessors; for (const EdgeID current_edge_id : graph.GetAdjacentEdgeRange(node_u)) diff --git a/include/extractor/restriction_parser.hpp b/include/extractor/restriction_parser.hpp index 732affc1f..7a5540bdc 100644 --- a/include/extractor/restriction_parser.hpp +++ b/include/extractor/restriction_parser.hpp @@ -41,14 +41,17 @@ class ScriptingEnvironment; class RestrictionParser { public: - RestrictionParser(ScriptingEnvironment &scripting_environment); - boost::optional TryParse(const osmium::Relation &relation) const; + RestrictionParser(bool use_turn_restrictions, + bool parse_conditionals, + std::vector &restrictions); + std::vector TryParse(const osmium::Relation &relation) const; private: bool ShouldIgnoreRestriction(const std::string &except_tag_string) const; - std::vector restrictions; bool use_turn_restrictions; + bool parse_conditionals; + std::vector restrictions; }; } } diff --git a/include/extractor/serialization.hpp b/include/extractor/serialization.hpp index ed48c2044..e94455a0f 100644 --- a/include/extractor/serialization.hpp +++ b/include/extractor/serialization.hpp @@ -4,6 +4,7 @@ #include "extractor/datasources.hpp" #include "extractor/nbg_to_ebg.hpp" #include "extractor/node_data_container.hpp" +#include "extractor/restriction.hpp" #include "extractor/segment_data_container.hpp" #include "extractor/turn_data_container.hpp" @@ -19,6 +20,7 @@ namespace extractor namespace serialization { +// read/write for datasources file inline void read(storage::io::FileReader &reader, Datasources &sources) { reader.ReadInto(sources); @@ -29,6 +31,7 @@ inline void write(storage::io::FileWriter &writer, Datasources &sources) writer.WriteFrom(sources); } +// read/write for segment data file template inline void read(storage::io::FileReader &reader, detail::SegmentDataContainerImpl &segment_data) @@ -55,6 +58,7 @@ inline void write(storage::io::FileWriter &writer, storage::serialization::write(writer, segment_data.datasources); } +// read/write for turn data file template inline void read(storage::io::FileReader &reader, detail::TurnDataContainerImpl &turn_data_container) @@ -94,6 +98,51 @@ inline void write(storage::io::FileWriter &writer, storage::serialization::write(writer, node_data_container.name_ids); storage::serialization::write(writer, node_data_container.travel_modes); } + +// read/write for conditional turn restrictions file +inline void read(storage::io::FileReader &reader, std::vector &restrictions) +{ + auto num_indices = reader.ReadElementCount64(); + restrictions.reserve(num_indices); + TurnRestriction restriction; + while (num_indices > 0) + { + bool is_only; + reader.ReadInto(restriction.via); + reader.ReadInto(restriction.from); + reader.ReadInto(restriction.to); + reader.ReadInto(is_only); + auto num_conditions = reader.ReadElementCount64(); + restriction.condition.resize(num_conditions); + for (uint64_t i = 0; i < num_conditions; i++) + { + reader.ReadInto(restriction.condition[i].modifier); + storage::serialization::read(reader, restriction.condition[i].times); + storage::serialization::read(reader, restriction.condition[i].weekdays); + storage::serialization::read(reader, restriction.condition[i].monthdays); + } + restriction.flags.is_only = is_only; + + restrictions.push_back(std::move(restriction)); + num_indices--; + } +} + +inline void write(storage::io::FileWriter &writer, const TurnRestriction &restriction) +{ + writer.WriteOne(restriction.via); + writer.WriteOne(restriction.from); + writer.WriteOne(restriction.to); + writer.WriteOne(restriction.flags.is_only); + writer.WriteElementCount64(restriction.condition.size()); + for (const auto &c : restriction.condition) + { + writer.WriteOne(c.modifier); + storage::serialization::write(writer, c.times); + storage::serialization::write(writer, c.weekdays); + storage::serialization::write(writer, c.monthdays); + } +} } } } diff --git a/include/updater/source.hpp b/include/updater/source.hpp index 1c881ff4a..462c6b98d 100644 --- a/include/updater/source.hpp +++ b/include/updater/source.hpp @@ -63,10 +63,9 @@ struct Turn final : from(from), via(via), to(to) { } - template - Turn(const Other &turn) - : from(static_cast(turn.from_id)), - via(static_cast(turn.via_id)), to(static_cast(turn.to_id)) + Turn(const OSMNodeID &from_id, const OSMNodeID &via_id, const OSMNodeID &to_id) + : from(static_cast(from_id)), via(static_cast(via_id)), + to(static_cast(to_id)) { } bool operator<(const Turn &rhs) const diff --git a/include/updater/updater.hpp b/include/updater/updater.hpp index 550f4e7f8..78f881a9a 100644 --- a/include/updater/updater.hpp +++ b/include/updater/updater.hpp @@ -5,6 +5,7 @@ #include "extractor/edge_based_edge.hpp" +#include #include namespace osrm diff --git a/include/updater/updater_config.hpp b/include/updater/updater_config.hpp index 27483b747..751a0e0f5 100644 --- a/include/updater/updater_config.hpp +++ b/include/updater/updater_config.hpp @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include #include namespace osrm @@ -53,6 +54,7 @@ struct UpdaterConfig final rtree_leaf_path = osrm_input_path.string() + ".fileIndex"; datasource_names_path = osrm_input_path.string() + ".datasource_names"; profile_properties_path = osrm_input_path.string() + ".properties"; + turn_restrictions_path = osrm_input_path.string() + ".restrictions"; } boost::filesystem::path osrm_input_path; @@ -69,11 +71,14 @@ struct UpdaterConfig final std::string rtree_leaf_path; double log_edge_updates_factor; + std::time_t valid_now; std::vector segment_speed_lookup_paths; std::vector turn_penalty_lookup_paths; std::string datasource_names_path; std::string profile_properties_path; + std::string turn_restrictions_path; + std::string tz_file_path; }; } } diff --git a/include/util/conditional_restrictions.hpp b/include/util/conditional_restrictions.hpp index db519b885..18c0463e6 100644 --- a/include/util/conditional_restrictions.hpp +++ b/include/util/conditional_restrictions.hpp @@ -1,9 +1,8 @@ #ifndef OSRM_CONDITIONAL_RESTRICTIONS_HPP #define OSRM_CONDITIONAL_RESTRICTIONS_HPP -#include -#include -#include +#include +#include namespace osrm { @@ -20,88 +19,7 @@ struct ConditionalRestriction std::string condition; }; -#ifndef NDEBUG -// Debug output stream operators for use with BOOST_SPIRIT_DEBUG -inline std::ostream &operator<<(std::ostream &stream, const ConditionalRestriction &restriction) -{ - return stream << restriction.value << "=" << restriction.condition; -} -#endif -} -} - -BOOST_FUSION_ADAPT_STRUCT(osrm::util::ConditionalRestriction, - (std::string, value)(std::string, condition)) - -namespace osrm -{ -namespace util -{ -namespace detail -{ - -namespace -{ -namespace ph = boost::phoenix; -namespace qi = boost::spirit::qi; -} - -template -struct conditional_restrictions_grammar - : qi::grammar()> -{ - // http://wiki.openstreetmap.org/wiki/Conditional_restrictions - conditional_restrictions_grammar() : conditional_restrictions_grammar::base_type(restrictions) - { - using qi::_1; - using qi::_val; - using qi::lit; - - // clang-format off - - restrictions - = restriction % ';' - ; - - restriction - = value >> '@' >> condition - ; - - value - = +(qi::char_ - '@') - ; - - condition - = *qi::blank - >> (lit('(') >> qi::as_string[qi::no_skip[*~lit(')')]][_val = _1] >> lit(')') - | qi::as_string[qi::no_skip[*~lit(';')]][_val = _1] - ) - ; - - // clang-format on - - BOOST_SPIRIT_DEBUG_NODES((restrictions)(restriction)(value)(condition)); - } - - qi::rule()> restrictions; - qi::rule restriction; - qi::rule value, condition; -}; -} - -inline std::vector ParseConditionalRestrictions(const std::string &str) -{ - auto it(str.begin()), end(str.end()); - const detail::conditional_restrictions_grammar static grammar; - - std::vector result; - bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result); - - if (!ok || it != end) - return std::vector(); - - return result; -} +std::vector ParseConditionalRestrictions(const std::string &str); } // util } // osrm diff --git a/include/util/graph_loader.hpp b/include/util/graph_loader.hpp index 0a1fbac31..8fb610b84 100644 --- a/include/util/graph_loader.hpp +++ b/include/util/graph_loader.hpp @@ -31,24 +31,6 @@ namespace osrm namespace util { -/** - * Reads the .restrictions file and loads it to a vector. - * The since the restrictions reference nodes using their external node id, - * we need to renumber it to the new internal id. -*/ -inline unsigned loadRestrictionsFromFile(storage::io::FileReader &file_reader, - std::vector &restriction_list) -{ - auto number_of_usable_restrictions = file_reader.ReadElementCount64(); - restriction_list.resize(number_of_usable_restrictions); - if (number_of_usable_restrictions > 0) - { - file_reader.ReadInto(restriction_list.data(), number_of_usable_restrictions); - } - - return number_of_usable_restrictions; -} - /** * Reads the beginning of an .osrm file and produces: * - barrier nodes diff --git a/include/util/opening_hours.hpp b/include/util/opening_hours.hpp index 618dc7363..502dee3ec 100644 --- a/include/util/opening_hours.hpp +++ b/include/util/opening_hours.hpp @@ -2,17 +2,9 @@ #define OSRM_OPENING_HOURS_HPP #include -#include -#include -#include - -#include -#include -#include -#include -#include #include +#include namespace osrm { @@ -219,421 +211,9 @@ struct OpeningHours Modifier modifier; }; -#ifndef NDEBUG -// Debug output stream operators for use with BOOST_SPIRIT_DEBUG -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Modifier value) -{ - switch (value) - { - case OpeningHours::unknown: - return stream << "unknown"; - case OpeningHours::open: - return stream << "open"; - case OpeningHours::closed: - return stream << "closed"; - case OpeningHours::off: - return stream << "off"; - case OpeningHours::is24_7: - return stream << "24/7"; - } - return stream; -} +std::vector ParseOpeningHours(const std::string &str); -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Time::Event value) -{ - switch (value) - { - case OpeningHours::Time::dawn: - return stream << "dawn"; - case OpeningHours::Time::sunrise: - return stream << "sunrise"; - case OpeningHours::Time::sunset: - return stream << "sunset"; - case OpeningHours::Time::dusk: - return stream << "dusk"; - default: - break; - } - return stream; -} - -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Time &value) -{ - boost::io::ios_flags_saver ifs(stream); - if (value.event == OpeningHours::Time::invalid) - return stream << "???"; - if (value.event == OpeningHours::Time::none) - return stream << std::setfill('0') << std::setw(2) << value.minutes / 60 << ":" - << std::setfill('0') << std::setw(2) << value.minutes % 60; - stream << value.event; - if (value.minutes != 0) - stream << value.minutes; - return stream; -} - -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::TimeSpan &value) -{ - return stream << value.from << "-" << value.to; -} - -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Monthday &value) -{ - bool empty = true; - if (value.year != 0) - { - stream << (int)value.year; - empty = false; - }; - if (value.month != 0) - { - stream << (empty ? "" : "/") << (int)value.month; - empty = false; - }; - if (value.day != 0) - { - stream << (empty ? "" : "/") << (int)value.day; - }; - return stream; -} - -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::WeekdayRange &value) -{ - boost::io::ios_flags_saver ifs(stream); - return stream << std::hex << std::setfill('0') << std::setw(2) << value.weekdays; -} - -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::MonthdayRange &value) -{ - return stream << value.from << "-" << value.to; -} - -inline std::ostream &operator<<(std::ostream &stream, const OpeningHours &value) -{ - if (value.modifier == OpeningHours::is24_7) - return stream << OpeningHours::is24_7; - - for (auto x : value.monthdays) - stream << x << ", "; - for (auto x : value.weekdays) - stream << x << ", "; - for (auto x : value.times) - stream << x << ", "; - return stream << " |" << value.modifier << "|"; -} -#endif - -namespace detail -{ - -namespace -{ -namespace ph = boost::phoenix; -namespace qi = boost::spirit::qi; -} - -template -struct opening_hours_grammar : qi::grammar()> -{ - // http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification - opening_hours_grammar() : opening_hours_grammar::base_type(time_domain) - { - using qi::_1; - using qi::_a; - using qi::_b; - using qi::_c; - using qi::_r1; - using qi::_pass; - using qi::_val; - using qi::eoi; - using qi::lit; - using qi::char_; - using qi::uint_; - using oh = osrm::util::OpeningHours; - - // clang-format off - - // General syntax - time_domain = rule_sequence[ph::push_back(_val, _1)] % any_rule_separator; - - rule_sequence - = lit("24/7")[ph::bind(&oh::modifier, _val) = oh::is24_7] - | (selector_sequence[_val = _1] >> -rule_modifier[ph::bind(&oh::modifier, _val) = _1] >> -comment) - | comment - ; - - any_rule_separator = char_(';') | lit("||") | additional_rule_separator; - - additional_rule_separator = char_(','); - - // Rule modifiers - rule_modifier.add - ("unknown", oh::unknown) - ("open", oh::open) - ("closed", oh::closed) - ("off", oh::off) - ; - - // Selectors - selector_sequence = (wide_range_selectors(_a) >> small_range_selectors(_a))[_val = _a]; - - wide_range_selectors - = (-monthday_selector(_r1) - >> -year_selector(_r1) - >> -week_selector(_r1) // TODO week_selector - ) >> -lit(':') - ; - - small_range_selectors = -(weekday_selector(_r1) >> (&~lit(',') | eoi)) >> -time_selector(_r1); - - // Time selector - time_selector = (timespan % ',')[ph::bind(&OpeningHours::times, _r1) = _1]; - - timespan - = (time[_a = _1] - >> -(lit('+')[_b = ph::construct(24, 0)] - | ('-' >> extended_time[_b = _1] - >> -('+' | '/' >> (minute | hour_minutes)))) - )[_val = ph::construct(_a, _b)] - ; - - time = hour_minutes | variable_time; - - extended_time = extended_hour_minutes | variable_time; - - variable_time - = event[_val = ph::construct(_1)] - | ('(' >> event[_a = _1] >> plus_or_minus[_b = _1] >> hour_minutes[_c = _1] >> ')') - [_val = ph::construct(_a, _b, _c)] - ; - - event.add - ("dawn", OpeningHours::Time::dawn) - ("sunrise", OpeningHours::Time::sunrise) - ("sunset", OpeningHours::Time::sunset) - ("dusk", OpeningHours::Time::dusk) - ; - - // Weekday selector - weekday_selector - = (holiday_sequence(_r1) >> -(char_(", ") >> weekday_sequence(_r1))) - | (weekday_sequence(_r1) >> -(char_(", ") >> holiday_sequence(_r1))) - ; - - weekday_sequence = (weekday_range % ',')[ph::bind(&OpeningHours::weekdays, _r1) = _1]; - - weekday_range - = wday[_a = _1, _b = _1] - >> -(('-' >> wday[_b = _1]) - | ('[' >> (nth_entry % ',') >> ']' >> -day_offset)) - [_val = ph::construct(_a, _b)] - ; - - holiday_sequence = (lit("SH") >> -day_offset) | lit("PH"); - - nth_entry = nth | nth >> '-' >> nth | '-' >> nth; - - nth = char_("12345"); - - day_offset = plus_or_minus >> uint_ >> lit("days"); - - // Week selector - week_selector = (lit("week ") >> week) % ','; - - week = weeknum >> -('-' >> weeknum >> -('/' >> uint_)); - - // Month selector - monthday_selector = (monthday_range % ',')[ph::bind(&OpeningHours::monthdays, _r1) = _1]; - - monthday_range - = (date_from[ph::bind(&OpeningHours::MonthdayRange::from, _val) = _1] - >> -date_offset - >> '-' - >> date_to[ph::bind(&OpeningHours::MonthdayRange::to, _val) = _1] - >> -date_offset) - | (date_from[ph::bind(&OpeningHours::MonthdayRange::from, _val) = _1] - >> -(date_offset - >> -lit('+')[ph::bind(&OpeningHours::MonthdayRange::from, _val) = ph::construct(-1)] - )) - ; - - date_offset = (plus_or_minus >> wday) | day_offset; - - date_from - = ((-year[_a = _1] >> ((month[_b = _1] >> -daynum[_c = _1]) | daynum[_c = _1])) - | variable_date) - [_val = ph::construct(_a, _b, _c)] - ; - - date_to - = date_from[_val = _1] - | daynum[_val = ph::construct(0, 0, _1)] - ; - - variable_date = lit("easter"); - - // Year selector - year_selector = (year_range % ',')[ph::bind(&OpeningHours::monthdays, _r1) = _1]; - - year_range - = year[ph::bind(&OpeningHours::MonthdayRange::from, _val) = ph::construct(_1)] - >> -(('-' >> year[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct(_1)] - >> -('/' >> uint_)) - | lit('+')[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct(-1)]); - - // Basic elements - plus_or_minus = lit('+')[_val = true] | lit('-')[_val = false]; - - hour = uint2_p[_pass = bind([](unsigned x) { return x <= 24; }, _1), _val = _1]; - - extended_hour = uint2_p[_pass = bind([](unsigned x) { return x <= 48; }, _1), _val = _1]; - - minute = uint2_p[_pass = bind([](unsigned x) { return x < 60; }, _1), _val = _1]; - - hour_minutes = - hour[_a = _1] >> ':' >> minute[_val = ph::construct(_a, _1)]; - - extended_hour_minutes = extended_hour[_a = _1] >> ':' >> - minute[_val = ph::construct(_a, _1)]; - - wday.add - ("Su", 0) - ("Mo", 1) - ("Tu", 2) - ("We", 3) - ("Th", 4) - ("Fr", 5) - ("Sa", 6) - ; - - daynum - = uint2_p[_pass = bind([](unsigned x) { return 01 <= x && x <= 31; }, _1), _val = _1] - >> (&~lit(':') | eoi) - ; - - weeknum = uint2_p[_pass = bind([](unsigned x) { return 01 <= x && x <= 53; }, _1), _val = _1]; - - month.add - ("Jan", 1) - ("Feb", 2) - ("Mar", 3) - ("Apr", 4) - ("May", 5) - ("Jun", 6) - ("Jul", 7) - ("Aug", 8) - ("Sep", 9) - ("Oct", 10) - ("Nov", 11) - ("Dec", 12) - ; - - year = uint4_p[_pass = bind([](unsigned x) { return x > 1900; }, _1), _val = _1]; - - comment = lit('"') >> *(~qi::char_('"')) >> lit('"'); - - // clang-format on - - BOOST_SPIRIT_DEBUG_NODES((time_domain)(rule_sequence)(any_rule_separator)( - selector_sequence)(wide_range_selectors)(small_range_selectors)(time_selector)( - timespan)(time)(extended_time)(variable_time)(weekday_selector)(weekday_sequence)( - weekday_range)(holiday_sequence)(nth_entry)(nth)(day_offset)(week_selector)(week)( - monthday_selector)(monthday_range)(date_offset)(date_from)(date_to)(variable_date)( - year_selector)(year_range)(plus_or_minus)(hour_minutes)(extended_hour_minutes)(comment)( - hour)(extended_hour)(minute)(daynum)(weeknum)(year)); - } - - qi::rule()> time_domain; - qi::rule rule_sequence; - qi::rule any_rule_separator, additional_rule_separator; - qi::rule> selector_sequence; - qi::symbols rule_modifier; - qi::rule wide_range_selectors, small_range_selectors, - time_selector, weekday_selector, year_selector, monthday_selector, week_selector; - - // Time rules - qi::rule> - timespan; - - qi::rule time, extended_time; - - qi::rule> - variable_time; - - qi::rule> hour_minutes, - extended_hour_minutes; - - qi::symbols event; - - qi::rule plus_or_minus; - - // Weekday rules - qi::rule weekday_sequence, holiday_sequence; - - qi::rule> - weekday_range; - - // Monthday rules - qi::rule monthday_range; - - qi::rule> - date_from; - - qi::rule date_to; - - // Year rules - qi::rule year_range; - - // Unused rules - qi::rule nth_entry, nth, day_offset, week, date_offset, - variable_date, comment; - - // Basic rules and parsers - qi::rule hour, extended_hour, minute, daynum, weeknum, year; - qi::symbols wday, month; - qi::uint_parser uint2_p; - qi::uint_parser uint4_p; -}; -} - -inline std::vector ParseOpeningHours(const std::string &str) -{ - auto it(str.begin()), end(str.end()); - const detail::opening_hours_grammar static grammar; - - std::vector result; - bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result); - - if (!ok || it != end) - return std::vector(); - - return result; -} - -inline bool CheckOpeningHours(const std::vector &input, const struct tm &time) -{ - bool is_open = false; - for (auto &opening_hours : input) - { - if (opening_hours.modifier == OpeningHours::is24_7) - return true; - - if (opening_hours.IsInRange(time)) - { - is_open = opening_hours.modifier == OpeningHours::open; - } - } - - return is_open; -} +bool CheckOpeningHours(const std::vector &input, const struct tm &time); } // util } // osrm diff --git a/include/util/timezones.hpp b/include/util/timezones.hpp new file mode 100644 index 000000000..0685309fb --- /dev/null +++ b/include/util/timezones.hpp @@ -0,0 +1,48 @@ +#ifndef OSRM_TIMEZONES_HPP +#define OSRM_TIMEZONES_HPP + +#include "util/log.hpp" + +#include +#include + +#include + +namespace osrm +{ +namespace updater +{ + +// Time zone shape polygons loaded in R-tree +// local_time_t is a pair of a time zone shape polygon and the corresponding local time +// rtree_t is a lookup R-tree that maps a geographic point to an index in a local_time_t vector +using point_t = boost::geometry::model:: + point>; +using polygon_t = boost::geometry::model::polygon; +using box_t = boost::geometry::model::box; +using rtree_t = + boost::geometry::index::rtree, boost::geometry::index::rstar<8>>; +using local_time_t = std::pair; + +bool SupportsShapefiles(); + +class Timezoner +{ + public: + Timezoner() = default; + + Timezoner(std::string tz_filename, std::time_t utc_time_now); + + struct tm operator()(const point_t &point) const; + + private: + void LoadLocalTimesRTree(const std::string &tz_shapes_filename, std::time_t utc_time); + + struct tm default_time; + rtree_t rtree; + std::vector local_times; +}; +} +} + +#endif diff --git a/package.json b/package.json index 2d4eff3f8..fa07f1b5d 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "scripts": { "lint": "eslint -c ./.eslintrc features/step_definitions/ features/support/", "test": "npm run lint && node ./node_modules/cucumber/bin/cucumber.js features/ -p verify && node ./node_modules/cucumber/bin/cucumber.js features/ -p mld", + "test-conditionals": "npm run lint && node ./node_modules/cucumber/bin/cucumber.js features/ -p conditionals && node ./node_modules/cucumber/bin/cucumber.js features/ -p mld_conditionals", "clean": "rm -rf test/cache", "docs": "./scripts/build_api_docs.sh", "install": "node-pre-gyp install --fallback-to-build=false || ./scripts/node_install.sh", diff --git a/src/benchmarks/CMakeLists.txt b/src/benchmarks/CMakeLists.txt index 5663a22f1..140efab38 100644 --- a/src/benchmarks/CMakeLists.txt +++ b/src/benchmarks/CMakeLists.txt @@ -14,7 +14,8 @@ target_include_directories(rtree-bench target_link_libraries(rtree-bench ${BOOST_BASE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - ${TBB_LIBRARIES}) + ${TBB_LIBRARIES} + ${MAYBE_SHAPEFILE}) add_executable(match-bench EXCLUDE_FROM_ALL @@ -25,7 +26,8 @@ target_link_libraries(match-bench osrm ${BOOST_BASE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - ${TBB_LIBRARIES}) + ${TBB_LIBRARIES} + ${MAYBE_SHAPEFILE}) add_executable(alias-bench EXCLUDE_FROM_ALL @@ -35,7 +37,8 @@ add_executable(alias-bench target_link_libraries(alias-bench ${BOOST_BASE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - ${TBB_LIBRARIES}) + ${TBB_LIBRARIES} + ${MAYBE_SHAPEFILE}) add_custom_target(benchmarks DEPENDS diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index f8b8d4f80..8a6a6cdbf 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -547,20 +547,17 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // If this edge is 'trivial' -- where the compressed edge corresponds // exactly to an original OSM segment -- we can pull the turn's preceding // node ID directly with `node_along_road_entering`; otherwise, we need to - // look - // up the node - // immediately preceding the turn from the compressed edge container. + // look up the node immediately preceding the turn from the compressed edge + // container. const bool isTrivial = m_compressed_edge_container.IsTrivial(incoming_edge); const auto &from_node = - isTrivial ? m_osm_node_ids[node_along_road_entering] - : m_osm_node_ids[m_compressed_edge_container.GetLastEdgeSourceID( - incoming_edge)]; + isTrivial ? node_along_road_entering + : m_compressed_edge_container.GetLastEdgeSourceID(incoming_edge); const auto &via_node = - m_osm_node_ids[m_compressed_edge_container.GetLastEdgeTargetID( - incoming_edge)]; + m_compressed_edge_container.GetLastEdgeTargetID(incoming_edge); const auto &to_node = - m_osm_node_ids[m_compressed_edge_container.GetFirstEdgeTargetID(turn.eid)]; + m_compressed_edge_container.GetFirstEdgeTargetID(turn.eid); lookup::TurnIndexBlock turn_index_block = {from_node, via_node, to_node}; diff --git a/src/extractor/extraction_containers.cpp b/src/extractor/extraction_containers.cpp index 52ac1629e..68cd45c04 100644 --- a/src/extractor/extraction_containers.cpp +++ b/src/extractor/extraction_containers.cpp @@ -1,6 +1,8 @@ #include "extractor/extraction_containers.hpp" #include "extractor/extraction_segment.hpp" #include "extractor/extraction_way.hpp" +#include "extractor/restriction.hpp" +#include "extractor/serialization.hpp" #include "util/coordinate_calculation.hpp" @@ -134,7 +136,6 @@ void ExtractionContainers::FlushVectors() all_edges_list.flush(); name_char_data.flush(); name_offsets.flush(); - restrictions_list.flush(); way_start_end_id_list.flush(); } @@ -142,7 +143,7 @@ void ExtractionContainers::FlushVectors() * Processes the collected data and serializes it. * At this point nodes are still referenced by their OSM id. * - * - map start-end nodes of ways to ways used int restrictions to compute compressed + * - map start-end nodes of ways to ways used in restrictions to compute compressed * trippe representation * - filter nodes list to nodes that are referenced by ways * - merge edges with nodes to include location of start/end points and serialize @@ -630,7 +631,7 @@ void ExtractionContainers::WriteNodes(storage::io::FileWriter &file_out) const util::Log() << "Processed " << max_internal_node_id << " nodes"; } -void ExtractionContainers::WriteRestrictions(const std::string &path) const +void ExtractionContainers::WriteRestrictions(const std::string &path) { // serialize restrictions std::uint64_t written_restriction_count = 0; @@ -645,13 +646,27 @@ void ExtractionContainers::WriteRestrictions(const std::string &path) const SPECIAL_NODEID != restriction_container.restriction.via.node && SPECIAL_NODEID != restriction_container.restriction.to.node) { - restrictions_out_file.WriteOne(restriction_container.restriction); - ++written_restriction_count; + if (!restriction_container.restriction.condition.empty()) + { + // write conditional turn restrictions to disk, for use in contractor later + extractor::serialization::write(restrictions_out_file, + restriction_container.restriction); + ++written_restriction_count; + } + else + { + // save unconditional turn restriction to memory, for use in ebg later + unconditional_turn_restrictions.push_back( + std::move(restriction_container.restriction)); + } } } restrictions_out_file.SkipToBeginning(); restrictions_out_file.WriteElementCount64(written_restriction_count); - util::Log() << "usable restrictions: " << written_restriction_count; + util::Log() << "number of restrictions saved to memory: " + << unconditional_turn_restrictions.size(); + util::Log() << "number of conditional restrictions written to disk: " + << written_restriction_count; } void ExtractionContainers::PrepareRestrictions() @@ -672,10 +687,8 @@ void ExtractionContainers::PrepareRestrictions() util::UnbufferedLog log; log << "Sorting " << restrictions_list.size() << " restriction. by from... "; TIMER_START(sort_restrictions); - stxxl::sort(restrictions_list.begin(), - restrictions_list.end(), - CmpRestrictionContainerByFrom(), - stxxl_memory); + std::sort( + restrictions_list.begin(), restrictions_list.end(), CmpRestrictionContainerByFrom()); TIMER_STOP(sort_restrictions); log << "ok, after " << TIMER_SEC(sort_restrictions) << "s"; } @@ -758,6 +771,11 @@ void ExtractionContainers::PrepareRestrictions() } restrictions_iterator->restriction.from.node = id_iter->second; } + else + { + // if it's neither, this is an invalid restriction + restrictions_iterator->restriction.from.node = SPECIAL_NODEID; + } ++restrictions_iterator; } @@ -769,10 +787,8 @@ void ExtractionContainers::PrepareRestrictions() util::UnbufferedLog log; log << "Sorting restrictions. by to ... " << std::flush; TIMER_START(sort_restrictions_to); - stxxl::sort(restrictions_list.begin(), - restrictions_list.end(), - CmpRestrictionContainerByTo(), - stxxl_memory); + std::sort( + restrictions_list.begin(), restrictions_list.end(), CmpRestrictionContainerByTo()); TIMER_STOP(sort_restrictions_to); log << "ok, after " << TIMER_SEC(sort_restrictions_to) << "s"; } @@ -850,6 +866,11 @@ void ExtractionContainers::PrepareRestrictions() } restrictions_iterator->restriction.to.node = to_id_iter->second; } + else + { + // if it's neither, this is an invalid restriction + restrictions_iterator->restriction.to.node = SPECIAL_NODEID; + } ++restrictions_iterator; } TIMER_STOP(fix_restriction_ends); diff --git a/src/extractor/extractor.cpp b/src/extractor/extractor.cpp index ba3fe5561..31080b86c 100644 --- a/src/extractor/extractor.cpp +++ b/src/extractor/extractor.cpp @@ -115,208 +115,216 @@ transformTurnLaneMapIntoArrays(const guidance::LaneDescriptionMap &turn_lane_map int Extractor::run(ScriptingEnvironment &scripting_environment) { util::LogPolicy::GetInstance().Unmute(); - TIMER_START(extracting); const unsigned recommended_num_threads = tbb::task_scheduler_init::default_num_threads(); const auto number_of_threads = std::min(recommended_num_threads, config.requested_num_threads); tbb::task_scheduler_init init(number_of_threads ? number_of_threads : tbb::task_scheduler_init::automatic); + auto turn_restrictions = ParseOSMData(scripting_environment, number_of_threads); + + // 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 + util::Log() << "Generating edge-expanded graph representation"; + + TIMER_START(expansion); + + std::vector edge_based_node_list; + util::DeallocatingVector edge_based_edge_list; + std::vector node_is_startpoint; + std::vector edge_based_node_weights; + std::vector coordinates; + extractor::PackedOSMIDs osm_node_ids; + + auto graph_size = BuildEdgeExpandedGraph(scripting_environment, + coordinates, + osm_node_ids, + edge_based_node_list, + node_is_startpoint, + edge_based_node_weights, + edge_based_edge_list, + config.intersection_class_data_output_path, + turn_restrictions); + + auto number_of_node_based_nodes = graph_size.first; + auto max_edge_id = graph_size.second; + + TIMER_STOP(expansion); + + util::Log() << "Saving edge-based node weights to file."; + TIMER_START(timer_write_node_weights); { - util::Log() << "Input file: " << config.input_path.filename().string(); - if (!config.profile_path.empty()) - { - util::Log() << "Profile: " << config.profile_path.filename().string(); - } - util::Log() << "Threads: " << number_of_threads; - - const osmium::io::File input_file(config.input_path.string()); - - osmium::io::Reader reader( - input_file, - (config.use_metadata ? osmium::io::read_meta::yes : osmium::io::read_meta::no)); - - const osmium::io::Header header = reader.header(); - - unsigned number_of_nodes = 0; - unsigned number_of_ways = 0; - unsigned number_of_relations = 0; - - util::Log() << "Parsing in progress.."; - TIMER_START(parsing); - - ExtractionContainers extraction_containers; - auto extractor_callbacks = std::make_unique( - extraction_containers, scripting_environment.GetProfileProperties()); - - // setup raster sources - scripting_environment.SetupSources(); - - std::string generator = header.get("generator"); - if (generator.empty()) - { - generator = "unknown tool"; - } - util::Log() << "input file generated by " << generator; - - // write .timestamp data file - std::string timestamp = header.get("osmosis_replication_timestamp"); - if (timestamp.empty()) - { - timestamp = "n/a"; - } - util::Log() << "timestamp: " << timestamp; - - storage::io::FileWriter timestamp_file(config.timestamp_file_name, - storage::io::FileWriter::GenerateFingerprint); - - timestamp_file.WriteFrom(timestamp.c_str(), timestamp.length()); - - // initialize vectors holding parsed objects - tbb::concurrent_vector> resulting_nodes; - tbb::concurrent_vector> resulting_ways; - tbb::concurrent_vector> resulting_restrictions; - - // setup restriction parser - const RestrictionParser restriction_parser(scripting_environment); - - // create a vector of iterators into the buffer - for (std::vector osm_elements; - const osmium::memory::Buffer buffer = reader.read(); - osm_elements.clear()) - { - for (auto iter = std::begin(buffer), end = std::end(buffer); iter != end; ++iter) - { - osm_elements.push_back(iter); - } - - // clear resulting vectors - resulting_nodes.clear(); - resulting_ways.clear(); - resulting_restrictions.clear(); - - 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) - { - extractor_callbacks->ProcessNode( - static_cast(*(osm_elements[result.first])), - result.second); - } - number_of_ways += resulting_ways.size(); - for (const auto &result : resulting_ways) - { - extractor_callbacks->ProcessWay( - static_cast(*(osm_elements[result.first])), result.second); - } - number_of_relations += resulting_restrictions.size(); - for (const auto &result : resulting_restrictions) - { - extractor_callbacks->ProcessRestriction(result); - } - } - TIMER_STOP(parsing); - util::Log() << "Parsing finished after " << TIMER_SEC(parsing) << " seconds"; - - util::Log() << "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(); - - if (extraction_containers.all_edges_list.empty()) - { - throw util::exception(std::string("There are no edges remaining after parsing.") + - SOURCE_REF); - } - - extraction_containers.PrepareData(scripting_environment, - config.output_file_name, - config.restriction_file_name, - config.names_file_name); - - WriteProfileProperties(config.profile_properties_output_path, - scripting_environment.GetProfileProperties()); - - TIMER_STOP(extracting); - util::Log() << "extraction finished after " << TIMER_SEC(extracting) << "s"; + storage::io::FileWriter writer(config.edge_based_node_weights_output_path, + storage::io::FileWriter::GenerateFingerprint); + storage::serialization::write(writer, edge_based_node_weights); } + TIMER_STOP(timer_write_node_weights); + util::Log() << "Done writing. (" << TIMER_SEC(timer_write_node_weights) << ")"; - { - // 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 - util::Log() << "Generating edge-expanded graph representation"; + util::Log() << "Computing strictly connected components ..."; + FindComponents(max_edge_id, edge_based_edge_list, edge_based_node_list); - TIMER_START(expansion); + util::Log() << "Building r-tree ..."; + TIMER_START(rtree); + BuildRTree(std::move(edge_based_node_list), std::move(node_is_startpoint), coordinates); - std::vector edge_based_node_list; - util::DeallocatingVector edge_based_edge_list; - std::vector node_is_startpoint; - std::vector edge_based_node_weights; - std::vector coordinates; - extractor::PackedOSMIDs osm_node_ids; + TIMER_STOP(rtree); - auto graph_size = BuildEdgeExpandedGraph(scripting_environment, - coordinates, - osm_node_ids, - edge_based_node_list, - node_is_startpoint, - edge_based_node_weights, - edge_based_edge_list, - config.intersection_class_data_output_path); + util::Log() << "Writing node map ..."; + files::writeNodes(config.node_output_path, coordinates, osm_node_ids); - auto number_of_node_based_nodes = graph_size.first; - auto max_edge_id = graph_size.second; + WriteEdgeBasedGraph(config.edge_graph_output_path, max_edge_id, edge_based_edge_list); - TIMER_STOP(expansion); + const auto nodes_per_second = + static_cast(number_of_node_based_nodes / TIMER_SEC(expansion)); + const auto edges_per_second = + static_cast((max_edge_id + 1) / TIMER_SEC(expansion)); - util::Log() << "Saving edge-based node weights to file."; - TIMER_START(timer_write_node_weights); - { - storage::io::FileWriter writer(config.edge_based_node_weights_output_path, - storage::io::FileWriter::GenerateFingerprint); - storage::serialization::write(writer, edge_based_node_weights); - } - TIMER_STOP(timer_write_node_weights); - util::Log() << "Done writing. (" << TIMER_SEC(timer_write_node_weights) << ")"; - - util::Log() << "Computing strictly connected components ..."; - FindComponents(max_edge_id, edge_based_edge_list, edge_based_node_list); - - util::Log() << "Building r-tree ..."; - TIMER_START(rtree); - BuildRTree(std::move(edge_based_node_list), std::move(node_is_startpoint), coordinates); - - TIMER_STOP(rtree); - - util::Log() << "Writing node map ..."; - files::writeNodes(config.node_output_path, coordinates, osm_node_ids); - - WriteEdgeBasedGraph(config.edge_graph_output_path, max_edge_id, edge_based_edge_list); - - const auto nodes_per_second = - static_cast(number_of_node_based_nodes / TIMER_SEC(expansion)); - const auto edges_per_second = - static_cast((max_edge_id + 1) / TIMER_SEC(expansion)); - - util::Log() << "Expansion: " << nodes_per_second << " nodes/sec and " << edges_per_second - << " edges/sec"; - util::Log() << "To prepare the data for routing, run: " - << "./osrm-contract " << config.output_file_name; - } + util::Log() << "Expansion: " << nodes_per_second << " nodes/sec and " << edges_per_second + << " edges/sec"; + util::Log() << "To prepare the data for routing, run: " + << "./osrm-contract " << config.output_file_name; return 0; } +std::vector Extractor::ParseOSMData(ScriptingEnvironment &scripting_environment, + const unsigned number_of_threads) +{ + TIMER_START(extracting); + + util::Log() << "Input file: " << config.input_path.filename().string(); + if (!config.profile_path.empty()) + { + util::Log() << "Profile: " << config.profile_path.filename().string(); + } + util::Log() << "Threads: " << number_of_threads; + + const osmium::io::File input_file(config.input_path.string()); + + osmium::io::Reader reader( + input_file, (config.use_metadata ? osmium::io::read_meta::yes : osmium::io::read_meta::no)); + + const osmium::io::Header header = reader.header(); + + unsigned number_of_nodes = 0; + unsigned number_of_ways = 0; + unsigned number_of_relations = 0; + + util::Log() << "Parsing in progress.."; + TIMER_START(parsing); + + ExtractionContainers extraction_containers; + auto extractor_callbacks = std::make_unique( + extraction_containers, scripting_environment.GetProfileProperties()); + + // setup raster sources + scripting_environment.SetupSources(); + + std::string generator = header.get("generator"); + if (generator.empty()) + { + generator = "unknown tool"; + } + util::Log() << "input file generated by " << generator; + + // write .timestamp data file + std::string timestamp = header.get("osmosis_replication_timestamp"); + if (timestamp.empty()) + { + timestamp = "n/a"; + } + util::Log() << "timestamp: " << timestamp; + + storage::io::FileWriter timestamp_file(config.timestamp_file_name, + storage::io::FileWriter::GenerateFingerprint); + + timestamp_file.WriteFrom(timestamp.c_str(), timestamp.length()); + + // initialize vectors holding parsed objects + tbb::concurrent_vector> resulting_nodes; + tbb::concurrent_vector> resulting_ways; + tbb::concurrent_vector> resulting_restrictions; + + std::vector restrictions = scripting_environment.GetRestrictions(); + // setup restriction parser + const RestrictionParser restriction_parser( + scripting_environment.GetProfileProperties().use_turn_restrictions, + config.parse_conditionals, + restrictions); + + // create a vector of iterators into the buffer + for (std::vector osm_elements; + const osmium::memory::Buffer buffer = reader.read(); + osm_elements.clear()) + { + for (auto iter = std::begin(buffer), end = std::end(buffer); iter != end; ++iter) + { + osm_elements.push_back(iter); + } + + // clear resulting vectors + resulting_nodes.clear(); + resulting_ways.clear(); + resulting_restrictions.clear(); + + 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) + { + extractor_callbacks->ProcessNode( + static_cast(*(osm_elements[result.first])), result.second); + } + number_of_ways += resulting_ways.size(); + for (const auto &result : resulting_ways) + { + extractor_callbacks->ProcessWay( + static_cast(*(osm_elements[result.first])), result.second); + } + number_of_relations += resulting_restrictions.size(); + for (const auto &result : resulting_restrictions) + { + extractor_callbacks->ProcessRestriction(result); + } + } + TIMER_STOP(parsing); + util::Log() << "Parsing finished after " << TIMER_SEC(parsing) << " seconds"; + + util::Log() << "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(); + + if (extraction_containers.all_edges_list.empty()) + { + throw util::exception(std::string("There are no edges remaining after parsing.") + + SOURCE_REF); + } + + extraction_containers.PrepareData(scripting_environment, + config.output_file_name, + config.restriction_file_name, + config.names_file_name); + + WriteProfileProperties(config.profile_properties_output_path, + scripting_environment.GetProfileProperties()); + + TIMER_STOP(extracting); + util::Log() << "extraction finished after " << TIMER_SEC(extracting) << "s"; + + return extraction_containers.unconditional_turn_restrictions; +} + void Extractor::WriteProfileProperties(const std::string &output_path, const ProfileProperties &properties) const { @@ -384,22 +392,6 @@ void Extractor::FindComponents(unsigned max_edge_id, } } -/** - \brief Build load restrictions from .restriction file - */ -std::shared_ptr Extractor::LoadRestrictionMap() -{ - storage::io::FileReader file_reader(config.restriction_file_name, - storage::io::FileReader::VerifyFingerprint); - std::vector restriction_list; - - util::loadRestrictionsFromFile(file_reader, restriction_list); - - util::Log() << " - " << restriction_list.size() << " restrictions."; - - return std::make_shared(restriction_list); -} - /** \brief Load node based graph from .osrm file */ @@ -444,12 +436,13 @@ Extractor::BuildEdgeExpandedGraph(ScriptingEnvironment &scripting_environment, std::vector &node_is_startpoint, std::vector &edge_based_node_weights, util::DeallocatingVector &edge_based_edge_list, - const std::string &intersection_class_output_file) + const std::string &intersection_class_output_file, + std::vector &turn_restrictions) { std::unordered_set barrier_nodes; std::unordered_set traffic_lights; - auto restriction_map = LoadRestrictionMap(); + auto restriction_map = std::make_shared(turn_restrictions); auto node_based_graph = LoadNodeBasedGraph(barrier_nodes, traffic_lights, coordinates, osm_node_ids); diff --git a/src/extractor/restriction_parser.cpp b/src/extractor/restriction_parser.cpp index a5b5b412c..68d25cc68 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/conditional_restrictions.hpp" #include "util/log.hpp" #include @@ -24,12 +24,14 @@ namespace osrm namespace extractor { -RestrictionParser::RestrictionParser(ScriptingEnvironment &scripting_environment) - : use_turn_restrictions(scripting_environment.GetProfileProperties().use_turn_restrictions) +RestrictionParser::RestrictionParser(bool use_turn_restrictions_, + bool parse_conditionals_, + std::vector &restrictions_) + : use_turn_restrictions(use_turn_restrictions_), parse_conditionals(parse_conditionals_), + restrictions(restrictions_) { if (use_turn_restrictions) { - restrictions = scripting_environment.GetRestrictions(); const unsigned count = restrictions.size(); if (count > 0) { @@ -54,9 +56,10 @@ RestrictionParser::RestrictionParser(ScriptingEnvironment &scripting_environment * in the corresponding profile. We use it for both namespacing restrictions, as in * restriction:motorcar as well as whitelisting if its in except:motorcar. */ -boost::optional +std::vector RestrictionParser::TryParse(const osmium::Relation &relation) const { + std::vector parsed_restrictions; // return if turn restrictions should be ignored if (!use_turn_restrictions) { @@ -65,10 +68,21 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const osmium::tags::KeyFilter filter(false); filter.add(true, "restriction"); + if (parse_conditionals) + { + filter.add(true, "restriction:conditional"); + for (const auto &namespaced : restrictions) + { + filter.add(true, "restriction:" + namespaced + ":conditional"); + } + } // Not only use restriction= but also e.g. restriction:motorcar= + // Include restriction:{mode}:conditional if flagged for (const auto &namespaced : restrictions) + { filter.add(true, "restriction:" + namespaced); + } const osmium::TagList &tag_list = relation.tags(); @@ -160,7 +174,42 @@ RestrictionParser::TryParse(const osmium::Relation &relation) const break; } } - return boost::make_optional(std::move(restriction_container)); + + // parse conditional tags + if (parse_conditionals) + { + osmium::tags::KeyFilter::iterator fi_begin(filter, tag_list.begin(), tag_list.end()); + osmium::tags::KeyFilter::iterator fi_end(filter, tag_list.end(), tag_list.end()); + for (; fi_begin != fi_end; ++fi_begin) + { + const std::string key(fi_begin->key()); + const std::string value(fi_begin->value()); + + // Parse condition and add independent value/condition pairs + const auto &parsed = osrm::util::ParseConditionalRestrictions(value); + + if (parsed.empty()) + continue; + + for (const auto &p : parsed) + { + std::vector hours = util::ParseOpeningHours(p.condition); + // found unrecognized condition, continue + if (hours.empty()) + return {}; + + restriction_container.restriction.condition = std::move(hours); + } + } + } + + // push back a copy of turn restriction + if (restriction_container.restriction.via.node != SPECIAL_NODEID && + restriction_container.restriction.from.node != SPECIAL_NODEID && + restriction_container.restriction.to.node != SPECIAL_NODEID) + parsed_restrictions.push_back(restriction_container); + + return parsed_restrictions; } bool RestrictionParser::ShouldIgnoreRestriction(const std::string &except_tag_string) const diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index 73ee74a11..721c658fe 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -498,6 +498,7 @@ void Sol2ScriptingEnvironment::ProcessElements( [&](const tbb::blocked_range &range) { ExtractionNode result_node; ExtractionWay result_way; + std::vector result_res; auto &local_context = this->GetSol2Context(); for (auto x = range.begin(), end = range.end(); x != end; ++x) @@ -525,8 +526,13 @@ void Sol2ScriptingEnvironment::ProcessElements( 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(*entity))); + result_res.clear(); + result_res = + restriction_parser.TryParse(static_cast(*entity)); + for (const InputRestrictionContainer &r : result_res) + { + resulting_restrictions.push_back(r); + } break; default: break; diff --git a/src/tools/contract.cpp b/src/tools/contract.cpp index 07a0e4fe7..5654ea160 100644 --- a/src/tools/contract.cpp +++ b/src/tools/contract.cpp @@ -2,6 +2,7 @@ #include "osrm/contractor.hpp" #include "osrm/contractor_config.hpp" #include "util/log.hpp" +#include "util/timezones.hpp" #include "util/version.hpp" #include @@ -61,7 +62,24 @@ return_code parseArguments(int argc, char *argv[], contractor::ContractorConfig &contractor_config.updater_config.log_edge_updates_factor) ->default_value(0.0), "Use with `--segment-speed-file`. Provide an `x` factor, by which Extractor will log edge " - "weights updated by more than this factor"); + "weights updated by more than this factor")( + "parse-conditionals-from-now", + boost::program_options::value(&contractor_config.updater_config.valid_now) + ->default_value(0), + "Optional for conditional turn restriction parsing, provide a UTC time stamp from " + "which " + "to evaluate the validity of conditional turn restrictions"); + + if (updater::SupportsShapefiles()) + { + config_options.add_options()("time-zone-file", + boost::program_options::value( + &contractor_config.updater_config.tz_file_path) + ->default_value(""), + "Required for conditional turn restriction parsing, provide a " + "shp or dbf file containing " + "time zone boundaries"); + } // 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"); diff --git a/src/tools/customize.cpp b/src/tools/customize.cpp index f675f309f..423365f2e 100644 --- a/src/tools/customize.cpp +++ b/src/tools/customize.cpp @@ -8,6 +8,7 @@ #include #include +#include #include @@ -51,7 +52,25 @@ parseArguments(int argc, char *argv[], customizer::CustomizationConfig &customiz ->default_value(0.0), "Use with `--segment-speed-file`. Provide an `x` factor, by which Extractor " "will log edge " - "weights updated by more than this factor"); + "weights updated by more than this factor")( + "parse-conditionals-from-now", + boost::program_options::value( + &customization_config.updater_config.valid_now) + ->default_value(0), + "Optional for conditional turn restriction parsing, provide a UTC time stamp from " + "which " + "to evaluate the validity of conditional turn restrictions"); + + if (updater::SupportsShapefiles()) + { + config_options.add_options()("time-zone-file", + boost::program_options::value( + &customization_config.updater_config.tz_file_path) + ->default_value(""), + "Required for conditional turn restriction parsing, provide a " + "shp or dbf file containing " + "time zone boundaries"); + } // hidden options, will be allowed on command line, but will not be // shown to the user diff --git a/src/tools/extract-conditionals.cpp b/src/tools/extract-conditionals.cpp index bea74fffe..bfeb213be 100644 --- a/src/tools/extract-conditionals.cpp +++ b/src/tools/extract-conditionals.cpp @@ -1,35 +1,13 @@ -#include "util/conditional_restrictions.hpp" +#include "tools/extract-conditionals.hpp" + #include "util/exception.hpp" -#include "util/for_each_pair.hpp" -#include "util/log.hpp" #include "util/opening_hours.hpp" +#include "util/timezones.hpp" #include "util/version.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include +#include #include #include -#include - -#include -#include //#include // Program arguments parsing functions @@ -181,23 +159,6 @@ void ParseCheckCommandArguments(const char *executable, po::notify(vm); } -// Data types and functions for conditional restriction -struct ConditionalRestriction -{ - osmium::object_id_type from; - osmium::object_id_type via; - osmium::object_id_type to; - std::string tag; - std::string value; - std::string condition; -}; - -struct LocatedConditionalRestriction -{ - osmium::Location location; - ConditionalRestriction restriction; -}; - // clang-format off BOOST_FUSION_ADAPT_ADT(LocatedConditionalRestriction, (osmium::object_id_type, osmium::object_id_type, obj.restriction.from, obj.restriction.from = val) @@ -210,189 +171,6 @@ BOOST_FUSION_ADAPT_ADT(LocatedConditionalRestriction, (std::int32_t, std::int32_t, obj.location.lat(), obj.location.set_lat(val))) // clang-format on -// The first pass relations handler that collects conditional restrictions -class ConditionalRestrictionsCollector : public osmium::handler::Handler -{ - public: - ConditionalRestrictionsCollector(std::vector &restrictions) - : restrictions(restrictions) - { - tag_filter.add(true, std::regex("^restriction.*:conditional$")); - } - - void relation(const osmium::Relation &relation) const - { - // Check if relation contains any ":conditional" tag - const osmium::TagList &tags = relation.tags(); - typename decltype(tag_filter)::iterator first(tag_filter, tags.begin(), tags.end()); - typename decltype(tag_filter)::iterator last(tag_filter, tags.end(), tags.end()); - if (first == last) - return; - - // Get member references of from(way) -> via(node) -> to(way) - auto from = invalid_id, via = invalid_id, to = invalid_id; - for (const auto &member : relation.members()) - { - if (member.ref() == 0) - continue; - - if (member.type() == osmium::item_type::node && strcmp(member.role(), "via") == 0) - { - via = member.ref(); - } - else if (member.type() == osmium::item_type::way && strcmp(member.role(), "from") == 0) - { - from = member.ref(); - } - else if (member.type() == osmium::item_type::way && strcmp(member.role(), "to") == 0) - { - to = member.ref(); - } - } - - if (from == invalid_id || via == invalid_id || to == invalid_id) - return; - - for (; first != last; ++first) - { - // Parse condition and add independent value/condition pairs - const auto &parsed = osrm::util::ParseConditionalRestrictions(first->value()); - - if (parsed.empty()) - { - osrm::util::Log(logWARNING) << "Conditional restriction parsing failed for \"" - << first->value() << "\" at the turn " << from << " -> " - << via << " -> " << to; - continue; - } - - for (auto &restriction : parsed) - { - restrictions.push_back( - {from, via, to, first->key(), restriction.value, restriction.condition}); - } - } - } - - private: - const osmium::object_id_type invalid_id = std::numeric_limits::max(); - osmium::tags::Filter tag_filter; - std::vector &restrictions; -}; - -// The second pass handler that collects related nodes and ways. -// process_restrictions method calls for every collected conditional restriction -// a callback function with the prototype: -// (location, from node, via node, to node, tag, value, condition) -// If the restriction value starts with "only_" the callback function will be called -// for every edge adjacent to `via` node but not containing `to` node. -class ConditionalRestrictionsHandler : public osmium::handler::Handler -{ - - public: - ConditionalRestrictionsHandler(const std::vector &restrictions) - : restrictions(restrictions) - { - for (auto &restriction : restrictions) - related_vias.insert(restriction.via); - - via_adjacency.reserve(restrictions.size() * 2); - } - - void node(const osmium::Node &node) - { - const osmium::object_id_type id = node.id(); - if (related_vias.find(id) != related_vias.end()) - { - location_storage.set(static_cast(id), node.location()); - } - } - - void way(const osmium::Way &way) - { - const auto &nodes = way.nodes(); - - if (related_vias.find(nodes.front().ref()) != related_vias.end()) - via_adjacency.push_back(std::make_tuple(nodes.front().ref(), way.id(), nodes[1].ref())); - - if (related_vias.find(nodes.back().ref()) != related_vias.end()) - via_adjacency.push_back( - std::make_tuple(nodes.back().ref(), way.id(), nodes[nodes.size() - 2].ref())); - } - - template void process(Callback callback) - { - location_storage.sort(); - std::sort(via_adjacency.begin(), via_adjacency.end()); - - auto adjacent_nodes = [this](auto node) { - auto first = std::lower_bound( - via_adjacency.begin(), via_adjacency.end(), adjacency_type{node, 0, 0}); - auto last = - std::upper_bound(first, via_adjacency.end(), adjacency_type{node + 1, 0, 0}); - return std::make_pair(first, last); - }; - - auto find_node = [](auto nodes, auto way) { - return std::find_if( - nodes.first, nodes.second, [way](const auto &n) { return std::get<1>(n) == way; }); - }; - - for (auto &restriction : restrictions) - { - auto nodes = adjacent_nodes(restriction.via); - auto from = find_node(nodes, restriction.from); - if (from == nodes.second) - continue; - - const auto &location = - location_storage.get(static_cast(restriction.via)); - - if (boost::algorithm::starts_with(restriction.value, "only_")) - { - for (auto it = nodes.first; it != nodes.second; ++it) - { - if (std::get<1>(*it) == restriction.to) - continue; - - callback(location, - std::get<2>(*from), - restriction.via, - std::get<2>(*it), - restriction.tag, - restriction.value, - restriction.condition); - } - } - else - { - auto to = find_node(nodes, restriction.to); - if (to != nodes.second) - { - callback(location, - std::get<2>(*from), - restriction.via, - std::get<2>(*to), - restriction.tag, - restriction.value, - restriction.condition); - } - } - } - } - - private: - using index_type = - osmium::index::map::SparseMemArray; - using adjacency_type = - std::tuple; - - const std::vector &restrictions; - std::unordered_set related_vias; - std::vector via_adjacency; - index_type location_storage; -}; - int RestrictionsDumpCommand(const char *executable, const std::vector &arguments) { std::string osm_filename, csv_filename; @@ -444,22 +222,6 @@ int RestrictionsDumpCommand(const char *executable, const std::vector(id), node.location()); - } - } - - void way(const osmium::Way &way) - { - const osmium::TagList &tags = way.tags(); - typename decltype(tag_filter)::iterator first(tag_filter, tags.begin(), tags.end()); - typename decltype(tag_filter)::iterator last(tag_filter, tags.end(), tags.end()); - if (first == last) - return; - - for (; first != last; ++first) - { - // Parse condition and add independent value/condition pairs - const auto &parsed = osrm::util::ParseConditionalRestrictions(first->value()); - - if (parsed.empty()) - { - osrm::util::Log(logWARNING) << "Conditional speed limit parsing failed for \"" - << first->value() << "\" on the way " << way.id(); - continue; - } - - // Collect node IDs for the second pass over nodes - std::transform(way.nodes().begin(), - way.nodes().end(), - std::inserter(related_nodes, related_nodes.end()), - [](const auto &node_ref) { return node_ref.ref(); }); - - // Collect speed limits - for (auto &speed_limit : parsed) - { - // Convert value to an integer - int speed_limit_value; - { - namespace qi = boost::spirit::qi; - std::string::const_iterator first(speed_limit.value.begin()), - last(speed_limit.value.end()); - if (!qi::parse(first, last, qi::int_, speed_limit_value) || first != last) - { - osrm::util::Log(logWARNING) - << "Conditional speed limit has non-integer value \"" - << speed_limit.value << "\" on the way " << way.id(); - continue; - } - } - - std::string key = first->key(); - const bool is_forward = key.find(":forward:") != std::string::npos; - const bool is_backward = key.find(":backward:") != std::string::npos; - const bool is_direction_defined = is_forward || is_backward; - - if (is_forward || !is_direction_defined) - { - osrm::util::for_each_pair(way.nodes().cbegin(), - way.nodes().cend(), - [&](const auto &from, const auto &to) { - speed_limits.push_back( - ConditionalSpeedLimit{from.ref(), - to.ref(), - key, - speed_limit_value, - speed_limit.condition}); - }); - } - - if (is_backward || !is_direction_defined) - { - osrm::util::for_each_pair(way.nodes().cbegin(), - way.nodes().cend(), - [&](const auto &to, const auto &from) { - speed_limits.push_back( - ConditionalSpeedLimit{from.ref(), - to.ref(), - key, - speed_limit_value, - speed_limit.condition}); - }); - } - } - } - } - - template void process(Callback callback) - { - location_storage.sort(); - - for (auto &speed_limit : speed_limits) - { - const auto &location_from = location_storage.get( - static_cast(speed_limit.from)); - const auto &location_to = - location_storage.get(static_cast(speed_limit.to)); - osmium::Location location{(location_from.lon() + location_to.lon()) / 2, - (location_from.lat() + location_to.lat()) / 2}; - - callback(location, - speed_limit.from, - speed_limit.to, - speed_limit.tag, - speed_limit.value, - speed_limit.condition); - } - } - - private: - using index_type = - osmium::index::map::SparseMemArray; - - osmium::tags::Filter tag_filter; - std::unordered_set related_nodes; - std::vector speed_limits; - index_type location_storage; -}; - int SpeedLimitsDumpCommand(const char *executable, const std::vector &arguments) { std::string osm_filename, csv_filename; @@ -646,119 +278,6 @@ int SpeedLimitsDumpCommand(const char *executable, const std::vector>; -using polygon_t = boost::geometry::model::polygon; -using box_t = boost::geometry::model::box; -using rtree_t = - boost::geometry::index::rtree, boost::geometry::index::rstar<8>>; -using local_time_t = std::pair; - -// Function loads time zone shape polygons, computes a zone local time for utc_time, -// creates a lookup R-tree and returns a lambda function that maps a point -// to the corresponding local time -auto LoadLocalTimesRTree(const std::string &tz_shapes_filename, std::time_t utc_time) -{ - // Load time zones shapes and collect local times of utc_time - auto shphandle = SHPOpen(tz_shapes_filename.c_str(), "rb"); - auto dbfhandle = DBFOpen(tz_shapes_filename.c_str(), "rb"); - - BOOST_SCOPE_EXIT(&shphandle, &dbfhandle) - { - DBFClose(dbfhandle); - SHPClose(shphandle); - } - BOOST_SCOPE_EXIT_END - - if (!shphandle || !dbfhandle) - { - throw osrm::util::exception("failed to open " + tz_shapes_filename + ".shp or " + - tz_shapes_filename + ".dbf file"); - } - - int num_entities, shape_type; - SHPGetInfo(shphandle, &num_entities, &shape_type, NULL, NULL); - if (num_entities != DBFGetRecordCount(dbfhandle)) - { - throw osrm::util::exception("inconsistent " + tz_shapes_filename + ".shp and " + - tz_shapes_filename + ".dbf files"); - } - - const auto tzid = DBFGetFieldIndex(dbfhandle, "TZID"); - if (tzid == -1) - { - throw osrm::util::exception("did not find field called 'TZID' in the " + - tz_shapes_filename + ".dbf file"); - } - - // Lambda function that returns local time in the tzname time zone - // Thread safety: MT-Unsafe const:env - std::unordered_map local_time_memo; - auto get_local_time_in_tz = [utc_time, &local_time_memo](const char *tzname) { - auto it = local_time_memo.find(tzname); - if (it == local_time_memo.end()) - { - struct tm timeinfo; - setenv("TZ", tzname, 1); - tzset(); - localtime_r(&utc_time, &timeinfo); - it = local_time_memo.insert({tzname, timeinfo}).first; - } - - return it->second; - }; - - // Get all time zone shapes and save local times in a vector - std::vector polygons; - std::vector local_times; - for (int shape = 0; shape < num_entities; ++shape) - { - auto object = SHPReadObject(shphandle, shape); - BOOST_SCOPE_EXIT(&object) { SHPDestroyObject(object); } - BOOST_SCOPE_EXIT_END - - if (object && object->nSHPType == SHPT_POLYGON) - { - // Find time zone polygon and place its bbox in into R-Tree - polygon_t polygon; - for (int vertex = 0; vertex < object->nVertices; ++vertex) - { - polygon.outer().emplace_back(object->padfX[vertex], object->padfY[vertex]); - } - - polygons.emplace_back(boost::geometry::return_envelope(polygon), - local_times.size()); - - // Get time zone name and emplace polygon and local time for the UTC input - const auto tzname = DBFReadStringAttribute(dbfhandle, shape, tzid); - local_times.emplace_back(local_time_t{polygon, get_local_time_in_tz(tzname)}); - - // std::cout << boost::geometry::dsv(boost::geometry::return_envelope(polygon)) - // << " " << tzname << " " << asctime(&local_times.back().second); - } - } - - // Create R-tree for collected shape polygons - rtree_t rtree(polygons); - - // Return a lambda function that maps the input point and UTC time to the local time - // binds rtree and local_times - return [rtree, local_times](const point_t &point) { - std::vector result; - rtree.query(boost::geometry::index::intersects(point), std::back_inserter(result)); - for (const auto v : result) - { - const auto index = v.second; - if (boost::geometry::within(point, local_times[index].first)) - return local_times[index].second; - } - return tm{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - }; -} - int RestrictionsCheckCommand(const char *executable, const std::vector &arguments) { std::string input_filename, output_filename; diff --git a/src/tools/extract.cpp b/src/tools/extract.cpp index a76a69dfb..22d6edff4 100644 --- a/src/tools/extract.cpp +++ b/src/tools/extract.cpp @@ -49,7 +49,13 @@ return_code parseArguments(int argc, char *argv[], extractor::ExtractorConfig &e boost::program_options::bool_switch(&extractor_config.use_metadata) ->implicit_value(true) ->default_value(false), - "Use metada during osm parsing (This can affect the extraction performance)."); + "Use metadata during osm parsing (This can affect the extraction performance).")( + "parse-conditional-restrictions", + boost::program_options::value(&extractor_config.parse_conditionals) + ->implicit_value(true) + ->default_value(false), + "Save conditional restrictions found during extraction to disk for use " + "during contraction"); bool dummy; // hidden options, will be allowed on command line, but will not be diff --git a/src/updater/updater.cpp b/src/updater/updater.cpp index 8745600ec..5bafe7534 100644 --- a/src/updater/updater.cpp +++ b/src/updater/updater.cpp @@ -1,11 +1,12 @@ #include "updater/updater.hpp" - #include "updater/csv_source.hpp" #include "extractor/compressed_edge_container.hpp" #include "extractor/edge_based_graph_factory.hpp" #include "extractor/files.hpp" #include "extractor/node_based_edge.hpp" +#include "extractor/packed_osm_ids.hpp" +#include "extractor/restriction.hpp" #include "storage/io.hpp" @@ -15,15 +16,18 @@ #include "util/graph_loader.hpp" #include "util/integer_range.hpp" #include "util/log.hpp" +#include "util/opening_hours.hpp" #include "util/static_graph.hpp" #include "util/static_rtree.hpp" #include "util/string_util.hpp" +#include "util/timezones.hpp" #include "util/timing_util.hpp" #include "util/typedefs.hpp" #include #include -#include +#include +#include #include #include @@ -44,6 +48,25 @@ #include #include +namespace std +{ +template struct hash> +{ + size_t operator()(const std::tuple &t) const + { + return hash_val(std::get<0>(t), std::get<1>(t), std::get<2>(t)); + } +}; + +template struct hash> +{ + size_t operator()(const std::tuple &t) const + { + return hash_val(std::get<0>(t), std::get<1>(t)); + } +}; +} + namespace osrm { namespace updater @@ -135,12 +158,10 @@ tbb::concurrent_vector updateSegmentData(const UpdaterConfig &config, const extractor::ProfileProperties &profile_properties, const SegmentLookupTable &segment_speed_lookup, - extractor::SegmentDataContainer &segment_data) + extractor::SegmentDataContainer &segment_data, + std::vector &coordinates, + extractor::PackedOSMIDs &osm_node_ids) { - std::vector coordinates; - extractor::PackedOSMIDs osm_node_ids; - extractor::files::readNodes(config.node_based_graph_path, coordinates, osm_node_ids); - // vector to count used speeds for logging // size offset by one since index 0 is used for speeds not from external file using counters_type = std::vector; @@ -379,26 +400,33 @@ updateTurnPenalties(const UpdaterConfig &config, const extractor::ProfileProperties &profile_properties, const TurnLookupTable &turn_penalty_lookup, std::vector &turn_weight_penalties, - std::vector &turn_duration_penalties) + std::vector &turn_duration_penalties, + extractor::PackedOSMIDs osm_node_ids) { const auto weight_multiplier = profile_properties.GetWeightMultiplier(); - const auto turn_penalties_index_region = + const auto turn_index_region = mmapFile(config.turn_penalties_index_path, boost::interprocess::read_only); // Mapped file pointer for turn indices const extractor::lookup::TurnIndexBlock *turn_index_blocks = reinterpret_cast( - turn_penalties_index_region.get_address()); + turn_index_region.get_address()); BOOST_ASSERT(is_aligned(turn_index_blocks)); + // Get the turn penalty and update to the new value if required std::vector updated_turns; for (std::uint64_t edge_index = 0; edge_index < turn_weight_penalties.size(); ++edge_index) { - // Get the turn penalty and update to the new value if required - const auto &turn_index = turn_index_blocks[edge_index]; + // edges are stored by internal OSRM ids, these need to be mapped back to OSM ids + const extractor::lookup::TurnIndexBlock internal_turn = turn_index_blocks[edge_index]; + const Turn osm_turn{osm_node_ids[internal_turn.from_id], + osm_node_ids[internal_turn.via_id], + osm_node_ids[internal_turn.to_id]}; + // original turn weight/duration values auto turn_weight_penalty = turn_weight_penalties[edge_index]; auto turn_duration_penalty = turn_duration_penalties[edge_index]; - if (auto value = turn_penalty_lookup(turn_index)) + + if (auto value = turn_penalty_lookup(osm_turn)) { turn_duration_penalty = boost::numeric_cast(std::round(value->duration * 10.)); @@ -413,9 +441,113 @@ updateTurnPenalties(const UpdaterConfig &config, if (turn_weight_penalty < 0) { - util::Log(logWARNING) << "Negative turn penalty at " << turn_index.from_id << ", " - << turn_index.via_id << ", " << turn_index.to_id - << ": turn penalty " << turn_weight_penalty; + util::Log(logWARNING) << "Negative turn penalty at " << osm_turn.from << ", " + << osm_turn.via << ", " << osm_turn.to << ": turn penalty " + << turn_weight_penalty; + } + } + + return updated_turns; +} + +bool IsRestrictionValid(const Timezoner &tz_handler, + const extractor::TurnRestriction &turn, + const std::vector &coordinates, + const extractor::PackedOSMIDs &osm_node_ids) +{ + const auto via_node = osm_node_ids[turn.via.node]; + const auto from_node = osm_node_ids[turn.from.node]; + const auto to_node = osm_node_ids[turn.to.node]; + if (turn.condition.empty()) + { + osrm::util::Log(logWARNING) << "Condition parsing failed for the turn " << from_node + << " -> " << via_node << " -> " << to_node; + return false; + } + + const auto lon = static_cast(toFloating(coordinates[turn.via.node].lon)); + const auto lat = static_cast(toFloating(coordinates[turn.via.node].lat)); + const auto &condition = turn.condition; + + // Get local time of the restriction + const auto &local_time = tz_handler(point_t{lon, lat}); + + // TODO: check restriction type [:][:] + // http://wiki.openstreetmap.org/wiki/Conditional_restrictions#Tagging + + // TODO: parsing will fail for combined conditions, e.g. Sa-Su AND weight>7 + // http://wiki.openstreetmap.org/wiki/Conditional_restrictions#Combined_conditions:_AND + + if (osrm::util::CheckOpeningHours(condition, local_time)) + return true; + + return false; +} + +std::vector +updateConditionalTurns(const UpdaterConfig &config, + std::vector &turn_weight_penalties, + const std::vector &conditional_turns, + std::vector &coordinates, + extractor::PackedOSMIDs &osm_node_ids, + Timezoner time_zone_handler) +{ + const auto turn_index_region = + mmapFile(config.turn_penalties_index_path, boost::interprocess::read_only); + // Mapped file pointer for turn indices + const extractor::lookup::TurnIndexBlock *turn_index_blocks = + reinterpret_cast( + turn_index_region.get_address()); + BOOST_ASSERT(is_aligned(turn_index_blocks)); + + std::vector updated_turns; + if (conditional_turns.size() == 0) + return updated_turns; + + // TODO make this into a function + LookupTable, NodeID> is_only_lookup; + std::unordered_set, + std::hash>> + is_no_set; + for (const auto &c : conditional_turns) + { + // only add restrictions to the lookups if the restriction is valid now + if (!IsRestrictionValid(time_zone_handler, c, coordinates, osm_node_ids)) + continue; + if (c.flags.is_only) + { + is_only_lookup.lookup.push_back({std::make_tuple(c.from.node, c.via.node), c.to.node}); + } + else + { + is_no_set.insert({std::make_tuple(c.from.node, c.via.node, c.to.node)}); + } + } + + for (std::uint64_t edge_index = 0; edge_index < turn_weight_penalties.size(); ++edge_index) + { + const extractor::lookup::TurnIndexBlock internal_turn = turn_index_blocks[edge_index]; + + const auto is_no_tuple = + std::make_tuple(internal_turn.from_id, internal_turn.via_id, internal_turn.to_id); + const auto is_only_tuple = std::make_tuple(internal_turn.from_id, internal_turn.via_id); + // turn has a no_* restriction + if (is_no_set.find(is_no_tuple) != is_no_set.end()) + { + util::Log(logDEBUG) << "Conditional penalty set on edge: " << edge_index; + turn_weight_penalties[edge_index] = INVALID_TURN_PENALTY; + updated_turns.push_back(edge_index); + } + // turn has an only_* restriction + else if (is_only_lookup(is_only_tuple)) + { + // with only_* restrictions, the turn on which the restriction is tagged is valid + if (*is_only_lookup(is_only_tuple) == internal_turn.to_id) + continue; + + util::Log(logDEBUG) << "Conditional penalty set on edge: " << edge_index; + turn_weight_penalties[edge_index] = INVALID_TURN_PENALTY; + updated_turns.push_back(edge_index); } } @@ -425,8 +557,8 @@ updateTurnPenalties(const UpdaterConfig &config, Updater::NumNodesAndEdges Updater::LoadAndUpdateEdgeExpandedGraph() const { - std::vector edge_based_edge_list; std::vector node_weights; + std::vector edge_based_edge_list; auto max_edge_id = Updater::LoadAndUpdateEdgeExpandedGraph(edge_based_edge_list, node_weights); return std::make_tuple(max_edge_id + 1, std::move(edge_based_edge_list)); } @@ -438,6 +570,8 @@ Updater::LoadAndUpdateEdgeExpandedGraph(std::vector &e TIMER_START(load_edges); EdgeID max_edge_id = 0; + std::vector node_coordinates; + extractor::PackedOSMIDs osm_node_ids; { storage::io::FileReader reader(config.edge_based_graph_path, @@ -446,12 +580,16 @@ Updater::LoadAndUpdateEdgeExpandedGraph(std::vector &e edge_based_edge_list.resize(num_edges); max_edge_id = reader.ReadOne(); reader.ReadInto(edge_based_edge_list); + + extractor::files::readNodes(config.node_based_graph_path, node_coordinates, osm_node_ids); } + const bool update_conditional_turns = + !config.turn_restrictions_path.empty() && config.valid_now; const bool update_edge_weights = !config.segment_speed_lookup_paths.empty(); const bool update_turn_penalties = !config.turn_penalty_lookup_paths.empty(); - if (!update_edge_weights && !update_turn_penalties) + if (!update_edge_weights && !update_turn_penalties && !update_conditional_turns) { saveDatasourcesNames(config); return max_edge_id; @@ -467,7 +605,7 @@ Updater::LoadAndUpdateEdgeExpandedGraph(std::vector &e extractor::ProfileProperties profile_properties; std::vector turn_weight_penalties; std::vector turn_duration_penalties; - if (update_edge_weights || update_turn_penalties) + if (update_edge_weights || update_turn_penalties || update_conditional_turns) { const auto load_segment_data = [&] { extractor::files::readSegmentData(config.geometry_path, segment_data); @@ -508,28 +646,70 @@ Updater::LoadAndUpdateEdgeExpandedGraph(std::vector &e load_profile_properties); } + std::vector conditional_turns; + if (update_conditional_turns) + { + using storage::io::FileReader; + FileReader reader(config.turn_restrictions_path, FileReader::VerifyFingerprint); + extractor::serialization::read(reader, conditional_turns); + } + tbb::concurrent_vector updated_segments; if (update_edge_weights) { auto segment_speed_lookup = csv::readSegmentValues(config.segment_speed_lookup_paths); TIMER_START(segment); - updated_segments = - updateSegmentData(config, profile_properties, segment_speed_lookup, segment_data); + updated_segments = updateSegmentData(config, + profile_properties, + segment_speed_lookup, + segment_data, + node_coordinates, + osm_node_ids); // Now save out the updated compressed geometries extractor::files::writeSegmentData(config.geometry_path, segment_data); TIMER_STOP(segment); util::Log() << "Updating segment data took " << TIMER_MSEC(segment) << "ms."; } + auto turn_penalty_lookup = csv::readTurnValues(config.turn_penalty_lookup_paths); if (update_turn_penalties) { - auto turn_penalty_lookup = csv::readTurnValues(config.turn_penalty_lookup_paths); auto updated_turn_penalties = updateTurnPenalties(config, profile_properties, turn_penalty_lookup, turn_weight_penalties, - turn_duration_penalties); + turn_duration_penalties, + osm_node_ids); + const auto offset = updated_segments.size(); + updated_segments.resize(offset + updated_turn_penalties.size()); + // we need to re-compute all edges that have updated turn penalties. + // this marks it for re-computation + std::transform(updated_turn_penalties.begin(), + updated_turn_penalties.end(), + updated_segments.begin() + offset, + [&node_data, &edge_based_edge_list](const std::uint64_t turn_id) { + const auto node_id = edge_based_edge_list[turn_id].source; + return node_data.GetGeometryID(node_id); + }); + } + + if (update_conditional_turns) + { + // initialize instance of class that handles time zone resolution + if (config.valid_now <= 0) + { + util::Log(logERROR) << "Given UTC time is invalid: " << config.valid_now; + throw; + } + const Timezoner time_zone_handler = Timezoner(config.tz_file_path, config.valid_now); + + auto updated_turn_penalties = updateConditionalTurns(config, + turn_weight_penalties, + conditional_turns, + node_coordinates, + osm_node_ids, + time_zone_handler); const auto offset = updated_segments.size(); updated_segments.resize(offset + updated_turn_penalties.size()); // we need to re-compute all edges that have updated turn penalties. diff --git a/src/util/conditional_restrictions.cpp b/src/util/conditional_restrictions.cpp new file mode 100644 index 000000000..ed37873c8 --- /dev/null +++ b/src/util/conditional_restrictions.cpp @@ -0,0 +1,96 @@ +#include "util/conditional_restrictions.hpp" + +#include +#include +#include + +namespace osrm +{ +namespace util +{ + +#ifndef NDEBUG +// Debug output stream operators for use with BOOST_SPIRIT_DEBUG +inline std::ostream &operator<<(std::ostream &stream, const ConditionalRestriction &restriction) +{ + return stream << restriction.value << "=" << restriction.condition; +} +#endif +} +} + +BOOST_FUSION_ADAPT_STRUCT(osrm::util::ConditionalRestriction, + (std::string, value)(std::string, condition)) + +namespace osrm +{ +namespace util +{ +namespace detail +{ + +namespace +{ +namespace ph = boost::phoenix; +namespace qi = boost::spirit::qi; +} + +template +struct conditional_restrictions_grammar + : qi::grammar()> +{ + // http://wiki.openstreetmap.org/wiki/Conditional_restrictions + conditional_restrictions_grammar() : conditional_restrictions_grammar::base_type(restrictions) + { + using qi::_1; + using qi::_val; + using qi::lit; + + // clang-format off + + restrictions + = restriction % ';' + ; + + restriction + = value >> '@' >> condition + ; + + value + = +(qi::char_ - '@') + ; + + condition + = *qi::blank + >> (lit('(') >> qi::as_string[qi::no_skip[*~lit(')')]][_val = _1] >> lit(')') + | qi::as_string[qi::no_skip[*~lit(';')]][_val = _1] + ) + ; + + // clang-format on + + BOOST_SPIRIT_DEBUG_NODES((restrictions)(restriction)(value)(condition)); + } + + qi::rule()> restrictions; + qi::rule restriction; + qi::rule value, condition; +}; +} + +std::vector ParseConditionalRestrictions(const std::string &str) +{ + auto it(str.begin()), end(str.end()); + const detail::conditional_restrictions_grammar static grammar; + + std::vector result; + bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result); + + if (!ok || it != end) + return std::vector(); + + return result; +} + +} // util +} // osrm diff --git a/src/util/opening_hours.cpp b/src/util/opening_hours.cpp new file mode 100644 index 000000000..0bbf9def4 --- /dev/null +++ b/src/util/opening_hours.cpp @@ -0,0 +1,435 @@ +#include "util/opening_hours.hpp" + +#include +#include + +#include + +#include +#include +#include +#include + +namespace osrm +{ +namespace util +{ + +#ifndef NDEBUG +// Debug output stream operators for use with BOOST_SPIRIT_DEBUG +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Modifier value) +{ + switch (value) + { + case OpeningHours::unknown: + return stream << "unknown"; + case OpeningHours::open: + return stream << "open"; + case OpeningHours::closed: + return stream << "closed"; + case OpeningHours::off: + return stream << "off"; + case OpeningHours::is24_7: + return stream << "24/7"; + } + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Time::Event value) +{ + switch (value) + { + case OpeningHours::Time::dawn: + return stream << "dawn"; + case OpeningHours::Time::sunrise: + return stream << "sunrise"; + case OpeningHours::Time::sunset: + return stream << "sunset"; + case OpeningHours::Time::dusk: + return stream << "dusk"; + default: + break; + } + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Time &value) +{ + boost::io::ios_flags_saver ifs(stream); + if (value.event == OpeningHours::Time::invalid) + return stream << "???"; + if (value.event == OpeningHours::Time::none) + return stream << std::setfill('0') << std::setw(2) << value.minutes / 60 << ":" + << std::setfill('0') << std::setw(2) << value.minutes % 60; + stream << value.event; + if (value.minutes != 0) + stream << value.minutes; + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::TimeSpan &value) +{ + return stream << value.from << "-" << value.to; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Monthday &value) +{ + bool empty = true; + if (value.year != 0) + { + stream << (int)value.year; + empty = false; + }; + if (value.month != 0) + { + stream << (empty ? "" : "/") << (int)value.month; + empty = false; + }; + if (value.day != 0) + { + stream << (empty ? "" : "/") << (int)value.day; + }; + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::WeekdayRange &value) +{ + boost::io::ios_flags_saver ifs(stream); + return stream << std::hex << std::setfill('0') << std::setw(2) << value.weekdays; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::MonthdayRange &value) +{ + return stream << value.from << "-" << value.to; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours &value) +{ + if (value.modifier == OpeningHours::is24_7) + return stream << OpeningHours::is24_7; + + for (auto x : value.monthdays) + stream << x << ", "; + for (auto x : value.weekdays) + stream << x << ", "; + for (auto x : value.times) + stream << x << ", "; + return stream << " |" << value.modifier << "|"; +} +#endif + +namespace detail +{ + +namespace +{ +namespace ph = boost::phoenix; +namespace qi = boost::spirit::qi; +} + +template +struct opening_hours_grammar : qi::grammar()> +{ + // http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification + opening_hours_grammar() : opening_hours_grammar::base_type(time_domain) + { + using qi::_1; + using qi::_a; + using qi::_b; + using qi::_c; + using qi::_r1; + using qi::_pass; + using qi::_val; + using qi::eoi; + using qi::lit; + using qi::char_; + using qi::uint_; + using oh = osrm::util::OpeningHours; + + // clang-format off + + // General syntax + time_domain = rule_sequence[ph::push_back(_val, _1)] % any_rule_separator; + + rule_sequence + = lit("24/7")[ph::bind(&oh::modifier, _val) = oh::is24_7] + | (selector_sequence[_val = _1] >> -rule_modifier[ph::bind(&oh::modifier, _val) = _1] >> -comment) + | comment + ; + + any_rule_separator = char_(';') | lit("||") | additional_rule_separator; + + additional_rule_separator = char_(','); + + // Rule modifiers + rule_modifier.add + ("unknown", oh::unknown) + ("open", oh::open) + ("closed", oh::closed) + ("off", oh::off) + ; + + // Selectors + selector_sequence = (wide_range_selectors(_a) >> small_range_selectors(_a))[_val = _a]; + + wide_range_selectors + = (-monthday_selector(_r1) + >> -year_selector(_r1) + >> -week_selector(_r1) // TODO week_selector + ) >> -lit(':') + ; + + small_range_selectors = -(weekday_selector(_r1) >> (&~lit(',') | eoi)) >> -time_selector(_r1); + + // Time selector + time_selector = (timespan % ',')[ph::bind(&OpeningHours::times, _r1) = _1]; + + timespan + = (time[_a = _1] + >> -(lit('+')[_b = ph::construct(24, 0)] + | ('-' >> extended_time[_b = _1] + >> -('+' | '/' >> (minute | hour_minutes)))) + )[_val = ph::construct(_a, _b)] + ; + + time = hour_minutes | variable_time; + + extended_time = extended_hour_minutes | variable_time; + + variable_time + = event[_val = ph::construct(_1)] + | ('(' >> event[_a = _1] >> plus_or_minus[_b = _1] >> hour_minutes[_c = _1] >> ')') + [_val = ph::construct(_a, _b, _c)] + ; + + event.add + ("dawn", OpeningHours::Time::dawn) + ("sunrise", OpeningHours::Time::sunrise) + ("sunset", OpeningHours::Time::sunset) + ("dusk", OpeningHours::Time::dusk) + ; + + // Weekday selector + weekday_selector + = (holiday_sequence(_r1) >> -(char_(", ") >> weekday_sequence(_r1))) + | (weekday_sequence(_r1) >> -(char_(", ") >> holiday_sequence(_r1))) + ; + + weekday_sequence = (weekday_range % ',')[ph::bind(&OpeningHours::weekdays, _r1) = _1]; + + weekday_range + = wday[_a = _1, _b = _1] + >> -(('-' >> wday[_b = _1]) + | ('[' >> (nth_entry % ',') >> ']' >> -day_offset)) + [_val = ph::construct(_a, _b)] + ; + + holiday_sequence = (lit("SH") >> -day_offset) | lit("PH"); + + nth_entry = nth | nth >> '-' >> nth | '-' >> nth; + + nth = char_("12345"); + + day_offset = plus_or_minus >> uint_ >> lit("days"); + + // Week selector + week_selector = (lit("week ") >> week) % ','; + + week = weeknum >> -('-' >> weeknum >> -('/' >> uint_)); + + // Month selector + monthday_selector = (monthday_range % ',')[ph::bind(&OpeningHours::monthdays, _r1) = _1]; + + monthday_range + = (date_from[ph::bind(&OpeningHours::MonthdayRange::from, _val) = _1] + >> -date_offset + >> '-' + >> date_to[ph::bind(&OpeningHours::MonthdayRange::to, _val) = _1] + >> -date_offset) + | (date_from[ph::bind(&OpeningHours::MonthdayRange::from, _val) = _1] + >> -(date_offset + >> -lit('+')[ph::bind(&OpeningHours::MonthdayRange::from, _val) = ph::construct(-1)] + )) + ; + + date_offset = (plus_or_minus >> wday) | day_offset; + + date_from + = ((-year[_a = _1] >> ((month[_b = _1] >> -daynum[_c = _1]) | daynum[_c = _1])) + | variable_date) + [_val = ph::construct(_a, _b, _c)] + ; + + date_to + = date_from[_val = _1] + | daynum[_val = ph::construct(0, 0, _1)] + ; + + variable_date = lit("easter"); + + // Year selector + year_selector = (year_range % ',')[ph::bind(&OpeningHours::monthdays, _r1) = _1]; + + year_range + = year[ph::bind(&OpeningHours::MonthdayRange::from, _val) = ph::construct(_1)] + >> -(('-' >> year[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct(_1)] + >> -('/' >> uint_)) + | lit('+')[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct(-1)]); + + // Basic elements + plus_or_minus = lit('+')[_val = true] | lit('-')[_val = false]; + + hour = uint2_p[_pass = bind([](unsigned x) { return x <= 24; }, _1), _val = _1]; + + extended_hour = uint2_p[_pass = bind([](unsigned x) { return x <= 48; }, _1), _val = _1]; + + minute = uint2_p[_pass = bind([](unsigned x) { return x < 60; }, _1), _val = _1]; + + hour_minutes = + hour[_a = _1] >> ':' >> minute[_val = ph::construct(_a, _1)]; + + extended_hour_minutes = extended_hour[_a = _1] >> ':' >> + minute[_val = ph::construct(_a, _1)]; + + wday.add + ("Su", 0) + ("Mo", 1) + ("Tu", 2) + ("We", 3) + ("Th", 4) + ("Fr", 5) + ("Sa", 6) + ; + + daynum + = uint2_p[_pass = bind([](unsigned x) { return 01 <= x && x <= 31; }, _1), _val = _1] + >> (&~lit(':') | eoi) + ; + + weeknum = uint2_p[_pass = bind([](unsigned x) { return 01 <= x && x <= 53; }, _1), _val = _1]; + + month.add + ("Jan", 1) + ("Feb", 2) + ("Mar", 3) + ("Apr", 4) + ("May", 5) + ("Jun", 6) + ("Jul", 7) + ("Aug", 8) + ("Sep", 9) + ("Oct", 10) + ("Nov", 11) + ("Dec", 12) + ; + + year = uint4_p[_pass = bind([](unsigned x) { return x > 1900; }, _1), _val = _1]; + + comment = lit('"') >> *(~qi::char_('"')) >> lit('"'); + + // clang-format on + + BOOST_SPIRIT_DEBUG_NODES((time_domain)(rule_sequence)(any_rule_separator)( + selector_sequence)(wide_range_selectors)(small_range_selectors)(time_selector)( + timespan)(time)(extended_time)(variable_time)(weekday_selector)(weekday_sequence)( + weekday_range)(holiday_sequence)(nth_entry)(nth)(day_offset)(week_selector)(week)( + monthday_selector)(monthday_range)(date_offset)(date_from)(date_to)(variable_date)( + year_selector)(year_range)(plus_or_minus)(hour_minutes)(extended_hour_minutes)(comment)( + hour)(extended_hour)(minute)(daynum)(weeknum)(year)); + } + + qi::rule()> time_domain; + qi::rule rule_sequence; + qi::rule any_rule_separator, additional_rule_separator; + qi::rule> selector_sequence; + qi::symbols rule_modifier; + qi::rule wide_range_selectors, small_range_selectors, + time_selector, weekday_selector, year_selector, monthday_selector, week_selector; + + // Time rules + qi::rule> + timespan; + + qi::rule time, extended_time; + + qi::rule> + variable_time; + + qi::rule> hour_minutes, + extended_hour_minutes; + + qi::symbols event; + + qi::rule plus_or_minus; + + // Weekday rules + qi::rule weekday_sequence, holiday_sequence; + + qi::rule> + weekday_range; + + // Monthday rules + qi::rule monthday_range; + + qi::rule> + date_from; + + qi::rule date_to; + + // Year rules + qi::rule year_range; + + // Unused rules + qi::rule nth_entry, nth, day_offset, week, date_offset, + variable_date, comment; + + // Basic rules and parsers + qi::rule hour, extended_hour, minute, daynum, weeknum, year; + qi::symbols wday, month; + qi::uint_parser uint2_p; + qi::uint_parser uint4_p; +}; +} + +std::vector ParseOpeningHours(const std::string &str) +{ + auto it(str.begin()), end(str.end()); + const detail::opening_hours_grammar static grammar; + + std::vector result; + bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result); + + if (!ok || it != end) + return std::vector(); + + return result; +} + +bool CheckOpeningHours(const std::vector &input, const struct tm &time) +{ + bool is_open = false; + for (auto &opening_hours : input) + { + if (opening_hours.modifier == OpeningHours::is24_7) + return true; + + if (opening_hours.IsInRange(time)) + { + is_open = opening_hours.modifier == OpeningHours::open; + } + } + + return is_open; +} + +} // util +} // osrm diff --git a/src/util/timezones.cpp b/src/util/timezones.cpp new file mode 100644 index 000000000..df75d3f5b --- /dev/null +++ b/src/util/timezones.cpp @@ -0,0 +1,140 @@ +#include "util/timezones.hpp" +#include "util/exception.hpp" +#include "util/log.hpp" + +#include + +#ifdef ENABLE_SHAPEFILE +#include +#endif + +#include +#include + +// Function loads time zone shape polygons, computes a zone local time for utc_time, +// creates a lookup R-tree and returns a lambda function that maps a point +// to the corresponding local time +namespace osrm +{ +namespace updater +{ + +bool SupportsShapefiles() +{ +#ifdef ENABLE_SHAPEFILE + return true; +#else + return false; +#endif +} + +Timezoner::Timezoner(std::string tz_filename, std::time_t utc_time_now) +{ + util::Log() << "Time zone validation based on UTC time : " << utc_time_now; + // Thread safety: MT-Unsafe const:env + default_time = *gmtime(&utc_time_now); + LoadLocalTimesRTree(tz_filename, utc_time_now); +} + +void Timezoner::LoadLocalTimesRTree(const std::string &tz_shapes_filename, std::time_t utc_time) +{ + if (tz_shapes_filename.empty()) + return; +#ifdef ENABLE_SHAPEFILE + // Load time zones shapes and collect local times of utc_time + auto shphandle = SHPOpen(tz_shapes_filename.c_str(), "rb"); + auto dbfhandle = DBFOpen(tz_shapes_filename.c_str(), "rb"); + + BOOST_SCOPE_EXIT(&shphandle, &dbfhandle) + { + DBFClose(dbfhandle); + SHPClose(shphandle); + } + BOOST_SCOPE_EXIT_END + + if (!shphandle || !dbfhandle) + { + throw osrm::util::exception("failed to open " + tz_shapes_filename + ".shp or " + + tz_shapes_filename + ".dbf file"); + } + + int num_entities, shape_type; + SHPGetInfo(shphandle, &num_entities, &shape_type, NULL, NULL); + if (num_entities != DBFGetRecordCount(dbfhandle)) + { + throw osrm::util::exception("inconsistent " + tz_shapes_filename + ".shp and " + + tz_shapes_filename + ".dbf files"); + } + + const auto tzid = DBFGetFieldIndex(dbfhandle, "TZID"); + if (tzid == -1) + { + throw osrm::util::exception("did not find field called 'TZID' in the " + + tz_shapes_filename + ".dbf file"); + } + + // Lambda function that returns local time in the tzname time zone + // Thread safety: MT-Unsafe const:env + std::unordered_map local_time_memo; + auto get_local_time_in_tz = [utc_time, &local_time_memo](const char *tzname) { + auto it = local_time_memo.find(tzname); + if (it == local_time_memo.end()) + { + struct tm timeinfo; + setenv("TZ", tzname, 1); + tzset(); + localtime_r(&utc_time, &timeinfo); + it = local_time_memo.insert({tzname, timeinfo}).first; + } + + return it->second; + }; + + // Get all time zone shapes and save local times in a vector + std::vector polygons; + for (int shape = 0; shape < num_entities; ++shape) + { + auto object = SHPReadObject(shphandle, shape); + BOOST_SCOPE_EXIT(&object) { SHPDestroyObject(object); } + BOOST_SCOPE_EXIT_END + + if (object && object->nSHPType == SHPT_POLYGON) + { + // Find time zone polygon and place its bbox in into R-Tree + polygon_t polygon; + for (int vertex = 0; vertex < object->nVertices; ++vertex) + { + polygon.outer().emplace_back(object->padfX[vertex], object->padfY[vertex]); + } + + polygons.emplace_back(boost::geometry::return_envelope(polygon), + local_times.size()); + + // Get time zone name and emplace polygon and local time for the UTC input + const auto tzname = DBFReadStringAttribute(dbfhandle, shape, tzid); + local_times.emplace_back(local_time_t{polygon, get_local_time_in_tz(tzname)}); + + // std::cout << boost::geometry::dsv(boost::geometry::return_envelope(polygon)) + // << " " << tzname << " " << asctime(&local_times.back().second); + } + } + + // Create R-tree for collected shape polygons + rtree = rtree_t(polygons); +#endif +} + +struct tm Timezoner::operator()(const point_t &point) const +{ + std::vector result; + rtree.query(boost::geometry::index::intersects(point), std::back_inserter(result)); + for (const auto v : result) + { + const auto index = v.second; + if (boost::geometry::within(point, local_times[index].first)) + return local_times[index].second; + } + return default_time; +} +} +} diff --git a/test/data/tz/OGRGeoJSON.dbf b/test/data/tz/OGRGeoJSON.dbf new file mode 100644 index 000000000..0447c1deb Binary files /dev/null and b/test/data/tz/OGRGeoJSON.dbf differ diff --git a/test/data/tz/OGRGeoJSON.prj b/test/data/tz/OGRGeoJSON.prj new file mode 100644 index 000000000..a30c00a55 --- /dev/null +++ b/test/data/tz/OGRGeoJSON.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/test/data/tz/OGRGeoJSON.shp b/test/data/tz/OGRGeoJSON.shp new file mode 100644 index 000000000..f501406da Binary files /dev/null and b/test/data/tz/OGRGeoJSON.shp differ diff --git a/test/data/tz/OGRGeoJSON.shx b/test/data/tz/OGRGeoJSON.shx new file mode 100644 index 000000000..756ee549c Binary files /dev/null and b/test/data/tz/OGRGeoJSON.shx differ diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 24c2f69d9..835059018 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -102,15 +102,15 @@ target_include_directories(util-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(partition-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(customizer-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(engine-tests ${ENGINE_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) -target_link_libraries(extractor-tests ${EXTRACTOR_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) -target_link_libraries(partition-tests ${PARTITIONER_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) -target_link_libraries(customizer-tests ${CUSTOMIZER_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) +target_link_libraries(engine-tests ${ENGINE_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${MAYBE_SHAPEFILE}) +target_link_libraries(extractor-tests ${EXTRACTOR_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${MAYBE_SHAPEFILE}) +target_link_libraries(partition-tests ${PARTITIONER_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${MAYBE_SHAPEFILE}) +target_link_libraries(customizer-tests ${CUSTOMIZER_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${MAYBE_SHAPEFILE}) target_link_libraries(library-tests osrm ${ENGINE_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) target_link_libraries(library-extract-tests osrm_extract ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) target_link_libraries(library-contract-tests osrm_contract ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) -target_link_libraries(server-tests osrm ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) -target_link_libraries(util-tests ${UTIL_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) +target_link_libraries(server-tests osrm ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${MAYBE_SHAPEFILE}) +target_link_libraries(util-tests ${UTIL_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${MAYBE_SHAPEFILE}) add_custom_target(tests DEPENDS engine-tests extractor-tests partition-tests customizer-tests library-tests library-extract-tests server-tests util-tests)