parent
84b35f47a1
commit
eeea5b0e81
6
.gitignore
vendored
6
.gitignore
vendored
@ -70,4 +70,8 @@ win/*.suo
|
||||
win/Debug/
|
||||
win/Release/
|
||||
win/bin/
|
||||
win/bin-debug/
|
||||
win/bin-debug/
|
||||
/osrm-extract
|
||||
/osrm-routed
|
||||
/osrm-prepare
|
||||
/nohup.out
|
||||
|
6
Gemfile
Normal file
6
Gemfile
Normal file
@ -0,0 +1,6 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "cucumber"
|
||||
gem "rake"
|
||||
gem "osmlib-base"
|
||||
gem "sys-proctable"
|
27
Gemfile.lock
Normal file
27
Gemfile.lock
Normal file
@ -0,0 +1,27 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
builder (3.0.0)
|
||||
cucumber (1.1.4)
|
||||
builder (>= 2.1.2)
|
||||
diff-lcs (>= 1.1.2)
|
||||
gherkin (~> 2.7.1)
|
||||
json (>= 1.4.6)
|
||||
term-ansicolor (>= 1.0.6)
|
||||
diff-lcs (1.1.3)
|
||||
gherkin (2.7.6)
|
||||
json (>= 1.4.6)
|
||||
json (1.6.5)
|
||||
osmlib-base (0.1.4)
|
||||
rake (0.9.2.2)
|
||||
sys-proctable (0.9.1)
|
||||
term-ansicolor (1.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cucumber
|
||||
osmlib-base
|
||||
rake
|
||||
sys-proctable
|
181
Rakefile
Normal file
181
Rakefile
Normal file
@ -0,0 +1,181 @@
|
||||
require 'OSM/StreamParser'
|
||||
require 'socket'
|
||||
require 'digest/sha1'
|
||||
require 'cucumber/rake/task'
|
||||
require 'sys/proctable'
|
||||
|
||||
SANDBOX = 'sandbox'
|
||||
DATA_FOLDER = 'osm_data'
|
||||
|
||||
Cucumber::Rake::Task.new do |t|
|
||||
t.cucumber_opts = %w{--format pretty}
|
||||
end
|
||||
|
||||
areas = {
|
||||
:kbh => { :country => 'denmark', :bbox => 'top=55.6972 left=12.5222 right=12.624 bottom=55.6376' },
|
||||
:frd => { :country => 'denmark', :bbox => 'top=55.7007 left=12.4765 bottom=55.6576 right=12.5698' },
|
||||
:regh => { :country => 'denmark', :bbox => 'top=56.164 left=11.792 bottom=55.403 right=12.731' },
|
||||
:dk => { :country => 'denmark', :bbox => nil },
|
||||
:skaane => { :counry => 'sweden', :bbox => 'top=56.55 left=12.4 bottom=55.3 right=14.6' }
|
||||
}
|
||||
|
||||
|
||||
|
||||
osm_data_area_name = ARGV[1] ? ARGV[1].to_s.to_sym : :kbh
|
||||
raise "Unknown data area." unless areas[osm_data_area_name]
|
||||
osm_data_country = areas[osm_data_area_name][:country]
|
||||
osm_data_area_bbox = areas[osm_data_area_name][:bbox]
|
||||
|
||||
|
||||
task osm_data_area_name.to_sym {} #define empty task to prevent rake from whining. will break if area has same name as a task
|
||||
|
||||
|
||||
def each_process name, &block
|
||||
Sys::ProcTable.ps do |process|
|
||||
if process.comm.strip == name.strip
|
||||
yield process.pid.to_i, process.state.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def up?
|
||||
find_pid('osrm-routed') != nil
|
||||
end
|
||||
|
||||
def find_pid name
|
||||
each_process(name) { |pid,state| return pid.to_i }
|
||||
return nil
|
||||
end
|
||||
|
||||
def wait_for_shutdown name
|
||||
timeout = 10
|
||||
(timeout*10).times do
|
||||
return if find_pid(name) == nil
|
||||
sleep 0.1
|
||||
end
|
||||
raise "*** Could not terminate #{name}."
|
||||
end
|
||||
|
||||
def write_server_ini osm_file
|
||||
s=<<-EOF
|
||||
Threads = 1
|
||||
IP = 0.0.0.0
|
||||
Port = 5000
|
||||
|
||||
hsgrData=#{DATA_FOLDER}/#{osm_file}.osrm.hsgr
|
||||
nodesData=#{DATA_FOLDER}/#{osm_file}.osrm.nodes
|
||||
ramIndex=#{DATA_FOLDER}/#{osm_file}.osrm.ramIndex
|
||||
fileIndex=#{DATA_FOLDER}/#{osm_file}.osrm.fileIndex
|
||||
namesData=#{DATA_FOLDER}/#{osm_file}.osrm.names
|
||||
EOF
|
||||
File.open( 'server.ini', 'w') {|f| f.write( s ) }
|
||||
end
|
||||
|
||||
|
||||
desc "Rebuild and run tests."
|
||||
task :default => [:build, :cucumber]
|
||||
|
||||
desc "Build using SConsstruct."
|
||||
task :build do
|
||||
system "scons"
|
||||
end
|
||||
|
||||
desc "Setup config files."
|
||||
task :setup do
|
||||
Dir.mkdir "#{SANDBOX}/#{DATA_FOLDER}" unless File.exist? "#{SANDBOX}/#{DATA_FOLDER}"
|
||||
['server.ini','speedprofile.ini','extractor.ini','contractor.ini'].each do |file|
|
||||
unless File.exist? "#{SANDBOX}/#{file}"
|
||||
puts "Copying #{file} template to sandbox/#{file}"
|
||||
FileUtils.cp file, "#{SANDBOX}/#{file}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "Download OSM data."
|
||||
task :download => :setup do
|
||||
puts "Downloading..."
|
||||
raise "Error while downloading data." unless system "curl http://download.geofabrik.de/osm/europe/#{osm_data_country}.osm.pbf -o #{SANDBOX}/#{DATA_FOLDER}/#{osm_data_country}.osm.pbf"
|
||||
if osm_data_area_bbox
|
||||
puts "Cropping and converting to protobuffer..."
|
||||
raise "Error while cropping data." unless system "osmosis --read-pbf file=#{SANDBOX}/#{DATA_FOLDER}/#{osm_data_country}.osm.pbf --bounding-box #{osm_data_area_bbox} --write-pbf file=#{SANDBOX}/#{DATA_FOLDER}/#{osm_data_area_name}.osm.pbf omitmetadata=true"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Crop OSM data"
|
||||
task :crop do
|
||||
if osm_data_area_bbox
|
||||
raise "Error while cropping data." unless system "osmosis --read-pbf file=#{SANDBOX}/#{DATA_FOLDER}/#{osm_data_country}.osm.pbf --bounding-box #{osm_data_area_bbox} --write-pbf file=#{SANDBOX}/#{DATA_FOLDER}/#{osm_data_area_name}.osm.pbf omitmetadata=true"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Reprocess OSM data."
|
||||
task :process => :setup do
|
||||
Dir.chdir SANDBOX do
|
||||
raise "Error while extracting data." unless system "../osrm-extract #{DATA_FOLDER}/#{osm_data_area_name}.osm.pbf"
|
||||
puts
|
||||
raise "Error while preparing data." unless system "../osrm-prepare #{DATA_FOLDER}/#{osm_data_area_name}.osrm #{DATA_FOLDER}/#{osm_data_area_name}.osrm.restrictions"
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
desc "Delete preprocessing files."
|
||||
task :clean do
|
||||
File.delete *Dir.glob("#{SANDBOX}/#{DATA_FOLDER}/*.osrm")
|
||||
File.delete *Dir.glob("#{SANDBOX}/#{DATA_FOLDER}/*.osrm.*")
|
||||
end
|
||||
|
||||
desc "Run all cucumber test"
|
||||
task :test do
|
||||
system "cucumber"
|
||||
puts
|
||||
end
|
||||
|
||||
desc "Run the routing server in the terminal. Press Ctrl-C to stop."
|
||||
task :run => :setup do
|
||||
Dir.chdir SANDBOX do
|
||||
write_server_ini osm_data_area_name
|
||||
system "../osrm-routed"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Launch the routing server in the background. Use rake:down to stop it."
|
||||
task :up => :setup do
|
||||
Dir.chdir SANDBOX do
|
||||
abort("Already up.") if up?
|
||||
write_server_ini osm_data_area_name
|
||||
pipe = IO.popen('../osrm-routed 1>>osrm-routed.log 2>>osrm-routed.log')
|
||||
timeout = 5
|
||||
(timeout*10).times do
|
||||
begin
|
||||
socket = TCPSocket.new('localhost', 5000)
|
||||
socket.puts 'ping'
|
||||
rescue Errno::ECONNREFUSED
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "Stop the routing server."
|
||||
task :down do
|
||||
pid = find_pid 'osrm-routed'
|
||||
abort("Already down.") unless pid
|
||||
Process.kill 'TERM', pid
|
||||
end
|
||||
|
||||
desc "Kill all osrm-extract, osrm-prepare and osrm-routed processes."
|
||||
task :kill do
|
||||
each_process('osrm-routed') { |pid,state| Process.kill 'KILL', pid }
|
||||
each_process('osrm-prepare') { |pid,state| Process.kill 'KILL', pid }
|
||||
each_process('osrm-extract') { |pid,state| Process.kill 'KILL', pid }
|
||||
wait_for_shutdown 'osrm-routed'
|
||||
wait_for_shutdown 'osrm-prepare'
|
||||
wait_for_shutdown 'osrm-extract'
|
||||
end
|
||||
|
||||
desc "Get PIDs of all osrm-extract, osrm-prepare and osrm-routed processes."
|
||||
task :pid do
|
||||
each_process 'osrm-routed' do |pid,state|
|
||||
puts "#{pid}\t#{state}"
|
||||
end
|
||||
end
|
55
features/access.feature
Normal file
55
features/access.feature
Normal file
@ -0,0 +1,55 @@
|
||||
@routing @access
|
||||
Feature: Oneway streets
|
||||
Basic accessability of various way types.
|
||||
|
||||
Scenario: Basic access for cars
|
||||
Given the speedprofile "car"
|
||||
Then routability should be
|
||||
| highway | forw |
|
||||
| motorway | x |
|
||||
| motorway_link | x |
|
||||
| trunk | x |
|
||||
| trunk_link | x |
|
||||
| primary | x |
|
||||
| secondary | x |
|
||||
| tertiary | x |
|
||||
| residential | x |
|
||||
| service | x |
|
||||
| unclassified | x |
|
||||
| living_street | x |
|
||||
| road | x |
|
||||
| track | |
|
||||
| path | |
|
||||
| footway | |
|
||||
| pedestrian | |
|
||||
| steps | |
|
||||
| pier | |
|
||||
| cycleway | |
|
||||
| bridleway | |
|
||||
|
||||
Scenario: Basic access for bicycles
|
||||
Bikes are allowed on footways etc because you can pull your bike at a lower speed.
|
||||
Given the speedprofile "bicycle"
|
||||
Then routability should be
|
||||
| highway | forw |
|
||||
| motorway | |
|
||||
| motorway_link | |
|
||||
| trunk | |
|
||||
| trunk_link | |
|
||||
| primary | x |
|
||||
| secondary | x |
|
||||
| tertiary | x |
|
||||
| residential | x |
|
||||
| service | x |
|
||||
| unclassified | x |
|
||||
| living_street | x |
|
||||
| road | x |
|
||||
| track | x |
|
||||
| path | x |
|
||||
| footway | x |
|
||||
| pedestrian | x |
|
||||
| steps | x |
|
||||
| pier | x |
|
||||
| cycleway | x |
|
||||
| bridleway | |
|
||||
|
55
features/bad.feature
Normal file
55
features/bad.feature
Normal file
@ -0,0 +1,55 @@
|
||||
@routing @bad
|
||||
Feature: Handle bad data in a graceful manner
|
||||
|
||||
Scenario: Empty dataset
|
||||
Given the nodes
|
||||
| a | b |
|
||||
|
||||
Given the ways
|
||||
| nodes |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | b | |
|
||||
|
||||
Scenario: Start/end point at the same location
|
||||
Given the nodes
|
||||
| a | b |
|
||||
| 1 | 2 |
|
||||
|
||||
Given the ways
|
||||
| nodes |
|
||||
| ab |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | a | |
|
||||
| b | b | |
|
||||
| 1 | 1 | |
|
||||
| 2 | 2 | |
|
||||
|
||||
Scenario: Start/end point far outside data area
|
||||
Given the nodes
|
||||
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | 1 |
|
||||
| a | b | | | | | | | | | | | | | | | | | | | | | | | | | | | 2 |
|
||||
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | 3 |
|
||||
|
||||
Given the ways
|
||||
| nodes |
|
||||
| ab |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| 1 | a | ab |
|
||||
| 2 | a | ab |
|
||||
| 3 | a | ab |
|
||||
| 1 | b | |
|
||||
| 2 | b | |
|
||||
| 3 | b | |
|
||||
| 1 | 2 | |
|
||||
| 1 | 3 | |
|
||||
| 2 | 1 | |
|
||||
| 2 | 3 | |
|
||||
| 3 | 1 | |
|
||||
| 3 | 2 | |
|
||||
|
136
features/basic.feature
Normal file
136
features/basic.feature
Normal file
@ -0,0 +1,136 @@
|
||||
@routing @basic
|
||||
Feature: Basic Routing
|
||||
|
||||
@smallest
|
||||
Scenario: A single way with two nodes
|
||||
Given the nodes
|
||||
| a | b |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| ab |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | b | ab |
|
||||
| b | a | ab |
|
||||
|
||||
Scenario: Routing in between two nodes of way
|
||||
Given the nodes
|
||||
| a | b | 1 | 2 | c | d |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| abcd |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| 1 | 2 | abcd |
|
||||
| 2 | 1 | abcd |
|
||||
|
||||
Scenario: Routing between the middle nodes of way
|
||||
Given the nodes
|
||||
| a | b | c | d | e | f |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| abcdef |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| b | c | abcdef |
|
||||
| b | d | abcdef |
|
||||
| b | e | abcdef |
|
||||
| c | b | abcdef |
|
||||
| c | d | abcdef |
|
||||
| c | e | abcdef |
|
||||
| d | b | abcdef |
|
||||
| d | c | abcdef |
|
||||
| d | e | abcdef |
|
||||
| e | b | abcdef |
|
||||
| e | c | abcdef |
|
||||
| e | d | abcdef |
|
||||
|
||||
Scenario: Two ways connected in a straight line
|
||||
Given the nodes
|
||||
| a | b | c |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| ab |
|
||||
| bc |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | c | ab,bc |
|
||||
| c | a | bc,ab |
|
||||
| a | b | ab |
|
||||
| b | a | ab |
|
||||
| b | c | bc |
|
||||
| c | b | bc |
|
||||
|
||||
Scenario: 2 unconnected parallel ways
|
||||
Given the nodes
|
||||
| a | b |
|
||||
| c | d |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| ab |
|
||||
| cd |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | b | ab |
|
||||
| b | a | ab |
|
||||
| c | d | cd |
|
||||
| d | c | cd |
|
||||
| a | c | |
|
||||
| c | a | |
|
||||
| b | d | |
|
||||
| d | b | |
|
||||
| a | d | |
|
||||
| d | a | |
|
||||
|
||||
Scenario: 3 ways connected in a triangle
|
||||
Given the nodes
|
||||
| a | | b |
|
||||
| | | |
|
||||
| | c | |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| ab |
|
||||
| bc |
|
||||
| ca |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | b | ab |
|
||||
| a | c | ca |
|
||||
| b | c | bc |
|
||||
| b | a | ab |
|
||||
| c | a | ca |
|
||||
| c | b | bc |
|
||||
|
||||
Scenario: To ways connected at a 45 degree angle
|
||||
Given the nodes
|
||||
| a | | |
|
||||
| b | | |
|
||||
| c | d | e |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| abc |
|
||||
| cde |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| b | d | abc,cde |
|
||||
| a | e | abc,cde |
|
||||
| a | c | abc |
|
||||
| c | a | abc |
|
||||
| c | e | cde |
|
||||
| e | c | cde |
|
||||
|
||||
|
27
features/ferry.feature
Normal file
27
features/ferry.feature
Normal file
@ -0,0 +1,27 @@
|
||||
@routing @ferry
|
||||
Feature: Handle ferry routes
|
||||
|
||||
Scenario: Use a ferry route
|
||||
Given the nodes
|
||||
| a | b | c | | |
|
||||
| | | d | | |
|
||||
| | | e | f | g |
|
||||
|
||||
And the ways
|
||||
| nodes | highway | route | bicycle |
|
||||
| abc | primary | | |
|
||||
| cde | | ferry | yes |
|
||||
| efg | primary | | |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | g | abc,cde,efg |
|
||||
| b | f | abc,cde,efg |
|
||||
| e | c | cde |
|
||||
| e | b | cde,abc |
|
||||
| e | a | cde,abc |
|
||||
| c | e | cde |
|
||||
| c | f | cde,efg |
|
||||
| c | g | cde,efg |
|
||||
|
||||
|
166
features/oneway.feature
Normal file
166
features/oneway.feature
Normal file
@ -0,0 +1,166 @@
|
||||
@routing @oneway
|
||||
Feature: Oneway streets
|
||||
Handle oneways streets, as defined at http://wiki.openstreetmap.org/wiki/OSM_tags_for_routing
|
||||
|
||||
Scenario: Simple oneway
|
||||
Given the speedprofile "car"
|
||||
Then routability should be
|
||||
| highway | oneway | forw | backw |
|
||||
| primary | yes | x | |
|
||||
|
||||
Scenario: Simple reverse oneway
|
||||
Given the speedprofile "car"
|
||||
Then routability should be
|
||||
| highway | oneway | forw | backw |
|
||||
| primary | -1 | | x |
|
||||
|
||||
Scenario: Around the Block
|
||||
Given the nodes
|
||||
| a | b |
|
||||
| d | c |
|
||||
|
||||
And the ways
|
||||
| nodes | oneway |
|
||||
| ab | yes |
|
||||
| bc | |
|
||||
| cd | |
|
||||
| da | |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | b | ab |
|
||||
| b | a | bc,cd,da |
|
||||
|
||||
Scenario: Avoid oneway traps
|
||||
Given the nodes
|
||||
| | x | |
|
||||
| a | b | c |
|
||||
| | y | |
|
||||
|
||||
And the ways
|
||||
| nodes | oneway |
|
||||
| abc | |
|
||||
| bx | yes |
|
||||
| yb | -1 |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| b | x | |
|
||||
| b | y | |
|
||||
|
||||
Scenario: Handle various oneway tag values
|
||||
Given the speedprofile "bicycle"
|
||||
Then routability should be
|
||||
| highway | oneway | forw | backw |
|
||||
| primary | | x | x |
|
||||
| primary | nonsense | x | x |
|
||||
| primary | no | x | x |
|
||||
| primary | false | x | x |
|
||||
| primary | 0 | x | x |
|
||||
| primary | yes | x | |
|
||||
| primary | true | x | |
|
||||
| primary | 1 | x | |
|
||||
| primary | -1 | | x |
|
||||
|
||||
Scenario: Implied oneways
|
||||
Given the speedprofile "car"
|
||||
Then routability should be
|
||||
| highway | junction | forw | backw |
|
||||
| motorway | | x | |
|
||||
| motorway_link | | x | |
|
||||
| trunk | | x | x |
|
||||
| trunk_link | | x | x |
|
||||
| primary | | x | x |
|
||||
| primary_link | | x | x |
|
||||
| secondary | | x | x |
|
||||
| secondary_link | | x | x |
|
||||
| tertiary | | x | x |
|
||||
| tertiary_link | | x | x |
|
||||
| residential | | x | x |
|
||||
| primary | roundabout | x | |
|
||||
| secondary | roundabout | x | |
|
||||
| tertiary | roundabout | x | |
|
||||
| residential | roundabout | x | |
|
||||
|
||||
Scenario: Overriding implied oneways
|
||||
Given the speedprofile "car"
|
||||
Then routability should be
|
||||
| highway | junction | oneway | forw | backw |
|
||||
| motorway_link | | no | x | x |
|
||||
| trunk_link | | no | x | x |
|
||||
| primary | roundabout | no | x | x |
|
||||
| motorway_link | | yes | x | |
|
||||
| trunk_link | | yes | x | |
|
||||
| primary | roundabout | yes | x | |
|
||||
| motorway_link | | -1 | | x |
|
||||
| trunk_link | | -1 | | x |
|
||||
| primary | roundabout | -1 | | x |
|
||||
|
||||
Scenario: Disabling oneways in speedprofile
|
||||
Given the speedprofile "car"
|
||||
And the speedprofile settings
|
||||
| obeyOneways | no |
|
||||
Then routability should be
|
||||
| highway | junction | oneway | forw | backw |
|
||||
| primary | | yes | x | x |
|
||||
| primary | | true | x | x |
|
||||
| primary | | 1 | x | x |
|
||||
| primary | | -1 | x | x |
|
||||
| motorway_link | | | x | x |
|
||||
| trunk_link | | | x | x |
|
||||
| primary | roundabout | | x | x |
|
||||
|
||||
@bicycle
|
||||
Scenario: Oneway:bicycle should override normal oneways tags
|
||||
Given the speedprofile "bicycle"
|
||||
Then routability should be
|
||||
| highway | junction | oneway | oneway:bicycle | forw | backw |
|
||||
| primary | | | yes | x | |
|
||||
| primary | | yes | yes | x | |
|
||||
| primary | | no | yes | x | |
|
||||
| primary | | -1 | yes | x | |
|
||||
| primary | roundabout | | yes | x | |
|
||||
| primary | | | no | x | x |
|
||||
| primary | | yes | no | x | x |
|
||||
| primary | | no | no | x | x |
|
||||
| primary | | -1 | no | x | x |
|
||||
| primary | roundabout | | no | x | x |
|
||||
| primary | | | -1 | | x |
|
||||
| primary | | yes | -1 | | x |
|
||||
| primary | | no | -1 | | x |
|
||||
| primary | | -1 | -1 | | x |
|
||||
| primary | roundabout | | -1 | | x |
|
||||
|
||||
@bicycle
|
||||
Scenario: Bicycles and contra flow
|
||||
Given the speedprofile "bicycle"
|
||||
Then routability should be
|
||||
| highway | oneway | cycleway | forw | backw |
|
||||
| primary | yes | opposite | x | x |
|
||||
| primary | yes | opposite_track | x | x |
|
||||
| primary | yes | opposite_lane | x | x |
|
||||
| primary | -1 | opposite | x | x |
|
||||
| primary | -1 | opposite_track | x | x |
|
||||
| primary | -1 | opposite_lane | x | x |
|
||||
| primary | no | opposite | x | x |
|
||||
| primary | no | opposite_track | x | x |
|
||||
| primary | no | opposite_lane | x | x |
|
||||
|
||||
@bicycle
|
||||
Scenario: Cars should not be affected by bicycle tags
|
||||
Given the speedprofile "car"
|
||||
Then routability should be
|
||||
| highway | junction | oneway | oneway:bicycle | forw | backw |
|
||||
| primary | | yes | yes | x | |
|
||||
| primary | | yes | no | x | |
|
||||
| primary | | yes | -1 | x | |
|
||||
| primary | | no | yes | x | x |
|
||||
| primary | | no | no | x | x |
|
||||
| primary | | no | -1 | x | x |
|
||||
| primary | | -1 | yes | | x |
|
||||
| primary | | -1 | no | | x |
|
||||
| primary | | -1 | -1 | | x |
|
||||
| primary | roundabout | | yes | x | |
|
||||
| primary | roundabout | | no | x | |
|
||||
| primary | roundabout | | -1 | x | |
|
||||
|
95
features/restrictions.feature
Normal file
95
features/restrictions.feature
Normal file
@ -0,0 +1,95 @@
|
||||
@routing @restrictions
|
||||
Feature: Turn restrictions
|
||||
Handle turn restrictions as defined by http://wiki.openstreetmap.org/wiki/Relation:restriction
|
||||
How this plays with u-turns can be tricky.
|
||||
|
||||
Scenario: No left turn
|
||||
Given the nodes
|
||||
| | t | |
|
||||
| a | j | b |
|
||||
| | s | |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| bj |
|
||||
| aj |
|
||||
| sj |
|
||||
| tj |
|
||||
|
||||
And the relations
|
||||
| from | to | via | restriction |
|
||||
| sj | aj | j | no_left_turn |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| s | a | |
|
||||
| s | b | sj,jb |
|
||||
| s | t | sj,tj |
|
||||
| a | b | aj,bj |
|
||||
| a | a | aj,sj |
|
||||
| a | t | aj,tj |
|
||||
| b | b | jb,aj |
|
||||
| b | s | bj,sj |
|
||||
| b | t | bj,tj |
|
||||
|
||||
Scenario: No left turn, go counter-clockwise around the block instead
|
||||
Given the nodes
|
||||
| x | t | |
|
||||
| a | j | b |
|
||||
| | s | |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| bj |
|
||||
| aj |
|
||||
| sj |
|
||||
| tj |
|
||||
| axt |
|
||||
|
||||
And the relations
|
||||
| from | to | via | restriction |
|
||||
| sj | aj | j | no_left_turn |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| s | a | sj,tj,axt |
|
||||
| s | b | sj,jb |
|
||||
| s | t | sj,tj |
|
||||
| a | b | aj,bj |
|
||||
| a | a | aj,sj |
|
||||
| a | t | aj,tj |
|
||||
| b | b | jb,aj |
|
||||
| b | s | bj,sj |
|
||||
| b | t | bj,tj |
|
||||
|
||||
Scenario: No left turn, go clockwise around the block instead
|
||||
Given the nodes
|
||||
| | | t | |
|
||||
| z | a | j | b |
|
||||
| x | | s | |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| bj |
|
||||
| aj |
|
||||
| sj |
|
||||
| tj |
|
||||
| sxza |
|
||||
|
||||
And the relations
|
||||
| from | to | via | restriction |
|
||||
| sj | aj | j | no_left_turn |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| s | a | sxza |
|
||||
| s | b | sj,jb |
|
||||
| s | t | sj,tj |
|
||||
| a | b | aj,bj |
|
||||
| a | a | aj,sj |
|
||||
| a | t | aj,tj |
|
||||
| b | b | jb,aj |
|
||||
| b | s | bj,sj |
|
||||
| b | t | bj,tj |
|
||||
|
||||
|
86
features/snap.feature
Normal file
86
features/snap.feature
Normal file
@ -0,0 +1,86 @@
|
||||
@routing @snap
|
||||
Feature: Snap start/end point to the nearest way
|
||||
|
||||
Scenario: Snap to nearest protruding oneway
|
||||
Given the nodes
|
||||
| | 1 | | 2 | |
|
||||
| 8 | | n | | 3 |
|
||||
| | w | c | e | |
|
||||
| 7 | | s | | 4 |
|
||||
| | 6 | | 5 | |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| nc |
|
||||
| ec |
|
||||
| sc |
|
||||
| wc |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| 1 | c | nc |
|
||||
| 2 | c | nc |
|
||||
| 3 | c | ec |
|
||||
| 4 | c | ec |
|
||||
| 5 | c | sc |
|
||||
| 6 | c | sc |
|
||||
| 7 | c | wc |
|
||||
| 8 | c | wc |
|
||||
|
||||
Scenario: Snap to nearest edge of a square
|
||||
Given the nodes
|
||||
| 4 | 5 | 6 | 7 |
|
||||
| 3 | a | | u |
|
||||
| 2 | | | |
|
||||
| 1 | d | | b |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| aub |
|
||||
| adb |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| 1 | b | adb |
|
||||
| 2 | b | adb |
|
||||
| 6 | b | aub |
|
||||
| 7 | b | aub |
|
||||
|
||||
Scenario: Snap to edge right under start/end point
|
||||
Given the nodes
|
||||
| d | e | f | g |
|
||||
| c | | | h |
|
||||
| b | | | i |
|
||||
| a | l | k | j |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| abcd |
|
||||
| defg |
|
||||
| ghij |
|
||||
| jkla |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | b | abcd |
|
||||
| a | c | abcd |
|
||||
| a | d | abcd |
|
||||
| a | e | abcd |
|
||||
| a | f | abcd |
|
||||
| a | g | abcd |
|
||||
| a | h | jkla |
|
||||
| a | i | jkla |
|
||||
| a | j | jkla |
|
||||
| a | k | jkla |
|
||||
| a | l | jkla |
|
||||
| b | a | abcd |
|
||||
| b | c | abcd |
|
||||
| b | d | abcd |
|
||||
| b | e | abcd |
|
||||
| b | f | abcd |
|
||||
| b | g | abcd |
|
||||
| b | h | jkla |
|
||||
| b | i | jkla |
|
||||
| b | j | jkla |
|
||||
| b | k | jkla |
|
||||
| b | l | jkla |
|
73
features/step_definitions/data.rb
Normal file
73
features/step_definitions/data.rb
Normal file
@ -0,0 +1,73 @@
|
||||
Given /^the speedprofile "([^"]*)"$/ do |profile|
|
||||
read_speedprofile profile
|
||||
end
|
||||
|
||||
Given /^the speedprofile settings$/ do |table|
|
||||
table.raw.each do |row|
|
||||
speedprofile[ row[0] ] = row[1]
|
||||
end
|
||||
end
|
||||
|
||||
Given /^the nodes$/ do |table|
|
||||
table.raw.each_with_index do |row,ri|
|
||||
row.each_with_index do |name,ci|
|
||||
unless name.empty?
|
||||
raise "*** node invalid name '#{name}', must be single characters" unless name.size == 1
|
||||
raise "*** invalid node name '#{name}', must me alphanumeric" unless name.match /[a-z0-9]/
|
||||
raise "*** duplicate node '#{name}'" if name_node_hash[name]
|
||||
node = OSM::Node.new make_osm_id, OSM_USER, OSM_TIMESTAMP, ORIGIN[0]+ci*ZOOM, ORIGIN[1]-ri*ZOOM
|
||||
node << { :name => name }
|
||||
node.uid = OSM_UID
|
||||
osm_db << node
|
||||
name_node_hash[name] = node
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Given /^the ways$/ do |table|
|
||||
table.hashes.each do |row|
|
||||
name = row.delete 'nodes'
|
||||
raise "*** duplicate way '#{name}'" if name_way_hash[name]
|
||||
way = OSM::Way.new make_osm_id, OSM_USER, OSM_TIMESTAMP
|
||||
defaults = { 'highway' => 'primary' }
|
||||
way << defaults.merge( 'name' => name ).merge(row)
|
||||
way.uid = OSM_UID
|
||||
name.each_char do |c|
|
||||
raise "*** node invalid name '#{c}', must be single characters" unless c.size == 1
|
||||
raise "*** ways cannot use numbered nodes, '#{name}'" unless c.match /[a-z]/
|
||||
node = find_node_by_name(c)
|
||||
raise "*** unknown node '#{c}'" unless node
|
||||
way << node
|
||||
end
|
||||
osm_db << way
|
||||
name_way_hash[name] = way
|
||||
end
|
||||
end
|
||||
|
||||
Given /^the relations$/ do |table|
|
||||
table.hashes.each do |row|
|
||||
relation = OSM::Relation.new make_osm_id, OSM_USER, OSM_TIMESTAMP
|
||||
relation << { :type => :restriction, :restriction => 'no_left_turn' }
|
||||
from_way = find_way_by_name(row['from'])
|
||||
raise "*** unknown way '#{row['from']}'" unless from_way
|
||||
to_way = find_way_by_name(row['to'])
|
||||
raise "*** unknown way '#{row['to']}'" unless to_way
|
||||
relation << OSM::Member.new( 'way', from_way.id, 'from' )
|
||||
relation << OSM::Member.new( 'way', to_way.id, 'to' )
|
||||
c = row['via']
|
||||
unless c.empty?
|
||||
raise "*** node invalid name '#{c}', must be single characters" unless c.size == 1
|
||||
raise "*** via node cannot use numbered nodes, '#{c}'" unless c.match /[a-z]/
|
||||
via_node = find_node_by_name(c)
|
||||
raise "*** unknown node '#{row['via']}'" unless via_node
|
||||
relation << OSM::Member.new( 'node', via_node.id, 'via' )
|
||||
end
|
||||
relation.uid = OSM_UID
|
||||
osm_db << relation
|
||||
end
|
||||
end
|
||||
|
||||
Given /^the defaults$/ do
|
||||
end
|
||||
|
72
features/step_definitions/processing.rb
Normal file
72
features/step_definitions/processing.rb
Normal file
@ -0,0 +1,72 @@
|
||||
require 'OSM/StreamParser'
|
||||
|
||||
class OSMTestParserCallbacks < OSM::Callbacks
|
||||
@@locations = nil
|
||||
|
||||
def self.locations
|
||||
if @@locations
|
||||
@@locations
|
||||
else
|
||||
#parse the test file, so we can later reference nodes and ways by name in tests
|
||||
@@locations = {}
|
||||
file = 'test/data/test.osm'
|
||||
callbacks = OSMTestParserCallbacks.new
|
||||
parser = OSM::StreamParser.new(:filename => file, :callbacks => callbacks)
|
||||
parser.parse
|
||||
puts @@locations
|
||||
end
|
||||
end
|
||||
|
||||
def node(node)
|
||||
@@locations[node.name] = [node.lat,node.lon]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Given /^the OSM file contains$/ do |string|
|
||||
file = 'data/test.osm'
|
||||
File.open( file, 'w') {|f| f.write(string) }
|
||||
|
||||
#convert from .osm to .osm.pbf, which is the format osrm reads
|
||||
system "osmosis --read-xml data/test.osm --write-pbf data/test.osm.pbf omitmetadata=true"
|
||||
end
|
||||
|
||||
Given /^the speedprofile contains$/ do |string|
|
||||
File.open( 'speedprofile.ini', 'w') {|f| f.write(string) }
|
||||
end
|
||||
|
||||
|
||||
|
||||
Given /^the data file "([^"]*)" is present$/ do |file|
|
||||
File.exists?(file).should == true
|
||||
end
|
||||
|
||||
When /^I run the extractor with "([^"]*)"$/ do |cmd|
|
||||
@response = `#{cmd}`
|
||||
#Dir.chdir @test_folder do
|
||||
# @response = IO.popen([cmd, :err=>[:child, :out]]) { |ls_io| ls_result_with_error = ls_io.read }
|
||||
#end
|
||||
end
|
||||
|
||||
When /^I run the preprocessor with "([^"]*)"$/ do |cmd|
|
||||
@response = `#{cmd}`
|
||||
end
|
||||
|
||||
Given /^the preprocessed files for "([^"]*)" are present and up to date$/ do |area|
|
||||
File.exists?("#{area}.osrm").should == true
|
||||
File.exists?("#{area}.osrm.names").should == true
|
||||
File.exists?("#{area}.osrm.restrictions").should == true
|
||||
File.exists?("#{area}.osrm.hsgr").should == true
|
||||
File.exists?("#{area}.osrm.nodes").should == true
|
||||
File.exists?("#{area}.osrm.ramIndex").should == true
|
||||
File.exists?("#{area}.osrm.fileIndex").should == true
|
||||
end
|
||||
|
||||
Then /^I should see the file "([^"]*)"$/ do |file|
|
||||
File.exists?(file).should == true
|
||||
end
|
||||
|
||||
When /^preprocessed files for "([^"]*)" has been removed$/ do |file|
|
||||
FileUtils.rm_r Dir["#{file}.*"], :secure => true
|
||||
end
|
||||
|
221
features/step_definitions/routing.rb
Normal file
221
features/step_definitions/routing.rb
Normal file
@ -0,0 +1,221 @@
|
||||
When /^I request a route from ([^"]+) to ([^"]+)$/ do |a,b|
|
||||
@response = request_route a,b
|
||||
#puts @response.body
|
||||
#@response
|
||||
end
|
||||
|
||||
When /^I request a route from "([^"]*)" to "([^"]*)"$/ do |a,b|
|
||||
locations = OSMTestParserCallbacks.locations
|
||||
raise "Locations hash is empty. To reference nodes by name, please preprocess the test file earlier in the test." unless locations
|
||||
raise "Unknown node: #{a}" unless locations[a]
|
||||
raise "Unknown node: #{b}" unless locations[b]
|
||||
@response = request_route "#{locations[a][0]},#{locations[a][1]}", "#{locations[b][0]},#{locations[b][1]}"
|
||||
end
|
||||
|
||||
Then /^I should get a response/ do
|
||||
@response.code.should == "200"
|
||||
@response.body.should_not == nil
|
||||
@response.body.should_not == ''
|
||||
end
|
||||
|
||||
Then /^response should be valid JSON$/ do
|
||||
@json = JSON.parse @response.body
|
||||
end
|
||||
|
||||
Then /^response should be well-formed$/ do
|
||||
@json['version'].class.should == Float
|
||||
@json['status'].class.should == Fixnum
|
||||
@json['status_message'].class.should == String
|
||||
@json['route_summary'].class.should == Hash
|
||||
@json['route_geometry'].class.should == String
|
||||
@json['route_instructions'].class.should == Array
|
||||
@json['via_points'].class.should == Array
|
||||
@json['transactionId'].class.should == String
|
||||
end
|
||||
|
||||
Then /^a route should be found$/ do
|
||||
@json['status'].should == 0
|
||||
@json['status_message'].should == "Found route between points"
|
||||
end
|
||||
|
||||
Then /^no route should be found$/ do
|
||||
@json['status'].should == 207
|
||||
@json['status_message'].should == "Cannot find route between points"
|
||||
end
|
||||
|
||||
Then /^I should get a valid response$/ do
|
||||
step "I should get a response"
|
||||
step "response should be valid JSON"
|
||||
step "response should be well-formed"
|
||||
#step "no error should be reported in terminal"
|
||||
end
|
||||
|
||||
Then /^I should get a route$/ do
|
||||
step "I should get a valid response"
|
||||
step "a route should be found"
|
||||
#puts @response.body
|
||||
end
|
||||
|
||||
Then /^I should not get a route$/ do
|
||||
step "I should get a valid response"
|
||||
step "no route should be found"
|
||||
end
|
||||
|
||||
Then /^the route should start at "([^']*)"$/ do |name|
|
||||
@json['route_summary']['start_point'].should == name
|
||||
end
|
||||
|
||||
Then /^the route should end at "([^']*)"$/ do |name|
|
||||
@json['route_summary']['end_point'].should == name
|
||||
end
|
||||
|
||||
Then /^distance should be between (\d+) and (\d+)$/ do |min,max|
|
||||
@json['route_summary']['total_distance'].to_i.should >= min.to_i
|
||||
@json['route_summary']['total_distance'].to_i.should <= max.to_i
|
||||
end
|
||||
|
||||
Then /^the distance should be close to (\d+)m$/ do |d|
|
||||
@json['route_summary']['total_distance'].to_i.should >= d.to_i*0.95
|
||||
@json['route_summary']['total_distance'].to_i.should <= d.to_i/0.95
|
||||
end
|
||||
|
||||
Then /^number of instructions should be (\d+)$/ do |n|
|
||||
@json['route_instructions'].size.should == n
|
||||
end
|
||||
|
||||
Then /^there should be 1 turn$/ do
|
||||
step 'there should be 1 turns'
|
||||
end
|
||||
|
||||
Then /^there should be (\d+) turns$/ do |n|
|
||||
@json['route_instructions'].map {|t| t.first}.select {|t| t =~ /^Turn/ }.size.should == n.to_i
|
||||
end
|
||||
|
||||
Then /^there should be more than (\d+) turn$/ do |n|
|
||||
@json['route_instructions'].map {|t| t.first}.select {|t| t =~ /^Turn/ }.size.should > n.to_i
|
||||
end
|
||||
|
||||
Then /^there should not be any turns$/ do
|
||||
(@json['route_instructions'].size-1).should == 0
|
||||
end
|
||||
|
||||
def sanitize_route route
|
||||
route.split(',').map{|w| w.strip}.reject(&:empty?).join(', ')
|
||||
end
|
||||
|
||||
def computed_route
|
||||
@json['route_instructions'].map { |r| r[1] }.reject(&:empty?).join(', ')
|
||||
end
|
||||
|
||||
Then /^the route should follow "([^"]*)"$/ do |route|
|
||||
sanitize_route(route).should == computed_route
|
||||
end
|
||||
|
||||
Then /^the route should not follow "([^"]*)"$/ do |route|
|
||||
sanitize_route(route).should_not == computed_route
|
||||
end
|
||||
|
||||
Then /^the route should include "([^"]*)"$/ do |route|
|
||||
sanitize_route(route).should =~ /#{computed_route}/
|
||||
end
|
||||
|
||||
Then /^the route should not include "([^"]*)"$/ do |route|
|
||||
sanitize_route(route).should_not =~ /#{computed_route}/
|
||||
end
|
||||
|
||||
Then /^the route should stay on "([^"]*)"$/ do |way|
|
||||
step "the route should start at \"#{way}\""
|
||||
step "the route should end at \"#{way}\""
|
||||
step "the route should follow \"#{way}\""
|
||||
step "there should not be any turns"
|
||||
end
|
||||
|
||||
When /^I route between "([^"]*)" and "([^"]*)"$/ do |from,to|
|
||||
reprocess
|
||||
Dir.chdir 'test' do
|
||||
from_node = name_node_hash[from]
|
||||
to_node = name_node_hash[to]
|
||||
a = "#{from_node.lon},#{from_node.lat}"
|
||||
b = "#{to_node.lon},#{to_node.lat}"
|
||||
@route = parse_response( request_route(a,b) )
|
||||
end
|
||||
end
|
||||
|
||||
Then /^"([^"]*)" should be returned$/ do |route|
|
||||
@route.should == route.split(',').join(',')
|
||||
end
|
||||
|
||||
Then /^routability should be$/ do |table|
|
||||
osrm_kill
|
||||
build_ways_from_table table
|
||||
reprocess
|
||||
actual = []
|
||||
if table.headers&["forw","backw"] == []
|
||||
raise "*** routability tabel must contain either 'forw' or 'backw' column"
|
||||
end
|
||||
OSRMLauncher.new do
|
||||
table.hashes.each_with_index do |row,i|
|
||||
got = row.dup
|
||||
attempts = []
|
||||
if table.headers.include? 'forw'
|
||||
response = request_route("#{ORIGIN[1]},#{ORIGIN[0]+(1+WAY_SPACING*i)*ZOOM}","#{ORIGIN[1]},#{ORIGIN[0]+(2+WAY_SPACING*i)*ZOOM}")
|
||||
got['forw'] = route_status response
|
||||
if got['forw'] != row['forw']
|
||||
json = JSON.parse(response.body)
|
||||
attempts << { :attempt => 'Forward', :query => @query, :response => response }
|
||||
end
|
||||
end
|
||||
if table.headers.include? 'backw'
|
||||
response = request_route("#{ORIGIN[1]},#{ORIGIN[0]+(2+WAY_SPACING*i)*ZOOM}","#{ORIGIN[1]},#{ORIGIN[0]+(1+WAY_SPACING*i)*ZOOM}")
|
||||
got['backw'] = route_status response
|
||||
if got['backw'] != row['backw']
|
||||
attempts << { :attempt => 'Backward', :query => @query, :response => response }
|
||||
end
|
||||
end
|
||||
if got != row
|
||||
log_fail row,got,attempts
|
||||
end
|
||||
actual << got
|
||||
end
|
||||
end
|
||||
table.diff! actual
|
||||
end
|
||||
|
||||
When /^I route I should get$/ do |table|
|
||||
osrm_kill
|
||||
reprocess
|
||||
actual = []
|
||||
OSRMLauncher.new do
|
||||
table.hashes.each_with_index do |row,ri|
|
||||
from_node = @name_node_hash[ row['from'] ]
|
||||
raise "*** unknown from-node '#{row['from']}" unless from_node
|
||||
to_node = @name_node_hash[ row['to'] ]
|
||||
raise "*** unknown to-node '#{row['to']}" unless to_node
|
||||
response = request_route("#{from_node.lat},#{from_node.lon}", "#{to_node.lat},#{to_node.lon}")
|
||||
if response.code == "200" && response.body.empty? == false
|
||||
json = JSON.parse response.body
|
||||
if json['status'] == 0
|
||||
instructions = way_list json['route_instructions']
|
||||
end
|
||||
end
|
||||
|
||||
got = {'from' => row['from'], 'to' => row['to'] }
|
||||
if table.headers.include? 'start'
|
||||
got['start'] = instructions ? json['route_summary']['start_point'] : nil
|
||||
end
|
||||
if table.headers.include? 'end'
|
||||
got['end'] = instructions ? json['route_summary']['end_point'] : nil
|
||||
end
|
||||
if table.headers.include? 'route'
|
||||
got['route'] = (instructions || '').strip
|
||||
end
|
||||
|
||||
if got['route'] != row['route'] || got['start'] != row['start'] || got['end'] != row['end']
|
||||
failed = { :attempt => 'Backward', :query => @query, :response => response }
|
||||
log_fail row,got,[failed]
|
||||
end
|
||||
actual << got
|
||||
end
|
||||
end
|
||||
table.diff! actual
|
||||
end
|
41
features/support/config.rb
Normal file
41
features/support/config.rb
Normal file
@ -0,0 +1,41 @@
|
||||
def speedprofile
|
||||
@speedprofile ||= reset_speedprofile
|
||||
end
|
||||
|
||||
def reset_speedprofile
|
||||
@speedprofile = {}
|
||||
read_speedprofile DEFAULT_SPEEDPROFILE
|
||||
end
|
||||
|
||||
def read_speedprofile profile
|
||||
@speedprofile = {}
|
||||
@speedprofile_str = nil
|
||||
s = File.read "speedprofiles/#{profile}.ini"
|
||||
s.scan /(.*)=(.*)/ do |option|
|
||||
@speedprofile[option[0].strip] = option[1].strip
|
||||
end
|
||||
end
|
||||
|
||||
def speedprofile_str
|
||||
@speedprofile_str ||= "[Scenario: #{@scenario_title}]\n" + @speedprofile.map { |k,v| " #{k} = #{v}" }.join("\n")
|
||||
end
|
||||
|
||||
def write_speedprofile
|
||||
File.open( 'speedprofile.ini', 'w') {|f| f.write( speedprofile_str ) }
|
||||
end
|
||||
|
||||
def write_server_ini
|
||||
s=<<-EOF
|
||||
Threads = 1
|
||||
IP = 0.0.0.0
|
||||
Port = 5000
|
||||
|
||||
hsgrData=#{@osm_file}.osrm.hsgr
|
||||
nodesData=#{@osm_file}.osrm.nodes
|
||||
ramIndex=#{@osm_file}.osrm.ramIndex
|
||||
fileIndex=#{@osm_file}.osrm.fileIndex
|
||||
namesData=#{@osm_file}.osrm.names
|
||||
EOF
|
||||
File.open( 'server.ini', 'w') {|f| f.write( s ) }
|
||||
end
|
||||
|
189
features/support/data.rb
Normal file
189
features/support/data.rb
Normal file
@ -0,0 +1,189 @@
|
||||
require 'OSM/objects' #osmlib gem
|
||||
require 'OSM/Database'
|
||||
require 'builder'
|
||||
|
||||
OSM_USER = 'osrm'
|
||||
OSM_GENERATOR = 'osrm-test'
|
||||
OSM_UID = 1
|
||||
TEST_FOLDER = 'test'
|
||||
DATA_FOLDER = 'cache'
|
||||
PREPROCESS_LOG_FILE = 'preprocessing.log'
|
||||
LOG_FILE = 'fail.log'
|
||||
OSM_TIMESTAMP = '2000-00-00T00:00:00Z'
|
||||
DEFAULT_SPEEDPROFILE = 'bicycle'
|
||||
WAY_SPACING = 10
|
||||
|
||||
ORIGIN = [1,1]
|
||||
NODE_SPACING = 100 #meters
|
||||
ZOOM = 0.001*(NODE_SPACING.to_f/111.0)
|
||||
|
||||
def build_ways_from_table table
|
||||
#add one unconnected way for each row
|
||||
table.hashes.each_with_index do |row,ri|
|
||||
#NOTE:
|
||||
#currently osrm crashes when processing an isolated oneway with just 2 nodes, so we use 4
|
||||
#this is relatated to the fact that a oneway deadend doesn't make a lot of sense
|
||||
|
||||
#if we stack ways on different x coordinates, outability tests get messed up, because osrm might pick a neighboring way if the one test can't be used.
|
||||
#instead we place all lines as a string on the same y coordinate. this prevents using neightboring ways.
|
||||
|
||||
#a few nodes...
|
||||
node1 = OSM::Node.new make_osm_id, OSM_USER, OSM_TIMESTAMP, ORIGIN[0]+(0+WAY_SPACING*ri)*ZOOM, ORIGIN[1]
|
||||
node2 = OSM::Node.new make_osm_id, OSM_USER, OSM_TIMESTAMP, ORIGIN[0]+(1+WAY_SPACING*ri)*ZOOM, ORIGIN[1]
|
||||
node3 = OSM::Node.new make_osm_id, OSM_USER, OSM_TIMESTAMP, ORIGIN[0]+(2+WAY_SPACING*ri)*ZOOM, ORIGIN[1]
|
||||
node4 = OSM::Node.new make_osm_id, OSM_USER, OSM_TIMESTAMP, ORIGIN[0]+(3+WAY_SPACING*ri)*ZOOM, ORIGIN[1]
|
||||
node1.uid = OSM_UID
|
||||
node2.uid = OSM_UID
|
||||
node3.uid = OSM_UID
|
||||
node4.uid = OSM_UID
|
||||
node1 << { :name => "a#{ri}" }
|
||||
node2 << { :name => "b#{ri}" }
|
||||
node3 << { :name => "c#{ri}" }
|
||||
node4 << { :name => "d#{ri}" }
|
||||
|
||||
osm_db << node1
|
||||
osm_db << node2
|
||||
osm_db << node3
|
||||
osm_db << node4
|
||||
|
||||
#...with a way between them
|
||||
way = OSM::Way.new make_osm_id, OSM_USER, OSM_TIMESTAMP
|
||||
way.uid = OSM_UID
|
||||
way << node1
|
||||
way << node2
|
||||
way << node3
|
||||
way << node4
|
||||
tags = row.dup
|
||||
tags.delete 'forw'
|
||||
tags.delete 'backw'
|
||||
tags['name'] = "abcd#{ri}"
|
||||
tags.reject! { |k,v| v=='' }
|
||||
way << tags
|
||||
osm_db << way
|
||||
end
|
||||
end
|
||||
|
||||
def find_node_by_name s
|
||||
name_node_hash[s.to_s]
|
||||
end
|
||||
|
||||
def find_way_by_name s
|
||||
name_way_hash[s.to_s] || name_way_hash[s.to_s.reverse]
|
||||
end
|
||||
|
||||
def reset_data
|
||||
Dir.chdir TEST_FOLDER do
|
||||
#clear_log
|
||||
#clear_data_files
|
||||
end
|
||||
reset_speedprofile
|
||||
reset_osm
|
||||
@fingerprint = nil
|
||||
end
|
||||
|
||||
def make_osm_id
|
||||
@osm_id = @osm_id+1
|
||||
end
|
||||
|
||||
def reset_osm
|
||||
osm_db.clear
|
||||
name_node_hash.clear
|
||||
name_way_hash.clear
|
||||
@osm_str = nil
|
||||
@osm_hash = nil
|
||||
|
||||
##ID -1 causes trouble, so add a few nodes to avoid it
|
||||
#node = OSM::Node.new nil, OSM_USER, OSM_TIMESTAMP, 0,0
|
||||
#node = OSM::Node.new nil, OSM_USER, OSM_TIMESTAMP, 0,0
|
||||
@osm_id = 0
|
||||
end
|
||||
|
||||
def clear_data_files
|
||||
File.delete *Dir.glob("#{DATA_FOLDER}/test.*")
|
||||
end
|
||||
|
||||
def clear_log
|
||||
File.delete *Dir.glob("*.log")
|
||||
end
|
||||
|
||||
def osm_db
|
||||
@osm_db ||= OSM::Database.new
|
||||
end
|
||||
|
||||
def name_node_hash
|
||||
@name_node_hash ||= {}
|
||||
end
|
||||
|
||||
def name_way_hash
|
||||
@name_way_hash ||= {}
|
||||
end
|
||||
|
||||
def osm_str
|
||||
return @osm_str if @osm_str
|
||||
@osm_str = ''
|
||||
doc = Builder::XmlMarkup.new :indent => 2, :target => @osm_str
|
||||
doc.instruct!
|
||||
osm_db.to_xml doc, OSM_GENERATOR
|
||||
@osm_str
|
||||
end
|
||||
|
||||
def write_osm
|
||||
#write .oms file if needed
|
||||
Dir.mkdir DATA_FOLDER unless File.exist? DATA_FOLDER
|
||||
@osm_file = "#{DATA_FOLDER}/#{fingerprint}"
|
||||
unless File.exist?("#{@osm_file}.osm")
|
||||
File.open( "#{@osm_file}.osm", 'w') {|f| f.write(osm_str) }
|
||||
end
|
||||
end
|
||||
|
||||
def convert_osm_to_pbf
|
||||
unless File.exist?("#{@osm_file}.osm.pbf")
|
||||
log_preprocess_info
|
||||
log "== Converting #{@osm_file}.osm to protobuffer format...", :preprocess
|
||||
#redirect stdout and stderr to a log file avoid output in the cucumber console
|
||||
unless system "osmosis --read-xml #{@osm_file}.osm --write-pbf #{@osm_file}.osm.pbf omitmetadata=true 1>>#{PREPROCESS_LOG_FILE} 2>>#{PREPROCESS_LOG_FILE}"
|
||||
raise "Failed to convert to proto buffer format. Please see #{hash}.log for more info."
|
||||
end
|
||||
log '', :preprocess
|
||||
end
|
||||
end
|
||||
|
||||
def extracted?
|
||||
File.exist?("#{@osm_file}.osrm") &&
|
||||
File.exist?("#{@osm_file}.osrm.names") &&
|
||||
File.exist?("#{@osm_file}.osrm.restrictions")
|
||||
end
|
||||
|
||||
def prepared?
|
||||
base = "#{DATA_FOLDER}/#{fingerprint}"
|
||||
File.exist?("#{base}.osrm.hsgr")
|
||||
end
|
||||
|
||||
def reprocess
|
||||
Dir.chdir TEST_FOLDER do
|
||||
write_speedprofile
|
||||
write_osm
|
||||
convert_osm_to_pbf
|
||||
unless extracted?
|
||||
log_preprocess_info
|
||||
log "== Extracting #{@osm_file}.osm...", :preprocess
|
||||
unless system "../osrm-extract #{@osm_file}.osm.pbf 1>>#{PREPROCESS_LOG_FILE} 2>>#{PREPROCESS_LOG_FILE}"
|
||||
log "*** Exited with code #{$?.exitstatus}.", :preprocess
|
||||
raise "*** osrm-extract exited with code #{$?.exitstatus}. The file preprocess.log might contain more info."
|
||||
end
|
||||
log '', :preprocess
|
||||
end
|
||||
unless prepared?
|
||||
log_preprocess_info
|
||||
log "== Preparing #{@osm_file}.osm...", :preprocess
|
||||
unless system "../osrm-prepare #{@osm_file}.osrm #{@osm_file}.restrictions 1>>#{PREPROCESS_LOG_FILE} 2>>#{PREPROCESS_LOG_FILE}"
|
||||
log "*** Exited with code #{$?.exitstatus}.", :preprocess
|
||||
raise "*** osrm-prepare exited with code #{$?.exitstatus}. The file preprocess.log might contain more info."
|
||||
end
|
||||
log '', :preprocess
|
||||
end
|
||||
log_preprocess_done
|
||||
write_server_ini
|
||||
end
|
||||
end
|
||||
|
38
features/support/hash.rb
Normal file
38
features/support/hash.rb
Normal file
@ -0,0 +1,38 @@
|
||||
require 'digest/sha1'
|
||||
|
||||
def hash_of_file path
|
||||
hash = Digest::SHA1.new
|
||||
open(path,'r') do |io|
|
||||
while !io.eof
|
||||
buf = io.readpartial 1024
|
||||
hash.update buf
|
||||
end
|
||||
end
|
||||
return hash.hexdigest
|
||||
end
|
||||
|
||||
def speedprofile_hash
|
||||
@speedprofile_hash ||= Digest::SHA1.hexdigest speedprofile_str
|
||||
end
|
||||
|
||||
def osm_hash
|
||||
@osm_hash ||= Digest::SHA1.hexdigest osm_str
|
||||
end
|
||||
|
||||
def osm_hash
|
||||
@osm_hash ||= Digest::SHA1.hexdigest osm_str
|
||||
end
|
||||
|
||||
def bin_extract_hash
|
||||
@bin_hash ||= hash_of_file '../osrm-extract'
|
||||
end
|
||||
|
||||
def bin_prepare_hash
|
||||
@bin_hash ||= hash_of_file '../osrm-prepare'
|
||||
end
|
||||
|
||||
#combine state of data, speedprofile and binaries into a hash that identifies the exact test scenario
|
||||
def fingerprint
|
||||
@fingerprint ||= Digest::SHA1.hexdigest "#{bin_extract_hash}-#{bin_prepare_hash}-#{speedprofile_hash}-#{osm_hash}"
|
||||
end
|
||||
|
17
features/support/hooks.rb
Normal file
17
features/support/hooks.rb
Normal file
@ -0,0 +1,17 @@
|
||||
Before do |scenario|
|
||||
@scenario_title = scenario.title
|
||||
@scenario_time = Time.now.strftime("%Y-%m-%dT%H:%m:%SZ")
|
||||
reset_data
|
||||
@has_logged_preprocess_info = false
|
||||
@has_logged_scenario_info = false
|
||||
end
|
||||
|
||||
Around('@routing') do |scenario, block|
|
||||
Timeout.timeout(10) do
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
After do
|
||||
osrm_kill
|
||||
end
|
71
features/support/launch.rb
Normal file
71
features/support/launch.rb
Normal file
@ -0,0 +1,71 @@
|
||||
require 'socket'
|
||||
require 'sys/proctable'
|
||||
|
||||
class OSRMLauncher
|
||||
def initialize &block
|
||||
Dir.chdir TEST_FOLDER do
|
||||
osrm_up
|
||||
yield
|
||||
osrm_down
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def each_process name, &block
|
||||
Sys::ProcTable.ps do |process|
|
||||
if process.comm.strip == name.strip
|
||||
yield process.pid.to_i, process.state.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def osrm_up?
|
||||
find_pid('osrm-routed') != nil
|
||||
end
|
||||
|
||||
def find_pid name
|
||||
each_process(name) { |pid,state| return pid.to_i }
|
||||
return nil
|
||||
end
|
||||
|
||||
def osrm_up
|
||||
return if osrm_up?
|
||||
pipe = IO.popen('../osrm-routed 1>>osrm-routed.log 2>>osrm-routed.log')
|
||||
timeout = 5
|
||||
(timeout*10).times do
|
||||
begin
|
||||
socket = TCPSocket.new('localhost', 5000)
|
||||
socket.puts 'ping'
|
||||
rescue Errno::ECONNREFUSED
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
sleep 0.1
|
||||
end
|
||||
|
||||
def osrm_down
|
||||
each_process('osrm-routed') { |pid,state| Process.kill 'TERM', pid }
|
||||
each_process('osrm-prepare') { |pid,state| Process.kill 'TERM', pid }
|
||||
each_process('osrm-extract') { |pid,state| Process.kill 'TERM', pid }
|
||||
wait_for_shutdown 'osrm-routed'
|
||||
wait_for_shutdown 'osrm-prepare'
|
||||
wait_for_shutdown 'osrm-extract'
|
||||
end
|
||||
|
||||
def osrm_kill
|
||||
each_process('osrm-routed') { |pid,state| Process.kill 'KILL', pid }
|
||||
each_process('osrm-prepare') { |pid,state| Process.kill 'KILL', pid }
|
||||
each_process('osrm-extract') { |pid,state| Process.kill 'KILL', pid }
|
||||
wait_for_shutdown 'osrm-routed'
|
||||
wait_for_shutdown 'osrm-prepare'
|
||||
wait_for_shutdown 'osrm-extract'
|
||||
end
|
||||
|
||||
def wait_for_shutdown name
|
||||
timeout = 10
|
||||
(timeout*10).times do
|
||||
return if find_pid(name) == nil
|
||||
sleep 0.1
|
||||
end
|
||||
raise "*** Could not terminate #{name}."
|
||||
end
|
66
features/support/log.rb
Normal file
66
features/support/log.rb
Normal file
@ -0,0 +1,66 @@
|
||||
def log s='', type=nil
|
||||
if type == :preprocess
|
||||
file = PREPROCESS_LOG_FILE
|
||||
else
|
||||
file = LOG_FILE
|
||||
end
|
||||
File.open(file, 'a') {|f| f.write("#{s}\n") }
|
||||
end
|
||||
|
||||
|
||||
def log_scenario_fail_info
|
||||
return if @has_logged_scenario_info
|
||||
log "========================================="
|
||||
log "Failed scenario: #{@scenario_title}"
|
||||
log "Time: #{@scenario_time}"
|
||||
log
|
||||
log '```xml' #so output can be posted directly to github comment fields
|
||||
log osm_str.strip
|
||||
log '```'
|
||||
log
|
||||
log speedprofile_str
|
||||
log
|
||||
@has_logged_scenario_info = true
|
||||
end
|
||||
|
||||
def log_fail expected,actual,failed
|
||||
log_scenario_fail_info
|
||||
log "== "
|
||||
log "Expected: #{expected}"
|
||||
log "Got: #{actual}"
|
||||
log
|
||||
failed.each do |fail|
|
||||
log "Attempt: #{fail[:attempt]}"
|
||||
log "Query: #{fail[:query]}"
|
||||
log "Response: #{fail[:response].body}"
|
||||
log
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def log_preprocess_info
|
||||
return if @has_logged_preprocess_info
|
||||
log "=========================================", :preprocess
|
||||
log "Preprocessing data for scenario: #{@scenario_title}", :preprocess
|
||||
log "Time: #{@scenario_time}", :preprocess
|
||||
log '', :preprocess
|
||||
log "== OSM data:", :preprocess
|
||||
log '```xml', :preprocess #so output can be posted directly to github comment fields
|
||||
log osm_str, :preprocess
|
||||
log '```', :preprocess
|
||||
log '', :preprocess
|
||||
log "== Speed profile:", :preprocess
|
||||
log speedprofile_str.strip, :preprocess
|
||||
log '', :preprocess
|
||||
@has_logged_preprocess_info = true
|
||||
end
|
||||
|
||||
def log_preprocess str
|
||||
log_preprocess_info
|
||||
log str, :preprocess
|
||||
end
|
||||
|
||||
def log_preprocess_done
|
||||
end
|
||||
|
||||
|
14
features/support/osmlib.rb
Normal file
14
features/support/osmlib.rb
Normal file
@ -0,0 +1,14 @@
|
||||
#monkey-patch osmlib to fix a bug
|
||||
|
||||
module OSM
|
||||
class Way
|
||||
def to_xml(xml)
|
||||
xml.way(attributes) do
|
||||
nodes.each do |node|
|
||||
xml.nd(:ref => node)
|
||||
end
|
||||
tags.to_xml(xml)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
68
features/support/route.rb
Normal file
68
features/support/route.rb
Normal file
@ -0,0 +1,68 @@
|
||||
require 'net/http'
|
||||
|
||||
def request_route a,b
|
||||
@query = "http://localhost:5000/viaroute&start=#{a}&dest=#{b}&output=json&geomformat=cmp"
|
||||
#log @query
|
||||
uri = URI.parse @query
|
||||
Net::HTTP.get_response uri
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
raise "*** osrm-routed is not running."
|
||||
rescue Timeout::Error
|
||||
raise "*** osrm-routed did not respond."
|
||||
end
|
||||
|
||||
def parse_response response
|
||||
if response.code == "200" && response.body.empty? == false
|
||||
json = JSON.parse response.body
|
||||
if json['status'] == 0
|
||||
route = way_list json['route_instructions']
|
||||
if route.empty?
|
||||
"Empty route: #{json['route_instructions']}"
|
||||
else
|
||||
"Route: #{route}"
|
||||
end
|
||||
elsif json['status'] == 207
|
||||
"No route"
|
||||
else
|
||||
"Status: #{json['status']}"
|
||||
end
|
||||
else
|
||||
"HTTP: #{response.code}"
|
||||
end
|
||||
end
|
||||
|
||||
def got_route? response
|
||||
if response.code == "200" && !response.body.empty?
|
||||
json = JSON.parse response.body
|
||||
if json['status'] == 0
|
||||
return way_list( json['route_instructions']).empty? == false
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def route_status response
|
||||
if response.code == "200" && !response.body.empty?
|
||||
json = JSON.parse response.body
|
||||
if json['status'] == 0
|
||||
if way_list( json['route_instructions']).empty?
|
||||
return 'Empty route'
|
||||
else
|
||||
return 'x'
|
||||
end
|
||||
elsif json['status'] == 207
|
||||
''
|
||||
else
|
||||
"Status #{json['status']}"
|
||||
end
|
||||
else
|
||||
"HTTP #{response.code}"
|
||||
end
|
||||
end
|
||||
|
||||
def way_list instructions
|
||||
instructions.
|
||||
#reject { |i| i[2]<=1 }. #FIXME temporary hack to ignore instructions with length==0
|
||||
map { |r| r[1] }.
|
||||
reject(&:empty?).join(',')
|
||||
end
|
29
features/weight.feature
Normal file
29
features/weight.feature
Normal file
@ -0,0 +1,29 @@
|
||||
@routing @weight
|
||||
Feature: Choosing route based on length, speed, etc
|
||||
|
||||
Scenario: Pick the geometrically shortest route, way types being equal
|
||||
Given the nodes
|
||||
| | s | |
|
||||
| | t | |
|
||||
| a | | b |
|
||||
|
||||
And the ways
|
||||
| nodes |
|
||||
| atb |
|
||||
| asb |
|
||||
|
||||
When I route I should get
|
||||
| from | to | route |
|
||||
| a | b | atb |
|
||||
| a | b | atb |
|
||||
|
||||
Scenario: Pick the fastest way type, lengths being equal
|
||||
Given the nodes
|
||||
| a | s |
|
||||
| p | b |
|
||||
|
||||
And the ways
|
||||
| nodes | highway |
|
||||
| apb | primary |
|
||||
| asb | secondary |
|
||||
|
27
speedprofiles/bicycle.ini
Normal file
27
speedprofiles/bicycle.ini
Normal file
@ -0,0 +1,27 @@
|
||||
[bicycle]
|
||||
accessTag = bicycle
|
||||
defaultSpeed = 17
|
||||
obeyOneways = yes
|
||||
useRestrictions = yes
|
||||
obeyBollards = no
|
||||
|
||||
cycleway = 19
|
||||
primary = 19
|
||||
primary_link = 19
|
||||
secondary = 17
|
||||
secondary_link = 17
|
||||
tertiary = 15
|
||||
residential = 15
|
||||
unclassified = 15
|
||||
living_street = 13
|
||||
road = 13
|
||||
service = 12
|
||||
track = 12
|
||||
path = 12
|
||||
footway = 10
|
||||
pedestrian = 5
|
||||
pier = 5
|
||||
steps = 3
|
||||
ferry = 5
|
||||
|
||||
excludeFromGrid = ferry
|
23
speedprofiles/car.ini
Normal file
23
speedprofiles/car.ini
Normal file
@ -0,0 +1,23 @@
|
||||
[car]
|
||||
accessTag = motorcar
|
||||
defaultSpeed = 50
|
||||
obeyOneways = yes
|
||||
useRestrictions = yes
|
||||
barrier = bollard
|
||||
|
||||
motorway = 100
|
||||
motorway_link = 90
|
||||
trunk = 90
|
||||
trunk_link = 70
|
||||
primary = 70
|
||||
primary_link = 60
|
||||
secondary = 60
|
||||
secondary_link = 50
|
||||
tertiary = 50
|
||||
tertiary_link = 40
|
||||
road = 40
|
||||
residential = 40
|
||||
unclassified = 30
|
||||
service = 20
|
||||
living_street = 10
|
||||
|
26
speedprofiles/ebiki.ini
Normal file
26
speedprofiles/ebiki.ini
Normal file
@ -0,0 +1,26 @@
|
||||
[ebike]
|
||||
accessTag = bicycle
|
||||
obeyOneways = yes
|
||||
defaultSpeed = 25
|
||||
useRestrictions = no
|
||||
|
||||
cycleway = 25
|
||||
trunk = 25
|
||||
trunk_link = 25
|
||||
primary = 25
|
||||
primary_link = 25
|
||||
secondary = 25
|
||||
secondary_link = 25
|
||||
tertiary = 25
|
||||
unclassified = 25
|
||||
residential = 25
|
||||
living_street = 20
|
||||
service = 20
|
||||
track = 20
|
||||
path = 15
|
||||
pier = 5
|
||||
pedestrian = 5
|
||||
footway = 5
|
||||
ferry = 5
|
||||
|
||||
excludeFromGrid = ferry
|
3
test/.gitignore
vendored
Normal file
3
test/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/server.ini
|
||||
/cache
|
||||
/speedprofile.ini
|
1
test/.stxxl
Normal file
1
test/.stxxl
Normal file
@ -0,0 +1 @@
|
||||
disk=/tmp/stxxl,1,syscall
|
Loading…
Reference in New Issue
Block a user