Conditional turn restriction support (#3841)

* optionally include condition and via node coords in InputRestrictionContainer

* only write conditionals to disk, custom serialization for restrictions

* conditional turn lookup, reuse timezone validation from
extract-conditionals

* adapt updater to use coordinates/osm ids, remove internal to external map

* add utc time now parameter to contraction

* only compile timezone code where libshp is found, adapt test running

* slight refactor, more tests

* catch invalid via nodes in restriction parsing, set default cucumber
origin to guinée

* add another run to test mld routed paths

* cosmetic review changes

* Simplify Timezoner for windows build

* Split declaration and parsing parts for opening hours

* adjust conditional tests to run without shapefiles

* always include parse conditionals option

* Adjust travis timeout

* Added dummy TZ shapefile with test timezone polygons

* [skip ci] update changelog
This commit is contained in:
Karen Shea 2017-05-11 12:13:52 +02:00 committed by GitHub
parent 12f47708cd
commit 799a677e7a
42 changed files with 2116 additions and 1310 deletions

View File

@ -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

View File

@ -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:

View File

@ -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_OBJECTS:UTIL>)
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_OBJECTS:UTIL>)
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_OBJECTS:UTIL>)
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()

View File

@ -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'
}

View File

@ -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 |

View File

@ -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();
});

View File

@ -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;

View File

@ -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<TurnIndexBlock>::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

View File

@ -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<OSMNodeID>;
using STXXLNodeVector = stxxl::vector<ExternalMemoryNode>;
using STXXLEdgeVector = stxxl::vector<InternalExtractorEdge>;
using STXXLRestrictionsVector = stxxl::vector<InputRestrictionContainer>;
using RestrictionsVector = std::vector<InputRestrictionContainer>;
using STXXLWayIDStartEndVector = stxxl::vector<FirstAndLastSegmentOfWay>;
using STXXLNameCharData = stxxl::vector<unsigned char>;
using STXXLNameOffsets = stxxl::vector<unsigned>;
@ -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<OSMNodeID, NodeID> external_to_internal_node_id_map;
unsigned max_internal_node_id;
std::vector<TurnRestriction> unconditional_turn_restrictions;
ExtractionContainers();

View File

@ -56,6 +56,9 @@ class Extractor
private:
ExtractorConfig config;
std::vector<TurnRestriction> ParseOSMData(ScriptingEnvironment &scripting_environment,
const unsigned number_of_threads);
std::pair<std::size_t, EdgeID>
BuildEdgeExpandedGraph(ScriptingEnvironment &scripting_environment,
std::vector<util::Coordinate> &coordinates,
@ -64,7 +67,8 @@ class Extractor
std::vector<bool> &node_is_startpoint,
std::vector<EdgeWeight> &edge_based_node_weights,
util::DeallocatingVector<EdgeBasedEdge> &edge_based_edge_list,
const std::string &intersection_class_output_file);
const std::string &intersection_class_output_file,
std::vector<TurnRestriction> &turn_restrictions);
void WriteProfileProperties(const std::string &output_path,
const ProfileProperties &properties) const;
void FindComponents(unsigned max_edge_id,

View File

@ -112,6 +112,7 @@ struct ExtractorConfig
std::string turn_penalties_index_path;
bool use_metadata;
bool parse_conditionals;
};
}
}

View File

@ -1,6 +1,8 @@
#ifndef RESTRICTION_HPP
#define RESTRICTION_HPP
#include "util/coordinate.hpp"
#include "util/opening_hours.hpp"
#include "util/typedefs.hpp"
#include <limits>
@ -20,6 +22,8 @@ struct TurnRestriction
WayOrNode from;
WayOrNode to;
std::vector<util::OpeningHours> condition;
struct Bits
{ // mostly unused
Bits()

View File

@ -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<NodeID> predecessors;
for (const EdgeID current_edge_id : graph.GetAdjacentEdgeRange(node_u))

View File

@ -41,14 +41,17 @@ class ScriptingEnvironment;
class RestrictionParser
{
public:
RestrictionParser(ScriptingEnvironment &scripting_environment);
boost::optional<InputRestrictionContainer> TryParse(const osmium::Relation &relation) const;
RestrictionParser(bool use_turn_restrictions,
bool parse_conditionals,
std::vector<std::string> &restrictions);
std::vector<InputRestrictionContainer> TryParse(const osmium::Relation &relation) const;
private:
bool ShouldIgnoreRestriction(const std::string &except_tag_string) const;
std::vector<std::string> restrictions;
bool use_turn_restrictions;
bool parse_conditionals;
std::vector<std::string> restrictions;
};
}
}

View File

@ -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 <storage::Ownership Ownership>
inline void read(storage::io::FileReader &reader,
detail::SegmentDataContainerImpl<Ownership> &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 <storage::Ownership Ownership>
inline void read(storage::io::FileReader &reader,
detail::TurnDataContainerImpl<Ownership> &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<TurnRestriction> &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);
}
}
}
}
}

View File

@ -63,10 +63,9 @@ struct Turn final
: from(from), via(via), to(to)
{
}
template <typename Other>
Turn(const Other &turn)
: from(static_cast<std::uint64_t>(turn.from_id)),
via(static_cast<std::uint64_t>(turn.via_id)), to(static_cast<std::uint64_t>(turn.to_id))
Turn(const OSMNodeID &from_id, const OSMNodeID &via_id, const OSMNodeID &to_id)
: from(static_cast<std::uint64_t>(from_id)), via(static_cast<std::uint64_t>(via_id)),
to(static_cast<std::uint64_t>(to_id))
{
}
bool operator<(const Turn &rhs) const

View File

@ -5,6 +5,7 @@
#include "extractor/edge_based_edge.hpp"
#include <chrono>
#include <vector>
namespace osrm

View File

@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/filesystem/path.hpp>
#include <chrono>
#include <string>
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<std::string> segment_speed_lookup_paths;
std::vector<std::string> turn_penalty_lookup_paths;
std::string datasource_names_path;
std::string profile_properties_path;
std::string turn_restrictions_path;
std::string tz_file_path;
};
}
}

View File

@ -1,9 +1,8 @@
#ifndef OSRM_CONDITIONAL_RESTRICTIONS_HPP
#define OSRM_CONDITIONAL_RESTRICTIONS_HPP
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <vector>
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 <typename Iterator, typename Skipper = qi::blank_type>
struct conditional_restrictions_grammar
: qi::grammar<Iterator, Skipper, std::vector<ConditionalRestriction>()>
{
// 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<Iterator, Skipper, std::vector<ConditionalRestriction>()> restrictions;
qi::rule<Iterator, Skipper, ConditionalRestriction()> restriction;
qi::rule<Iterator, Skipper, std::string()> value, condition;
};
}
inline std::vector<ConditionalRestriction> ParseConditionalRestrictions(const std::string &str)
{
auto it(str.begin()), end(str.end());
const detail::conditional_restrictions_grammar<decltype(it)> static grammar;
std::vector<ConditionalRestriction> result;
bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result);
if (!ok || it != end)
return std::vector<ConditionalRestriction>();
return result;
}
std::vector<ConditionalRestriction> ParseConditionalRestrictions(const std::string &str);
} // util
} // osrm

View File

@ -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<extractor::TurnRestriction> &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

View File

@ -2,17 +2,9 @@
#define OSRM_OPENING_HOURS_HPP
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/io/ios_state.hpp>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <iterator>
#include <limits>
#include <string>
#include <vector>
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<OpeningHours> 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 <typename Iterator, typename Skipper = qi::blank_type>
struct opening_hours_grammar : qi::grammar<Iterator, Skipper, std::vector<OpeningHours>()>
{
// 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<OpeningHours::Time>(24, 0)]
| ('-' >> extended_time[_b = _1]
>> -('+' | '/' >> (minute | hour_minutes))))
)[_val = ph::construct<OpeningHours::TimeSpan>(_a, _b)]
;
time = hour_minutes | variable_time;
extended_time = extended_hour_minutes | variable_time;
variable_time
= event[_val = ph::construct<OpeningHours::Time>(_1)]
| ('(' >> event[_a = _1] >> plus_or_minus[_b = _1] >> hour_minutes[_c = _1] >> ')')
[_val = ph::construct<OpeningHours::Time>(_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<OpeningHours::WeekdayRange>(_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<OpeningHours::Monthday>(-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<OpeningHours::Monthday>(_a, _b, _c)]
;
date_to
= date_from[_val = _1]
| daynum[_val = ph::construct<OpeningHours::Monthday>(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<OpeningHours::Monthday>(_1)]
>> -(('-' >> year[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct<OpeningHours::Monthday>(_1)]
>> -('/' >> uint_))
| lit('+')[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct<OpeningHours::Monthday>(-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<OpeningHours::Time>(_a, _1)];
extended_hour_minutes = extended_hour[_a = _1] >> ':' >>
minute[_val = ph::construct<OpeningHours::Time>(_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<Iterator, Skipper, std::vector<OpeningHours>()> time_domain;
qi::rule<Iterator, Skipper, OpeningHours()> rule_sequence;
qi::rule<Iterator, Skipper, void()> any_rule_separator, additional_rule_separator;
qi::rule<Iterator, Skipper, OpeningHours(), qi::locals<OpeningHours>> selector_sequence;
qi::symbols<char const, OpeningHours::Modifier> rule_modifier;
qi::rule<Iterator, Skipper, void(OpeningHours &)> wide_range_selectors, small_range_selectors,
time_selector, weekday_selector, year_selector, monthday_selector, week_selector;
// Time rules
qi::rule<Iterator,
Skipper,
OpeningHours::TimeSpan(),
qi::locals<OpeningHours::Time, OpeningHours::Time>>
timespan;
qi::rule<Iterator, Skipper, OpeningHours::Time()> time, extended_time;
qi::rule<Iterator,
Skipper,
OpeningHours::Time(),
qi::locals<OpeningHours::Time::Event, bool, OpeningHours::Time>>
variable_time;
qi::rule<Iterator, Skipper, OpeningHours::Time(), qi::locals<unsigned>> hour_minutes,
extended_hour_minutes;
qi::symbols<char const, OpeningHours::Time::Event> event;
qi::rule<Iterator, Skipper, bool()> plus_or_minus;
// Weekday rules
qi::rule<Iterator, Skipper, void(OpeningHours &)> weekday_sequence, holiday_sequence;
qi::rule<Iterator,
Skipper,
OpeningHours::WeekdayRange(),
qi::locals<unsigned char, unsigned char>>
weekday_range;
// Monthday rules
qi::rule<Iterator, Skipper, OpeningHours::MonthdayRange()> monthday_range;
qi::rule<Iterator, Skipper, OpeningHours::Monthday(), qi::locals<unsigned, unsigned, unsigned>>
date_from;
qi::rule<Iterator, Skipper, OpeningHours::Monthday()> date_to;
// Year rules
qi::rule<Iterator, Skipper, OpeningHours::MonthdayRange()> year_range;
// Unused rules
qi::rule<Iterator, Skipper, void()> nth_entry, nth, day_offset, week, date_offset,
variable_date, comment;
// Basic rules and parsers
qi::rule<Iterator, Skipper, unsigned()> hour, extended_hour, minute, daynum, weeknum, year;
qi::symbols<char const, unsigned char> wday, month;
qi::uint_parser<unsigned, 10, 2, 2> uint2_p;
qi::uint_parser<unsigned, 10, 4, 4> uint4_p;
};
}
inline std::vector<OpeningHours> ParseOpeningHours(const std::string &str)
{
auto it(str.begin()), end(str.end());
const detail::opening_hours_grammar<decltype(it)> static grammar;
std::vector<OpeningHours> result;
bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result);
if (!ok || it != end)
return std::vector<OpeningHours>();
return result;
}
inline bool CheckOpeningHours(const std::vector<OpeningHours> &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<OpeningHours> &input, const struct tm &time);
} // util
} // osrm

View File

@ -0,0 +1,48 @@
#ifndef OSRM_TIMEZONES_HPP
#define OSRM_TIMEZONES_HPP
#include "util/log.hpp"
#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <chrono>
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<double, 2, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>>;
using polygon_t = boost::geometry::model::polygon<point_t>;
using box_t = boost::geometry::model::box<point_t>;
using rtree_t =
boost::geometry::index::rtree<std::pair<box_t, size_t>, boost::geometry::index::rstar<8>>;
using local_time_t = std::pair<polygon_t, struct tm>;
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_time_t> local_times;
};
}
}
#endif

View File

@ -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",

View File

@ -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

View File

@ -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};

View File

@ -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);

View File

@ -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<EdgeBasedNode> edge_based_node_list;
util::DeallocatingVector<EdgeBasedEdge> edge_based_edge_list;
std::vector<bool> node_is_startpoint;
std::vector<EdgeWeight> edge_based_node_weights;
std::vector<util::Coordinate> 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<ExtractorCallbacks>(
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<std::pair<std::size_t, ExtractionNode>> resulting_nodes;
tbb::concurrent_vector<std::pair<std::size_t, ExtractionWay>> resulting_ways;
tbb::concurrent_vector<boost::optional<InputRestrictionContainer>> resulting_restrictions;
// setup restriction parser
const RestrictionParser restriction_parser(scripting_environment);
// create a vector of iterators into the buffer
for (std::vector<osmium::memory::Buffer::const_iterator> 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<const osmium::Node &>(*(osm_elements[result.first])),
result.second);
}
number_of_ways += resulting_ways.size();
for (const auto &result : resulting_ways)
{
extractor_callbacks->ProcessWay(
static_cast<const osmium::Way &>(*(osm_elements[result.first])), result.second);
}
number_of_relations += resulting_restrictions.size();
for (const auto &result : resulting_restrictions)
{
extractor_callbacks->ProcessRestriction(result);
}
}
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<EdgeBasedNode> edge_based_node_list;
util::DeallocatingVector<EdgeBasedEdge> edge_based_edge_list;
std::vector<bool> node_is_startpoint;
std::vector<EdgeWeight> edge_based_node_weights;
std::vector<util::Coordinate> 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<std::uint64_t>(number_of_node_based_nodes / TIMER_SEC(expansion));
const auto edges_per_second =
static_cast<std::uint64_t>((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<std::uint64_t>(number_of_node_based_nodes / TIMER_SEC(expansion));
const auto edges_per_second =
static_cast<std::uint64_t>((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<TurnRestriction> 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<ExtractorCallbacks>(
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<std::pair<std::size_t, ExtractionNode>> resulting_nodes;
tbb::concurrent_vector<std::pair<std::size_t, ExtractionWay>> resulting_ways;
tbb::concurrent_vector<boost::optional<InputRestrictionContainer>> resulting_restrictions;
std::vector<std::string> 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<osmium::memory::Buffer::const_iterator> 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<const osmium::Node &>(*(osm_elements[result.first])), result.second);
}
number_of_ways += resulting_ways.size();
for (const auto &result : resulting_ways)
{
extractor_callbacks->ProcessWay(
static_cast<const osmium::Way &>(*(osm_elements[result.first])), result.second);
}
number_of_relations += resulting_restrictions.size();
for (const auto &result : resulting_restrictions)
{
extractor_callbacks->ProcessRestriction(result);
}
}
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<RestrictionMap> Extractor::LoadRestrictionMap()
{
storage::io::FileReader file_reader(config.restriction_file_name,
storage::io::FileReader::VerifyFingerprint);
std::vector<TurnRestriction> restriction_list;
util::loadRestrictionsFromFile(file_reader, restriction_list);
util::Log() << " - " << restriction_list.size() << " restrictions.";
return std::make_shared<RestrictionMap>(restriction_list);
}
/**
\brief Load node based graph from .osrm file
*/
@ -444,12 +436,13 @@ Extractor::BuildEdgeExpandedGraph(ScriptingEnvironment &scripting_environment,
std::vector<bool> &node_is_startpoint,
std::vector<EdgeWeight> &edge_based_node_weights,
util::DeallocatingVector<EdgeBasedEdge> &edge_based_edge_list,
const std::string &intersection_class_output_file)
const std::string &intersection_class_output_file,
std::vector<TurnRestriction> &turn_restrictions)
{
std::unordered_set<NodeID> barrier_nodes;
std::unordered_set<NodeID> traffic_lights;
auto restriction_map = LoadRestrictionMap();
auto restriction_map = std::make_shared<RestrictionMap>(turn_restrictions);
auto node_based_graph =
LoadNodeBasedGraph(barrier_nodes, traffic_lights, coordinates, osm_node_ids);

View File

@ -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 <boost/algorithm/string.hpp>
@ -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<std::string> &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<InputRestrictionContainer>
std::vector<InputRestrictionContainer>
RestrictionParser::TryParse(const osmium::Relation &relation) const
{
std::vector<InputRestrictionContainer> 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<util::OpeningHours> 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

View File

@ -498,6 +498,7 @@ void Sol2ScriptingEnvironment::ProcessElements(
[&](const tbb::blocked_range<std::size_t> &range) {
ExtractionNode result_node;
ExtractionWay result_way;
std::vector<InputRestrictionContainer> 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<const osmium::Relation &>(*entity)));
result_res.clear();
result_res =
restriction_parser.TryParse(static_cast<const osmium::Relation &>(*entity));
for (const InputRestrictionContainer &r : result_res)
{
resulting_restrictions.push_back(r);
}
break;
default:
break;

View File

@ -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 <boost/filesystem.hpp>
@ -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<std::time_t>(&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<std::string>(
&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");

View File

@ -8,6 +8,7 @@
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <util/timezones.hpp>
#include <iostream>
@ -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<std::time_t>(
&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<std::string>(
&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

View File

@ -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 <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/fusion/include/adapt_adt.hpp>
#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/program_options.hpp>
#include <boost/scope_exit.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include <osmium/handler.hpp>
#include <osmium/index/map/sparse_mem_array.hpp>
#include <osmium/io/any_input.hpp>
#include <osmium/tags/regex_filter.hpp>
#include <osmium/visitor.hpp>
#include <shapefil.h>
#include <fstream>
#include <cstdlib>
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <chrono>
#include <cstdlib>
//#include <ctime>
// 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<ConditionalRestriction> &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<osmium::object_id_type>::max();
osmium::tags::Filter<std::regex> tag_filter;
std::vector<ConditionalRestriction> &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<ConditionalRestriction> &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<osmium::unsigned_object_id_type>(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 <typename Callback> 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<osmium::unsigned_object_id_type>(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<osmium::unsigned_object_id_type, osmium::Location>;
using adjacency_type =
std::tuple<osmium::object_id_type, osmium::object_id_type, osmium::object_id_type>;
const std::vector<ConditionalRestriction> &restrictions;
std::unordered_set<osmium::object_id_type> related_vias;
std::vector<adjacency_type> via_adjacency;
index_type location_storage;
};
int RestrictionsDumpCommand(const char *executable, const std::vector<std::string> &arguments)
{
std::string osm_filename, csv_filename;
@ -444,22 +222,6 @@ int RestrictionsDumpCommand(const char *executable, const std::vector<std::strin
return EXIT_SUCCESS;
}
// Data types and functions for conditional speed limits
struct ConditionalSpeedLimit
{
osmium::object_id_type from;
osmium::object_id_type to;
std::string tag;
int value;
std::string condition;
};
struct LocatedConditionalSpeedLimit
{
osmium::Location location;
ConditionalSpeedLimit speed_limit;
};
// clang-format off
BOOST_FUSION_ADAPT_ADT(LocatedConditionalSpeedLimit,
(osmium::object_id_type, osmium::object_id_type, obj.speed_limit.from, obj.speed_limit.from = val)
@ -471,136 +233,6 @@ BOOST_FUSION_ADAPT_ADT(LocatedConditionalSpeedLimit,
(std::int32_t, std::int32_t, obj.location.lat(), obj.location.set_lat(val)))
// clang-format on
class ConditionalSpeedLimitsCollector : public osmium::handler::Handler
{
public:
ConditionalSpeedLimitsCollector()
{
tag_filter.add(true, std::regex("^maxspeed.*:conditional$"));
}
void node(const osmium::Node &node)
{
const osmium::object_id_type id = node.id();
if (related_nodes.find(id) != related_nodes.end())
{
location_storage.set(static_cast<osmium::unsigned_object_id_type>(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 <typename Callback> void process(Callback callback)
{
location_storage.sort();
for (auto &speed_limit : speed_limits)
{
const auto &location_from = location_storage.get(
static_cast<osmium::unsigned_object_id_type>(speed_limit.from));
const auto &location_to =
location_storage.get(static_cast<osmium::unsigned_object_id_type>(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::unsigned_object_id_type, osmium::Location>;
osmium::tags::Filter<std::regex> tag_filter;
std::unordered_set<osmium::object_id_type> related_nodes;
std::vector<ConditionalSpeedLimit> speed_limits;
index_type location_storage;
};
int SpeedLimitsDumpCommand(const char *executable, const std::vector<std::string> &arguments)
{
std::string osm_filename, csv_filename;
@ -646,119 +278,6 @@ int SpeedLimitsDumpCommand(const char *executable, const std::vector<std::string
return EXIT_SUCCESS;
}
// 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<double, 2, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>>;
using polygon_t = boost::geometry::model::polygon<point_t>;
using box_t = boost::geometry::model::box<point_t>;
using rtree_t =
boost::geometry::index::rtree<std::pair<box_t, size_t>, boost::geometry::index::rstar<8>>;
using local_time_t = std::pair<polygon_t, struct tm>;
// 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<std::string, struct tm> 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<rtree_t::value_type> polygons;
std::vector<local_time_t> 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<box_t>(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<box_t>(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<rtree_t::value_type> 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<std::string> &arguments)
{
std::string input_filename, output_filename;

View File

@ -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<bool>(&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

View File

@ -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 <boost/assert.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/functional/hash.hpp>
#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
@ -44,6 +48,25 @@
#include <tuple>
#include <vector>
namespace std
{
template <typename T1, typename T2, typename T3> struct hash<std::tuple<T1, T2, T3>>
{
size_t operator()(const std::tuple<T1, T2, T3> &t) const
{
return hash_val(std::get<0>(t), std::get<1>(t), std::get<2>(t));
}
};
template <typename T1, typename T2> struct hash<std::tuple<T1, T2>>
{
size_t operator()(const std::tuple<T1, T2> &t) const
{
return hash_val(std::get<0>(t), std::get<1>(t));
}
};
}
namespace osrm
{
namespace updater
@ -135,12 +158,10 @@ tbb::concurrent_vector<GeometryID>
updateSegmentData(const UpdaterConfig &config,
const extractor::ProfileProperties &profile_properties,
const SegmentLookupTable &segment_speed_lookup,
extractor::SegmentDataContainer &segment_data)
extractor::SegmentDataContainer &segment_data,
std::vector<util::Coordinate> &coordinates,
extractor::PackedOSMIDs &osm_node_ids)
{
std::vector<util::Coordinate> 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<std::size_t>;
@ -379,26 +400,33 @@ updateTurnPenalties(const UpdaterConfig &config,
const extractor::ProfileProperties &profile_properties,
const TurnLookupTable &turn_penalty_lookup,
std::vector<TurnPenalty> &turn_weight_penalties,
std::vector<TurnPenalty> &turn_duration_penalties)
std::vector<TurnPenalty> &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<const extractor::lookup::TurnIndexBlock *>(
turn_penalties_index_region.get_address());
turn_index_region.get_address());
BOOST_ASSERT(is_aligned<extractor::lookup::TurnIndexBlock>(turn_index_blocks));
// Get the turn penalty and update to the new value if required
std::vector<std::uint64_t> 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<TurnPenalty>(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<util::Coordinate> &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<double>(toFloating(coordinates[turn.via.node].lon));
const auto lat = static_cast<double>(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 [:<transportation mode>][:<direction>]
// 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<std::uint64_t>
updateConditionalTurns(const UpdaterConfig &config,
std::vector<TurnPenalty> &turn_weight_penalties,
const std::vector<extractor::TurnRestriction> &conditional_turns,
std::vector<util::Coordinate> &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<const extractor::lookup::TurnIndexBlock *>(
turn_index_region.get_address());
BOOST_ASSERT(is_aligned<extractor::lookup::TurnIndexBlock>(turn_index_blocks));
std::vector<std::uint64_t> updated_turns;
if (conditional_turns.size() == 0)
return updated_turns;
// TODO make this into a function
LookupTable<std::tuple<NodeID, NodeID>, NodeID> is_only_lookup;
std::unordered_set<std::tuple<NodeID, NodeID, NodeID>,
std::hash<std::tuple<NodeID, NodeID, NodeID>>>
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<extractor::EdgeBasedEdge> edge_based_edge_list;
std::vector<EdgeWeight> node_weights;
std::vector<extractor::EdgeBasedEdge> 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<extractor::EdgeBasedEdge> &e
TIMER_START(load_edges);
EdgeID max_edge_id = 0;
std::vector<util::Coordinate> node_coordinates;
extractor::PackedOSMIDs osm_node_ids;
{
storage::io::FileReader reader(config.edge_based_graph_path,
@ -446,12 +580,16 @@ Updater::LoadAndUpdateEdgeExpandedGraph(std::vector<extractor::EdgeBasedEdge> &e
edge_based_edge_list.resize(num_edges);
max_edge_id = reader.ReadOne<EdgeID>();
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<extractor::EdgeBasedEdge> &e
extractor::ProfileProperties profile_properties;
std::vector<TurnPenalty> turn_weight_penalties;
std::vector<TurnPenalty> 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<extractor::EdgeBasedEdge> &e
load_profile_properties);
}
std::vector<extractor::TurnRestriction> 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<GeometryID> 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.

View File

@ -0,0 +1,96 @@
#include "util/conditional_restrictions.hpp"
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
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 <typename Iterator, typename Skipper = qi::blank_type>
struct conditional_restrictions_grammar
: qi::grammar<Iterator, Skipper, std::vector<ConditionalRestriction>()>
{
// 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<Iterator, Skipper, std::vector<ConditionalRestriction>()> restrictions;
qi::rule<Iterator, Skipper, ConditionalRestriction()> restriction;
qi::rule<Iterator, Skipper, std::string()> value, condition;
};
}
std::vector<ConditionalRestriction> ParseConditionalRestrictions(const std::string &str)
{
auto it(str.begin()), end(str.end());
const detail::conditional_restrictions_grammar<decltype(it)> static grammar;
std::vector<ConditionalRestriction> result;
bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result);
if (!ok || it != end)
return std::vector<ConditionalRestriction>();
return result;
}
} // util
} // osrm

435
src/util/opening_hours.cpp Normal file
View File

@ -0,0 +1,435 @@
#include "util/opening_hours.hpp"
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/io/ios_state.hpp>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <iterator>
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 <typename Iterator, typename Skipper = qi::blank_type>
struct opening_hours_grammar : qi::grammar<Iterator, Skipper, std::vector<OpeningHours>()>
{
// 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<OpeningHours::Time>(24, 0)]
| ('-' >> extended_time[_b = _1]
>> -('+' | '/' >> (minute | hour_minutes))))
)[_val = ph::construct<OpeningHours::TimeSpan>(_a, _b)]
;
time = hour_minutes | variable_time;
extended_time = extended_hour_minutes | variable_time;
variable_time
= event[_val = ph::construct<OpeningHours::Time>(_1)]
| ('(' >> event[_a = _1] >> plus_or_minus[_b = _1] >> hour_minutes[_c = _1] >> ')')
[_val = ph::construct<OpeningHours::Time>(_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<OpeningHours::WeekdayRange>(_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<OpeningHours::Monthday>(-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<OpeningHours::Monthday>(_a, _b, _c)]
;
date_to
= date_from[_val = _1]
| daynum[_val = ph::construct<OpeningHours::Monthday>(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<OpeningHours::Monthday>(_1)]
>> -(('-' >> year[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct<OpeningHours::Monthday>(_1)]
>> -('/' >> uint_))
| lit('+')[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct<OpeningHours::Monthday>(-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<OpeningHours::Time>(_a, _1)];
extended_hour_minutes = extended_hour[_a = _1] >> ':' >>
minute[_val = ph::construct<OpeningHours::Time>(_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<Iterator, Skipper, std::vector<OpeningHours>()> time_domain;
qi::rule<Iterator, Skipper, OpeningHours()> rule_sequence;
qi::rule<Iterator, Skipper, void()> any_rule_separator, additional_rule_separator;
qi::rule<Iterator, Skipper, OpeningHours(), qi::locals<OpeningHours>> selector_sequence;
qi::symbols<char const, OpeningHours::Modifier> rule_modifier;
qi::rule<Iterator, Skipper, void(OpeningHours &)> wide_range_selectors, small_range_selectors,
time_selector, weekday_selector, year_selector, monthday_selector, week_selector;
// Time rules
qi::rule<Iterator,
Skipper,
OpeningHours::TimeSpan(),
qi::locals<OpeningHours::Time, OpeningHours::Time>>
timespan;
qi::rule<Iterator, Skipper, OpeningHours::Time()> time, extended_time;
qi::rule<Iterator,
Skipper,
OpeningHours::Time(),
qi::locals<OpeningHours::Time::Event, bool, OpeningHours::Time>>
variable_time;
qi::rule<Iterator, Skipper, OpeningHours::Time(), qi::locals<unsigned>> hour_minutes,
extended_hour_minutes;
qi::symbols<char const, OpeningHours::Time::Event> event;
qi::rule<Iterator, Skipper, bool()> plus_or_minus;
// Weekday rules
qi::rule<Iterator, Skipper, void(OpeningHours &)> weekday_sequence, holiday_sequence;
qi::rule<Iterator,
Skipper,
OpeningHours::WeekdayRange(),
qi::locals<unsigned char, unsigned char>>
weekday_range;
// Monthday rules
qi::rule<Iterator, Skipper, OpeningHours::MonthdayRange()> monthday_range;
qi::rule<Iterator, Skipper, OpeningHours::Monthday(), qi::locals<unsigned, unsigned, unsigned>>
date_from;
qi::rule<Iterator, Skipper, OpeningHours::Monthday()> date_to;
// Year rules
qi::rule<Iterator, Skipper, OpeningHours::MonthdayRange()> year_range;
// Unused rules
qi::rule<Iterator, Skipper, void()> nth_entry, nth, day_offset, week, date_offset,
variable_date, comment;
// Basic rules and parsers
qi::rule<Iterator, Skipper, unsigned()> hour, extended_hour, minute, daynum, weeknum, year;
qi::symbols<char const, unsigned char> wday, month;
qi::uint_parser<unsigned, 10, 2, 2> uint2_p;
qi::uint_parser<unsigned, 10, 4, 4> uint4_p;
};
}
std::vector<OpeningHours> ParseOpeningHours(const std::string &str)
{
auto it(str.begin()), end(str.end());
const detail::opening_hours_grammar<decltype(it)> static grammar;
std::vector<OpeningHours> result;
bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result);
if (!ok || it != end)
return std::vector<OpeningHours>();
return result;
}
bool CheckOpeningHours(const std::vector<OpeningHours> &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

140
src/util/timezones.cpp Normal file
View File

@ -0,0 +1,140 @@
#include "util/timezones.hpp"
#include "util/exception.hpp"
#include "util/log.hpp"
#include <boost/scope_exit.hpp>
#ifdef ENABLE_SHAPEFILE
#include <shapefil.h>
#endif
#include <string>
#include <unordered_map>
// 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<std::string, struct tm> 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<rtree_t::value_type> 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<box_t>(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<box_t>(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<rtree_t::value_type> 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;
}
}
}

BIN
test/data/tz/OGRGeoJSON.dbf Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

BIN
test/data/tz/OGRGeoJSON.shp Normal file

Binary file not shown.

BIN
test/data/tz/OGRGeoJSON.shx Normal file

Binary file not shown.

View File

@ -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)