├── circle.yml ├── .rubocop.yml ├── Berksfile ├── templates └── default │ ├── logrotate.erb │ ├── maproulette.json.erb │ ├── sv-workerd-global-run.erb │ ├── sv-proxyd-global-run.erb │ ├── sv-prime-httpd-run.erb │ ├── transit_dev_routes.tmpl.erb │ ├── health_check.sh.erb │ ├── run.sh.erb │ ├── test_tiles.sh.erb │ ├── batch.sh.erb │ ├── push_tiles.py.erb │ ├── minutely_update.sh.erb │ ├── cut_tiles.sh.erb │ ├── valhalla.json.erb │ ├── map_roulette.py.erb │ └── transit_prod_routes.tmpl.erb ├── recipes ├── _start.rb ├── _stop.rb ├── _restart.rb ├── default.rb ├── get_elevation_tiles.rb ├── get_routing_tiles.rb ├── install_from_ppa.rb ├── install_from_source.rb ├── elevation_service.rb ├── cut_tiles.rb ├── routing_service.rb ├── get_osm_data.rb └── setup.rb ├── Gemfile ├── .gitignore ├── CHANGELOG.md ├── spec ├── support │ └── matchers │ │ ├── runit_service.rb │ │ └── user_account.rb └── spec_helper.rb ├── Berksfile.lock ├── Rakefile ├── metadata.rb ├── definitions ├── stop_service.rb └── start_service.rb ├── LICENSE.md ├── chefignore ├── Vagrantfile ├── README.md ├── Gemfile.lock └── attributes └── default.rb /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | ruby: 3 | version: 2.1.0 4 | test: 5 | override: 6 | - bundle exec rake 7 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | LineLength: 2 | Max: 300 3 | SingleSpaceBeforeFirstArg: 4 | Enabled: false 5 | SpaceBeforeFirstArg: 6 | Enabled: false 7 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'http://api.berkshelf.com' 2 | metadata 3 | 4 | # opsworks 5 | cookbook 'apt' 6 | cookbook 'runit' 7 | cookbook 'user' 8 | -------------------------------------------------------------------------------- /templates/default/logrotate.erb: -------------------------------------------------------------------------------- 1 | <%= node[:valhalla][:log_dir] %>/*.log { 2 | daily 3 | rotate 7 4 | missingok 5 | notifempty 6 | compress 7 | copytruncate 8 | } 9 | -------------------------------------------------------------------------------- /recipes/_start.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: _start 5 | # 6 | 7 | # turn everything back on 8 | start_service do 9 | end 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'berkshelf', '= 3.1.4' 4 | gem 'chefspec', '= 4.0.1' 5 | gem 'foodcritic', '= 4.0.0' 6 | gem 'rainbow', '= 2.0.0' 7 | gem 'rubocop', '= 0.24.0' 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site-cookbooks 2 | nodes 3 | .vagrant 4 | *~ 5 | *# 6 | .#* 7 | \#*# 8 | .*.sw[a-z] 9 | *.un~ 10 | /cookbooks 11 | vendor/ 12 | tmp/ 13 | 14 | # Bundler 15 | bin/* 16 | .bundle/* 17 | vendor/ 18 | -------------------------------------------------------------------------------- /recipes/_stop.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: _stop 5 | # 6 | 7 | # stop everything from running 8 | include_recipe 'runit::default' 9 | stop_service do 10 | end 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | valhalla changelog 2 | ================== 3 | 4 | 0.4.3 5 | ----- 6 | * cut tiles fixes 7 | 8 | 0.4.2 9 | ----- 10 | * fix for faster restarts of services 11 | 12 | 0.4.1 13 | ----- 14 | * worker count 15 | 16 | 0.1.0 17 | ----- 18 | * initial stable release 19 | -------------------------------------------------------------------------------- /recipes/_restart.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: _restart 5 | # 6 | 7 | # stop everything from running 8 | include_recipe 'runit::default' 9 | stop_service do 10 | end 11 | 12 | # turn everything back on 13 | start_service do 14 | end 15 | -------------------------------------------------------------------------------- /templates/default/maproulette.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "recurring_tasks_file": "<%= node[:maproulette][:recurring_tasks_file] %>", 3 | "server_url": "<%= node[:maproulette][:server_url] %>", 4 | "api_key": "<%= node[:maproulette][:api_key] %>", 5 | "challenges": { 6 | "983": ["Node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /templates/default/sv-workerd-global-run.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd <%= node[:valhalla][:base_dir] %> 4 | exec 2>&1 5 | exec chpst -u <%= node[:valhalla][:user][:name] %> -e /etc/sv/workerd-<%= @options[:layer] %>-<%= @options[:num] %>/env valhalla_<%= @options[:layer] %>_worker <%= node[:valhalla][:config] %> 6 | -------------------------------------------------------------------------------- /spec/support/matchers/runit_service.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | def enable_runit_service(service) 3 | ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :enable, service) 4 | end 5 | 6 | def start_runit_service(service) 7 | ChefSpec::Matchers::ResourceMatcher.new(:runit_service, :start, service) 8 | end 9 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: default 5 | # 6 | 7 | include_recipe 'apt::default' 8 | 9 | package 'git' 10 | 11 | %w( 12 | user::default 13 | valhalla::setup 14 | valhalla::install_from_source 15 | ).each do |r| 16 | include_recipe r 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/matchers/user_account.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | def create_user_account(resource_name) 3 | ChefSpec::Matchers::ResourceMatcher.new(:user_account, :create, resource_name) 4 | end 5 | 6 | def delete_user_account(resource_name) 7 | ChefSpec::Matchers::ResourceMatcher.new(:user_account, :delete, resource_name) 8 | end 9 | -------------------------------------------------------------------------------- /Berksfile.lock: -------------------------------------------------------------------------------- 1 | DEPENDENCIES 2 | apt 3 | runit 4 | user 5 | valhalla 6 | path: . 7 | metadata: true 8 | 9 | GRAPH 10 | apt (3.0.0) 11 | packagecloud (0.0.17) 12 | runit (1.6.0) 13 | packagecloud (>= 0.0.0) 14 | user (0.4.2) 15 | valhalla (0.8.0) 16 | apt (>= 0.0.0) 17 | runit (>= 0.0.0) 18 | user (>= 0.0.0) 19 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | require 'chefspec' 3 | require 'chefspec/berkshelf' 4 | 5 | ChefSpec::Coverage.start! 6 | 7 | # load custom matchers 8 | Dir.glob('spec/support/matchers/*.rb') do |custom_matcher| 9 | load "#{custom_matcher}" 10 | end 11 | 12 | RSpec.configure do |config| 13 | config.log_level = :error 14 | config.platform = 'ubuntu' 15 | config.version = '12.04' 16 | end 17 | -------------------------------------------------------------------------------- /templates/default/sv-proxyd-global-run.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd <%= node[:valhalla][:base_dir] %> 4 | PROXY_IN=$(jq -r ".<%= @options[:layer] %>.service.proxy" <%= node[:valhalla][:config] %>)_in 5 | PROXY_OUT=$(jq -r ".<%= @options[:layer] %>.service.proxy" <%= node[:valhalla][:config] %>)_out 6 | exec 2>&1 7 | exec chpst -u <%= node[:valhalla][:user][:name] %> -e /etc/sv/proxyd-<%= @options[:layer] %>/env prime_proxyd $PROXY_IN $PROXY_OUT 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # -*- coding: UTF-8 -*- 3 | 4 | require 'rainbow/ext/string' 5 | 6 | desc 'Run integration tests: foodcritic, rubocop, rspec' 7 | task :build do 8 | # fail the build only for correctness 9 | puts "\nRunning foodcritic".color(:blue) 10 | sh 'foodcritic --chef-version 11.10 --tags ~FC001 --epic-fail correctness .' 11 | 12 | # check ruby syntax 13 | puts 'Running rubocop'.color(:blue) 14 | sh 'rubocop .' 15 | end 16 | 17 | task default: 'build' 18 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | 3 | name 'valhalla' 4 | maintainer 'valhalla' 5 | maintainer_email 'valhalla@mapzen.com' 6 | license 'MIT' 7 | description 'Installs/Configures valhalla' 8 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 9 | version '1.0.8' 10 | 11 | recipe 'valhalla', 'Installs valhalla' 12 | 13 | %w( 14 | apt 15 | user 16 | runit 17 | ).each do |dep| 18 | depends dep 19 | end 20 | 21 | supports 'ubuntu', '>= 12.04' 22 | -------------------------------------------------------------------------------- /templates/default/sv-prime-httpd-run.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd <%= node[:valhalla][:base_dir] %> 4 | LISTEN=$(jq -r ".httpd.service.listen" <%= node[:valhalla][:config] %>) 5 | PROXY=$(jq -r ".<%= @options[:first_layer] %>.service.proxy" <%= node[:valhalla][:config] %>)_in 6 | LOOPBACK=$(jq -r ".httpd.service.loopback" <%= node[:valhalla][:config] %>) 7 | INTERRUPT=$(jq -r ".httpd.service.interrupt" <%= node[:valhalla][:config] %>) 8 | exec 2>&1 9 | exec chpst -u <%= node[:valhalla][:user][:name] %> -e /etc/sv/prime-httpd/env prime_httpd $LISTEN $PROXY $LOOPBACK $INTERRUPT 10 | -------------------------------------------------------------------------------- /templates/default/transit_dev_routes.tmpl.erb: -------------------------------------------------------------------------------- 1 | -j '{"locations":[{"lat":40.744273,"lon":-73.990328,"name":"Mapzen"},{"lat":40.704651,"lon":-73.9361,"name":"CartoDB"}],"costing":"multimodal","directions_options":{"units":"miles"},"costing_options":{"transit":{"use_bus":"0.5","use_rail":"0.6","use_transfers":"0.4"}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 2 | -j '{"locations":[{"lat":40.70523754289766,"lon":-74.01114464155398},{"lat":40.63974890854884,"lon":-74.08361435635015}],"costing":"multimodal","directions_options":{"units":"miles"},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 3 | -------------------------------------------------------------------------------- /templates/default/health_check.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "${2}" ]; then 4 | echo "Path and request are required" 5 | exit 1 6 | fi 7 | 8 | #keep checking while we havent run out of time 9 | start=$(date +%s) 10 | while [ $[$(date +%s) - $start] -lt <%= node[:valhalla][:health_check_timeout] %> ]; do 11 | curl localhost:<%= node[:valhalla][:httpd][:port] %>/$1 -s --max-time 1 --fail --data "${2}" 12 | if [ $? -eq 0 ]; then 13 | exit 0 14 | fi 15 | sleep 5 16 | done 17 | 18 | #never got a decent response try one last time 19 | curl localhost:<%= node[:valhalla][:httpd][:port] %>/$1 -s --max-time 1 --fail --data "${2}" 20 | exit $? 21 | -------------------------------------------------------------------------------- /recipes/get_elevation_tiles.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: get_elevation_tiles 5 | # 6 | 7 | include_recipe 'runit::default' 8 | 9 | # stop everything from running 10 | stop_service do 11 | notifies :run, 'execute[sync tiles]', :immediately 12 | end 13 | 14 | # get them from s3 15 | execute 'sync tiles' do 16 | action :run 17 | user node[:valhalla][:user][:name] 18 | cwd node[:valhalla][:src_dir] 19 | command "valhalla_build_elevation -180 180 -90 90 #{node[:valhalla][:elevation_dir]} $(($(nproc)*2)) 2>&1 >> #{node[:valhalla][:log_dir]}/elevation_tiles.log" 20 | retries 3 21 | timeout 32_000 22 | end 23 | 24 | # turn everything back on 25 | start_service do 26 | end 27 | -------------------------------------------------------------------------------- /definitions/stop_service.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Definition:: stop_service 5 | # 6 | 7 | define :stop_service do 8 | # httpd 9 | runit_service 'prime-httpd' do 10 | action :stop 11 | only_if "test -h #{node[:runit][:service_dir]}/prime-httpd" 12 | end 13 | 14 | # cake layers 15 | %w(skadi loki thor odin).each do |layer| 16 | # proxy 17 | runit_service "proxyd-#{layer}" do 18 | action :stop 19 | only_if "test -h #{node[:runit][:service_dir]}/proxyd-#{layer}" 20 | end 21 | 22 | # workers 23 | (0..(node[:valhalla][:workers][:count] - 1)).step(1).each do |num| 24 | runit_service "workerd-#{layer}-#{num}" do 25 | action :stop 26 | only_if "test -h #{node[:runit][:service_dir]}/workerd-#{layer}-#{num}" 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /templates/default/run.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Default config 4 | DEFAULT_CONFIG="../../conf/valhalla.json" 5 | 6 | function usage() { 7 | echo "Usage: $0 path_test_request_file [conf=../../conf/valhalla.json]" 8 | echo "Example: $0 requests/demo_routes.txt" 9 | echo "Example: $0 requests/demo_routes.txt ~/valhalla.json" 10 | exit 1 11 | } 12 | 13 | #set the input file and verify 14 | if [ -z "${1}" ]; then 15 | echo "Missing path_test_request_file" 16 | usage 17 | elif [ ! -f "${1}" ]; then 18 | echo "Invalid path_test_request_file: ${1}" 19 | usage 20 | else 21 | INPUT="${1}" 22 | fi 23 | 24 | #set config variable to second argument or default 25 | CONF=${2:-${DEFAULT_CONFIG}} 26 | 27 | # verify config file exists 28 | if [ ! -f "${CONF}" ]; then 29 | echo "Invalid config file: ${CONF}" 30 | usage 31 | fi 32 | 33 | PATH=../:$PATH ./batch.sh ${INPUT} ${CONF} 34 | 35 | -------------------------------------------------------------------------------- /recipes/get_routing_tiles.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: get_routing_tiles 5 | # 6 | 7 | # stop everything from running, while we get new tiles 8 | include_recipe 'runit::default' 9 | stop_service do 10 | notifies :run, 'execute[sync tiles]', :immediately 11 | end 12 | 13 | # get them from s3 14 | execute 'sync tiles' do 15 | action :run 16 | user node[:valhalla][:user][:name] 17 | cwd node[:valhalla][:base_dir] 18 | command <<-EOH 19 | echo -n s3://#{node[:valhalla][:bucket]}/#{node[:valhalla][:bucket_dir]}/ > latest_tiles.txt && 20 | aws --region us-east-1 s3 ls $(cat latest_tiles.txt) | grep -F planet_ | awk '{print $4}' | sort | tail -n 1 >> latest_tiles.txt && 21 | aws --region us-east-1 s3 cp $(cat latest_tiles.txt) planet.tar 22 | EOH 23 | end 24 | 25 | # turn everything back on 26 | start_service do 27 | end 28 | -------------------------------------------------------------------------------- /recipes/install_from_ppa.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: install_from_ppa 5 | # 6 | 7 | # remove previous software 8 | execute 'package remove' do 9 | action :run 10 | command '(apt-get purge -y libvalhalla* valhalla* || true) && rm -rf /usr/local/lib/libvalhalla* /usr/local/include/valhalla /usr/local/bin/valhalla*' 11 | end 12 | 13 | # update the repository 14 | execute 'ppa update' do 15 | action :run 16 | command 'apt-get update' 17 | end 18 | 19 | # install the packages 20 | execute 'package install versioned' do 21 | action :run 22 | command "d=$(echo #{node[:valhalla][:ppa_version]} | sed -e 's/^.\\+$/-/g') && \ 23 | apt-get install -y libvalhalla#{node[:valhalla][:ppa_version]}${d}0 \ 24 | libvalhalla#{node[:valhalla][:ppa_version]}-dev \ 25 | valhalla#{node[:valhalla][:ppa_version]}-bin" 26 | end 27 | 28 | # restart the services if they are present 29 | include_recipe 'runit::default' 30 | stop_service do 31 | end 32 | start_service do 33 | end 34 | -------------------------------------------------------------------------------- /recipes/install_from_source.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: install_from_source 5 | # 6 | 7 | # remove previous software 8 | execute 'package remove' do 9 | action :run 10 | command '(apt-get purge -y libvalhalla* valhalla* || true) && rm -rf /usr/local/lib/libvalhalla* /usr/local/include/valhalla /usr/local/bin/valhalla*' 11 | end 12 | 13 | # clone software 14 | execute 'clone libvalhalla' do 15 | action :run 16 | command "rm -rf valhalla && git clone --depth=1 --recurse-submodules --single-branch \ 17 | --branch=#{node[:valhalla][:github][:revision]} \ 18 | #{node[:valhalla][:github][:base]}/valhalla.git" 19 | cwd node[:valhalla][:src_dir] 20 | 21 | notifies :run, 'execute[install libvalhalla]', :immediately 22 | end 23 | 24 | # install 25 | execute 'install libvalhalla' do 26 | action :nothing 27 | command './autogen.sh && ./configure && make -j$(nproc) && make install' 28 | cwd "#{node[:valhalla][:src_dir]}/valhalla" 29 | end 30 | 31 | # restart the services if they are present 32 | include_recipe 'runit::default' 33 | stop_service do 34 | end 35 | start_service do 36 | end 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Valhalla contributors 4 | Copyright (c) 2015-2017 Mapillary AB, Mapzen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /templates/default/test_tiles.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "<%= node[:valhalla][:with_testing] %>" != true ]; then 4 | exit 0 5 | fi 6 | 7 | pwd_dir=`pwd` 8 | cd <%= node[:valhalla][:test_dir] %> 9 | 10 | #fill out the template with a date relative to now 11 | sed "s/DATE_TIME_TAG/`date --date='08:00 next Tue' +%Y-%m-%dT%H:%M`/g" <%= node[:valhalla][:test_requests] %>/<%= node[:valhalla][:transit_test_file] %> > <%= node[:valhalla][:test_requests] %>/transit_routes.txt 12 | route_count=$(wc -l <%= node[:valhalla][:test_requests] %>/<%= node[:valhalla][:transit_test_file] %> | awk '{print $1}') 13 | #RAD those tests 14 | ./run.sh <%= node[:valhalla][:test_requests] %>/transit_routes.txt <%= node[:valhalla][:config] %> 15 | #check whats going on 16 | succeed_count=`grep -ic success <%= node[:valhalla][:test_results] %>/*_transit_routes/statistics.csv` 17 | #this doesnt look good 18 | if [[ ${succeed_count} != ${route_count} ]]; then 19 | cat - <%= node[:valhalla][:test_results] %>/*_transit_routes/statistics.csv << EOF | /usr/sbin/sendmail -t 20 | to:<%= node[:valhalla][:to_email] %> 21 | from:<%= node[:valhalla][:from_email] %> 22 | subject:Tile Tests FAILED! 23 | 24 | EOF 25 | rm -rf <%= node[:valhalla][:test_results] %> 26 | exit 1 27 | fi 28 | rm -rf <%= node[:valhalla][:test_results] %> 29 | cd $pwd_dir 30 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # or sharing to the community site. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | Icon? 9 | nohup.out 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # SASS # 14 | ######## 15 | .sass-cache 16 | 17 | # EDITORS # 18 | ########### 19 | \#* 20 | .#* 21 | *~ 22 | *.sw[a-z] 23 | *.bak 24 | REVISION 25 | TAGS* 26 | tmtags 27 | *_flymake.* 28 | *_flymake 29 | *.tmproj 30 | .project 31 | .settings 32 | mkmf.log 33 | 34 | ## COMPILED ## 35 | ############## 36 | a.out 37 | *.o 38 | *.pyc 39 | *.so 40 | *.com 41 | *.class 42 | *.dll 43 | *.exe 44 | */rdoc/ 45 | 46 | # Testing # 47 | ########### 48 | .watchr 49 | .rspec 50 | spec/* 51 | spec/fixtures/* 52 | test/* 53 | features/* 54 | Guardfile 55 | Procfile 56 | 57 | # SCM # 58 | ####### 59 | .git 60 | */.git 61 | .gitignore 62 | .gitmodules 63 | .gitconfig 64 | .gitattributes 65 | .svn 66 | */.bzr/* 67 | */.hg/* 68 | */.svn/* 69 | 70 | # Berkshelf # 71 | ############# 72 | Berksfile 73 | Berksfile.lock 74 | cookbooks/* 75 | tmp 76 | 77 | # Cookbooks # 78 | ############# 79 | CONTRIBUTING 80 | CHANGELOG* 81 | 82 | # Strainer # 83 | ############ 84 | Colanderfile 85 | Strainerfile 86 | .colander 87 | .strainer 88 | 89 | # Vagrant # 90 | ########### 91 | .vagrant 92 | Vagrantfile 93 | 94 | # Travis # 95 | ########## 96 | .travis.yml 97 | -------------------------------------------------------------------------------- /recipes/elevation_service.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: elevation_service 5 | # 6 | 7 | include_recipe 'runit::default' 8 | 9 | # httpd 10 | runit_service 'prime-httpd' do 11 | action :enable 12 | log true 13 | default_logger true 14 | sv_timeout 60 15 | retries 3 16 | options(first_layer: 'skadi') 17 | env( 18 | 'LD_LIBRARY_PATH' => '/usr/lib:/usr/local/lib' 19 | ) 20 | end 21 | 22 | # cake layers 23 | %w(skadi).each do |layer| 24 | # proxy 25 | runit_service "proxyd-#{layer}" do 26 | action :enable 27 | log true 28 | default_logger true 29 | run_template_name 'proxyd-global' 30 | sv_timeout 60 31 | retries 3 32 | options(layer: layer) 33 | env('LD_LIBRARY_PATH' => '/usr/lib:/usr/local/lib') 34 | end 35 | 36 | # workers 37 | (0..(node[:valhalla][:workers][:count] - 1)).step(1).each do |num| 38 | runit_service "workerd-#{layer}-#{num}" do 39 | action :enable 40 | log true 41 | default_logger true 42 | run_template_name 'workerd-global' 43 | sv_timeout 60 44 | retries 3 45 | only_if { node[:valhalla][:workers][:count] > 0 } 46 | options(layer: layer, num: num) 47 | env('LD_LIBRARY_PATH' => '/usr/lib:/usr/local/lib') 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /recipes/cut_tiles.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: cut_tiles 5 | # 6 | 7 | # cut tiles as a one-off 8 | execute 'cut tiles' do 9 | user node[:valhalla][:user][:name] 10 | cwd node[:valhalla][:base_dir] 11 | command <<-EOH 12 | #{node[:valhalla][:conf_dir]}/cut_tiles.sh >>#{node[:valhalla][:log_dir]}/cut_tiles.log 2>&1 13 | EOH 14 | only_if { node[:valhalla][:with_updates] == false } 15 | end 16 | 17 | # or install crontab to cut tiles all the time 18 | cron 'cut tiles' do 19 | user node[:valhalla][:user][:name] 20 | minute '*/5' 21 | command <<-EOH 22 | cd #{node[:valhalla][:base_dir]} && #{node[:valhalla][:conf_dir]}/cut_tiles.sh >> #{node[:valhalla][:log_dir]}/cut_tiles.log 2>&1 23 | EOH 24 | only_if { node[:valhalla][:with_updates] == true } 25 | end 26 | 27 | # make sure to keep the data up to date if we are constantly cutting tiles 28 | node[:valhalla][:extracts].each do |extract| 29 | filename = extract.split('/').last 30 | cron "apply changeset #{filename}" do 31 | user node[:valhalla][:user][:name] 32 | minute '*/5' 33 | command <<-EOH 34 | cd #{node[:valhalla][:base_dir]} && #{node[:valhalla][:conf_dir]}/minutely_update.sh update #{node[:valhalla][:extracts_dir]} #{filename} >> #{node[:valhalla][:log_dir]}/minutely_update_#{filename}.log 2>&1 35 | EOH 36 | only_if { node[:valhalla][:with_updates] == true } 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /recipes/routing_service.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: routing_service 5 | # 6 | 7 | include_recipe 'runit::default' 8 | 9 | # httpd 10 | runit_service 'prime-httpd' do 11 | action :enable 12 | log true 13 | default_logger true 14 | sv_timeout 60 15 | retries 3 16 | options(first_layer: 'loki') 17 | env( 18 | 'LD_LIBRARY_PATH' => '/usr/lib:/usr/local/lib' 19 | ) 20 | end 21 | 22 | # cake layers 23 | %w(loki thor odin).each do |layer| 24 | # proxy 25 | runit_service "proxyd-#{layer}" do 26 | action :enable 27 | log true 28 | default_logger true 29 | run_template_name 'proxyd-global' 30 | sv_timeout 60 31 | retries 3 32 | options(layer: layer) 33 | env('LD_LIBRARY_PATH' => '/usr/lib:/usr/local/lib') 34 | end 35 | 36 | # workers 37 | (0..(node[:valhalla][:workers][:count] - 1)).step(1).each do |num| 38 | runit_service "workerd-#{layer}-#{num}" do 39 | action :enable 40 | log true 41 | default_logger true 42 | run_template_name 'workerd-global' 43 | sv_timeout 60 44 | retries 3 45 | only_if { node[:valhalla][:workers][:count] > 0 } 46 | options(layer: layer, num: num) 47 | env('LD_LIBRARY_PATH' => '/usr/lib:/usr/local/lib') 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /definitions/start_service.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Definition:: start_service 5 | # 6 | 7 | define :start_service do 8 | # httpd 9 | runit_service 'prime-httpd' do 10 | action :start 11 | only_if "test -h #{node[:runit][:service_dir]}/prime-httpd" 12 | end 13 | 14 | # cake layers 15 | %w(skadi loki thor odin).each do |layer| 16 | # proxy 17 | runit_service "proxyd-#{layer}" do 18 | action :start 19 | only_if "test -h #{node[:runit][:service_dir]}/proxyd-#{layer}" 20 | end 21 | 22 | # workers 23 | (0..(node[:valhalla][:workers][:count] - 1)).step(1).each do |num| 24 | runit_service "workerd-#{layer}-#{num}" do 25 | action :start 26 | only_if "test -h #{node[:runit][:service_dir]}/workerd-#{layer}-#{num}" 27 | end 28 | end 29 | end 30 | 31 | # make sure everything is working in routing by issuing a request 32 | execute 'test routing service' do 33 | action :run 34 | user node[:valhalla][:user][:name] 35 | command "#{node[:valhalla][:conf_dir]}/health_check.sh '#{node[:valhalla][:health_check][:route_action]}' '#{node[:valhalla][:health_check][:route_request]}'" 36 | only_if "test -h #{node[:runit][:service_dir]}/proxyd-loki" 37 | end 38 | 39 | # make sure everything is working in elevation by issuing a request 40 | execute 'test elevation service' do 41 | action :run 42 | user node[:valhalla][:user][:name] 43 | command "#{node[:valhalla][:conf_dir]}/health_check.sh 'height' '{\"shape\":[{\"lat\":40.712431, \"lon\":-76.504916}]}'" 44 | only_if "test -h #{node[:runit][:service_dir]}/proxyd-skadi" 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /recipes/get_osm_data.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: get_osm_data 5 | # 6 | 7 | # for each extract 8 | node[:valhalla][:extracts].each do |url| 9 | # for the sake of brevity 10 | file = url.split('/').last 11 | 12 | # get the checksum for the data 13 | remote_file "#{node[:valhalla][:extracts_dir]}/#{file}.md5" do 14 | action :create 15 | backup false 16 | source "#{url}.md5" 17 | mode 0644 18 | not_if { File.exist?("#{node[:valhalla][:extracts_dir]}/#{file}") && node[:valhalla][:with_updates] } 19 | 20 | notifies :run, "execute[download #{url}]", :immediately 21 | notifies :run, "ruby_block[verify #{file}]", :immediately 22 | notifies :run, "execute[minutely_initialize #{file}]", :immediately 23 | end 24 | 25 | # get the actual data 26 | execute "download #{url}" do 27 | action :nothing 28 | command "wget --quiet -O #{node[:valhalla][:extracts_dir]}/#{file} #{url}" 29 | user node[:valhalla][:user][:name] 30 | timeout 10_800 31 | end 32 | 33 | # check the md5sum 34 | ruby_block "verify #{file}" do 35 | action :nothing 36 | block do 37 | require 'digest' 38 | file_md5 = Digest::MD5.file("#{node[:valhalla][:extracts_dir]}/#{file}").hexdigest 39 | md5 = File.read("#{node[:valhalla][:extracts_dir]}/#{file}.md5").split(' ').first 40 | if file_md5 != md5 41 | Chef::Log.info('Failure: the md5 of the data we downloaded does not appear to be correct. Aborting.') 42 | abort 43 | end 44 | end 45 | end 46 | 47 | # initialize the minutely updates 48 | execute "minutely_initialize #{file}" do 49 | action :nothing 50 | command "#{node[:valhalla][:conf_dir]}/minutely_update.sh initialize #{node[:valhalla][:extracts_dir]} #{file}" 51 | user node[:valhalla][:user][:name] 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure('2') do |config| 5 | config.vm.hostname = 'valhalla' 6 | config.vm.box = 'ubuntu-14.04' 7 | config.vm.box_url = 'https://oss-binaries.phusionpassenger.com/vagrant/boxes/latest/ubuntu-14.04-amd64-vbox.box' 8 | 9 | config.vm.provider 'virtualbox' do |v| 10 | host = RbConfig::CONFIG['host_os'] 11 | 12 | def memish(ram, mem_max) 13 | if ram > mem_max 14 | mem_max 15 | else 16 | ram 17 | end 18 | end 19 | 20 | mem_divisor = 2 21 | mem_min = 2048 22 | mem_max = 8192 23 | 24 | if host =~ /darwin/ 25 | cpu = ENV['VALHALLA_VAGRANT_CPUS'] || `sysctl -n hw.ncpu`.to_i 26 | ram = `sysctl -n hw.memsize`.to_i / 1024 / 1024 / mem_divisor 27 | mem = memish(ram, mem_max) 28 | elsif host =~ /linux/ 29 | cpu = ENV['VALHALLA_VAGRANT_CPUS'] || `nproc`.to_i 30 | ram = `grep 'MemTotal' /proc/meminfo | sed -e 's/MemTotal://' -e 's/ kB//'`.to_i / 1024 / mem_divisor 31 | mem = ENV['VALHALLA_VAGRANT_MB'] || memish(ram, mem_max) 32 | else 33 | cpu = ENV['VALHALLA_VAGRANT_CPUS'] || 2 34 | mem = ENV['VALHALLA_VAGRANT_MB'] || mem_min 35 | end 36 | 37 | v.cpus = ENV['VALHALLA_VAGRANT_CPUS'] || cpu 38 | v.memory = ENV['VALHALLA_VAGRANT_MB'] || mem 39 | end 40 | 41 | # forward 8002 42 | config.vm.network :forwarded_port, host: 8002, guest: 8002 43 | 44 | config.vm.network :private_network, ip: '192.168.33.10' 45 | config.berkshelf.berksfile_path = 'Berksfile' 46 | config.berkshelf.enabled = true 47 | 48 | # can run like: CHEF_RUN_LIST="recipe[valhalla::default],recipe[valhalla::serve]" vagrant provision 49 | config.vm.provision :chef_solo do |chef| 50 | chef.json = {} 51 | if ENV['CHEF_RUN_LIST'].nil? 52 | chef.run_list = ['recipe[valhalla::default]'] 53 | else 54 | chef.run_list = ENV['CHEF_RUN_LIST'].split(',') 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ██▒ █▓ ▄▄▄ ██▓ ██░ ██ ▄▄▄ ██▓ ██▓ ▄▄▄ 2 | ▓██░ █▒▒████▄ ▓██▒ ▓██░ ██▒▒████▄ ▓██▒ ▓██▒ ▒████▄ 3 | ▓██ █▒░▒██ ▀█▄ ▒██░ ▒██▀▀██░▒██ ▀█▄ ▒██░ ▒██░ ▒██ ▀█▄ 4 | ▒██ █░░░██▄▄▄▄██ ▒██░ ░▓█ ░██ ░██▄▄▄▄██ ▒██░ ▒██░ ░██▄▄▄▄██ 5 | ▒▀█░ ▓█ ▓██▒░██████▒░▓█▒░██▓ ▓█ ▓██▒░██████▒░██████▒▓█ ▓██▒ 6 | ░ ▐░ ▒▒ ▓▒█░░ ▒░▓ ░ ▒ ░░▒░▒ ▒▒ ▓▒█░░ ▒░▓ ░░ ▒░▓ ░▒▒ ▓▒█░ 7 | ░ ░░ ▒ ▒▒ ░░ ░ ▒ ░ ▒ ░▒░ ░ ▒ ▒▒ ░░ ░ ▒ ░░ ░ ▒ ░ ▒ ▒▒ ░ 8 | ░░ ░ ▒ ░ ░ ░ ░░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ 9 | ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ 10 | ░ 11 | 12 | Valhalla is an open source routing engine and accompanying libraries for use with Open Street Map and other open data sets. The chef-valhalla repository, as its name suggests, is a chef cookbook. The cookbook demonstrates how to deploy the valhalla stack to a virtual machine (sample vagrant file included). Upon completion the virtual machine will have cut a set of routable graph tiles and started up a server to handle route requests against that tile set. We hope this can serve as a primer on how one might deploy valhalla in one's own routing cluster. 13 | 14 | Deployment Types 15 | ---------------- 16 | 17 | Vagrant allows you to run a virtual machine as if it were a node in cluster of machines, perhaps in the cloud. We allow you to exercise several types of deployment simply by making use of different combinations of recipies within the cookbook. Currently we have the following deployment types: Global Tile Cutter, Routing Service (route/matrix/locate requests), Elevation Service (height requests), Extract Routing Service (like routing above but on static OSM extract). 18 | 19 | TODO: describe all of the recipes and the combinations needed to make the deployment types above, this info is laying around in a google doc some 20 | -------------------------------------------------------------------------------- /templates/default/batch.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | which parallel &> /dev/null 4 | if [ $? != 0 ]; then 5 | echo "parallel is required please install it" 6 | echo "sudo apt-get install parallel" 7 | exit 1 8 | fi 9 | 10 | function usage() { 11 | echo "Usage: $0 route_request_file conf [concurrency] [outDir]" 12 | echo "Example: $0 requests/demo_routes.txt" 13 | echo "Example: $0 requests/demo_routes.txt ~/valhalla.json" 14 | echo "Example: $0 requests/demo_routes.txt ~/valhalla.json 8" 15 | echo "Example: $0 requests/demo_routes.txt ~/valhalla.json 8 my_special_dir" 16 | exit 1 17 | } 18 | 19 | #set the input file 20 | if [ -z "${1}" ]; then 21 | usage 22 | elif [ ! -f "${1}" ]; then 23 | usage 24 | else 25 | INPUT="${1}" 26 | fi 27 | 28 | #set config file 29 | CONF="${2}" 30 | 31 | #how many threads do you want, default to max 32 | CONCURRENCY=$(nproc) 33 | if [ "${3}" ]; then 34 | CONCURRENCY="${3}" 35 | fi 36 | 37 | #where do you want the output, default to current time 38 | OUTDIR=$(date +%Y%m%d_%H%M%S)_$(basename "${INPUT%.*}") 39 | if [ "${4}" ]; then 40 | OUTDIR="${4}" 41 | fi 42 | RESULTS_OUTDIR="results/${OUTDIR}" 43 | mkdir -p "${RESULTS_OUTDIR}" 44 | 45 | #turn the nice input into something parallel can parse for args 46 | TMP="$(mktemp)" 47 | cp -rp "${INPUT}" "${TMP}" 48 | for arg in $(valhalla_run_route --help | grep -o '\-[a-z\-]\+' | sort | uniq); do 49 | sed -i -e "s/[ ]\?${arg}[ ]\+/|${arg}|/g" "${TMP}" 50 | done 51 | sed -i -e "s;$;|--config|${CONF};g" -e "s/\([^\\]\)'|/\1|/g" -e "s/|'/|/g" "${TMP}" 52 | 53 | #run all of the paths, make sure to cut off the timestamps 54 | #from the log messages otherwise every line will be a diff 55 | #TODO: add leading zeros to output files so they sort nicely 56 | echo -e "\x1b[32;1mWriting routes from ${INPUT} with a concurrency of ${CONCURRENCY} into ${OUTDIR}\x1b[0m" 57 | cat "${TMP}" | parallel --progress -k -C '\|' -P "${CONCURRENCY}" "valhalla_run_route {} 2>&1 | grep -F STATISTICS | sed -e 's/.* //g' > ${RESULTS_OUTDIR}/{#}_statistics.csv" 58 | rm -f "${TMP}" 59 | echo "orgLat, orgLng, destLat, destLng, result, #Passes, runtime, trip time, length, arcDistance, #Manuevers" > ${RESULTS_OUTDIR}/statistics.csv 60 | cat `ls -1v ${RESULTS_OUTDIR}/*_statistics.csv` >> ${RESULTS_OUTDIR}/statistics.csv 61 | -------------------------------------------------------------------------------- /templates/default/push_tiles.py.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import sys 4 | import time 5 | import boto 6 | 7 | #get all the instances of a layer and run some recipes on each 8 | def update_instances(elb_name, stack, layer, min_instances, recipes): 9 | #connect to opsworks 10 | opsworks = boto.connect_opsworks() 11 | 12 | #grab all the instances for that layer 13 | instances = opsworks.describe_instances(layer_id=layer) 14 | 15 | #TODO: make this more robust of a heuristic 16 | #TODO: sort by instance name so that we always try the same one first 17 | #if we dont have enough machines to feel safe we give up 18 | instances = [ i for i in instances['Instances'] if i.get('Status') == 'online' ] 19 | if len(instances) < min_instances: 20 | raise Exception('Not enough instances (%d < %d) in layer %s to risk the update' % (len(instances), min_instances, layer)) 21 | 22 | #connect to the elb and check which are in it 23 | elb = boto.connect_elb(elb_name) 24 | instances = [ i for i in instances if elb.describe_instance_health(elb_name, [i['Ec2InstanceId']])[0].state == 'InService' ] 25 | if len(instances) < min_instances: 26 | raise Exception('Not enough instances (%d < %d) in elb %s to risk the update' % (len(instances), min_instances, layer)) 27 | 28 | #for each one 29 | for instance in instances: 30 | #take this instance out of the elb 31 | registered = [ i.id for i in elb.deregister_instances(elb_name, [instance['Ec2InstanceId']]) ] 32 | if instance['Ec2InstanceId'] in registered: 33 | raise Exception('Instance was still registered with the elb') 34 | 35 | #check its status 36 | wait = 60 37 | while wait > 0: 38 | if elb.describe_instance_health(elb_name, [instance['Ec2InstanceId']])[0].state == 'OutOfService': 39 | break 40 | wait -= 5 41 | time.sleep(5) 42 | if wait < 1: 43 | elb.register_instances(elb_name, [instance['Ec2InstanceId']]) 44 | raise Exception('Deregistered instance was not OutOfService') 45 | 46 | #deployment task of running a recipe 47 | deployment_id = opsworks.create_deployment(stack, {'Name': 'execute_recipes', 'Args': {'recipes': recipes}}, instance_ids=[instance['InstanceId']])['DeploymentId'] 48 | #wait until it ends 49 | while True: 50 | if opsworks.describe_commands(deployment_id=deployment_id)['Commands'][0]['Status'] != 'pending': 51 | break 52 | time.sleep(5) 53 | #check that its status is all good 54 | if opsworks.describe_commands(deployment_id=deployment_id)['Commands'][0]['Status'] != 'successful': 55 | elb.register_instances(elb_name, [instance['Ec2InstanceId']]) 56 | print('Deployment %s to Instance %s failed' % (deployment_id, instance['InstanceId']), file=sys.stderr) 57 | sys.exit(1) 58 | 59 | #put the instance back in the elb 60 | registered = [ i.id for i in elb.register_instances(elb_name, [instance['Ec2InstanceId']]) ] 61 | if instance['Ec2InstanceId'] not in registered: 62 | raise Exception('Instance was not registered with the elb') 63 | 64 | #check its status 65 | wait = 60 66 | while wait > 0: 67 | if elb.describe_instance_health(elb_name, [instance['Ec2InstanceId']])[0].state == 'InService': 68 | break 69 | wait -= 5 70 | time.sleep(5) 71 | if wait < 1: 72 | raise Exception('Re-registered instance was not InService') 73 | 74 | 75 | #entry point for script 76 | if __name__ == "__main__": 77 | #update the service instances 78 | elbs = '<%= node[:valhalla][:routing_service_elbs] %>'.split(',') 79 | layers = '<%= node[:valhalla][:routing_service_layers] %>'.split(',') 80 | min_layers_instances = '<%= node[:valhalla][:min_layers_instances] %>'.split(',') 81 | recipes = '<%= node[:valhalla][:routing_service_recipes] %>'.split(',') 82 | for elb, layer, min_instances in zip(elbs, layers, min_layers_instances): 83 | update_instances(elb, '<%= node[:valhalla][:routing_service_stack] %>', layer, int(min_instances), recipes) 84 | -------------------------------------------------------------------------------- /templates/default/minutely_update.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #This script is based on the script located on github: 3 | #https://github.com/artemp/MapQuest-Render-Stack/blob/master/scripts/minutely_update.sh 4 | BASE_DIR="" 5 | WORKDIR_OSM="" 6 | CHANGESET_DIR="" 7 | OSMOSIS="/usr/bin/osmosis" 8 | 9 | export JAVACMD_OPTIONS="-Djava.io.tmpdir=<%= node[:valhalla][:temp_dir] %>" 10 | 11 | # check they're all present 12 | if [ ! -e $OSMOSIS ]; then 13 | echo "osmosis ($OSMOSIS) not installed, but is required." 14 | exit 1 15 | fi 16 | 17 | osmosis_fetch_changeset() { 18 | if [ ! -e $WORKDIR_OSM/state.txt ]; then 19 | echo "Osmosis state file not found - has the state been correctly initialized?" 20 | exit 1 21 | fi 22 | STATE_TIMESTAMP=$(grep '^timestamp=' $WORKDIR_OSM/state.txt | tail -n1 | cut -c 11-) 23 | CURRENT_TIMESTAMP=$(date -u "+%Y-%m-%d_%H:%M:%S") 24 | CHANGESET_FILE=$CHANGESET_DIR/changeset-$STATE_TIMESTAMP.ocs.gz 25 | 26 | echo "$CURRENT_TIMESTAMP:Downloading changeset $STATE_TIMESTAMP" 27 | cp $WORKDIR_OSM/state.txt $CHANGESET_DIR/state-$STATE_TIMESTAMP 28 | $OSMOSIS --read-replication-interval workingDirectory=$WORKDIR_OSM \ 29 | --simplify-change --write-xml-change $CHANGESET_FILE 30 | } 31 | 32 | osmosis_cleanup() { 33 | rm -f $CHANGESET_DIR/changeset-$STATE_TIMESTAMP.ocs.gz 34 | rm -f $CHANGESET_DIR/state-$STATE_TIMESTAMP 35 | } 36 | 37 | update() { 38 | 39 | osmosis_fetch_changeset 40 | 41 | PLANET=$BASE_DIR/$1 42 | 43 | $OSMOSIS --rxc $CHANGESET_FILE --rb $PLANET --ac --wb $PLANET.new 44 | 45 | # exit if osmosis fails 46 | if [ $? -ne 0 ] 47 | then 48 | echo "failed to apply $CHANGESET_DIR/changeset-$STATE_TIMESTAMP.ocs.gz" 49 | cp $CHANGESET_DIR/state-$STATE_TIMESTAMP $WORKDIR_OSM/state.txt 50 | exit 1 51 | else 52 | echo "applied $CHANGESET_DIR/changeset-$STATE_TIMESTAMP.ocs.gz" 53 | echo "Done" 54 | fi 55 | 56 | mv $PLANET.new $PLANET 57 | 58 | echo "running osmconvert $PLANET --out-statistics." 59 | osmconvert $PLANET --out-statistics > $WORKDIR_OSM/current_stats.txt 60 | 61 | osmosis_cleanup 62 | } 63 | 64 | initialize() { 65 | 66 | # make the directories 67 | mkdir -p $WORKDIR_OSM $CHANGESET_DIR 68 | 69 | if [ -e $WORKDIR_OSM/state.txt ]; then 70 | echo "Osmosis state file found - has this already been initialized?" 71 | exit 0 72 | fi 73 | PLANET=$BASE_DIR/$1 74 | if [ ! -e $PLANET ]; then 75 | echo "Planet file not found - cannot initialize without this." 76 | exit 1 77 | fi 78 | 79 | $OSMOSIS --read-replication-interval-init workingDirectory=$WORKDIR_OSM 80 | 81 | baseUrl=https://planet.openstreetmap.org/replication/minute 82 | replacement="s@^\(baseUrl\s*=\s*\).*\$@\1${baseUrl}@" 83 | sed -i $replacement $WORKDIR_OSM/configuration.txt 84 | sed -i "s/^\(maxInterval\s*=\s*\).*\$/\10/" $WORKDIR_OSM/configuration.txt 85 | 86 | echo "obtaining planet timestamp." 87 | planet_timestamp=$(osmconvert $PLANET --out-timestamp) 88 | 89 | #https://github.com/MaZderMind/replicate-sequences 90 | wget "http://osm.personalwerk.de/replicate-sequences/?$planet_timestamp" -O $WORKDIR_OSM/state.txt; 91 | } 92 | 93 | # directories used by the update process 94 | BASE_DIR=$2 95 | WORKDIR_OSM="$BASE_DIR/osmosis_work_dir" 96 | WORKDIR_OSM=$WORKDIR_OSM.$3 97 | CHANGESET_DIR="$WORKDIR_OSM/minutely" 98 | 99 | # make sure only one is running at any time... 100 | LOCK_FILE="<%= node[:valhalla][:lock_dir] %>/minutely.lock" 101 | (set -C; : > $LOCK_FILE) 2> /dev/null 102 | if [ $? != "0" ]; then 103 | echo "Lock file exists" 104 | exit 1 105 | fi 106 | trap 'rm $LOCK_FILE' EXIT 1 2 3 6 107 | 108 | case "$1" in 109 | update) 110 | update $3;; 111 | 112 | initialize) 113 | initialize $3;; 114 | 115 | *) 116 | echo "Usage: $0 {update|initialize pbf_directory pbf_file}" 117 | echo 118 | echo " update: Update the database using Osmosis and minutely diffs." 119 | echo 120 | echo " initialize: Set up osmosis replication - relies on the location of the" 121 | echo " planet file from the import step." 122 | echo 123 | exit 1;; 124 | esac 125 | 126 | 127 | -------------------------------------------------------------------------------- /recipes/setup.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Recipe:: setup 5 | # 6 | 7 | # make the valhalla user 8 | user_account node[:valhalla][:user][:name] do 9 | manage_home true 10 | create_group true 11 | ssh_keygen false 12 | home node[:valhalla][:user][:home] 13 | not_if { node[:valhalla][:user][:name] == 'root' } 14 | end 15 | 16 | # make a few places to work in 17 | [ 18 | node[:valhalla][:base_dir], 19 | node[:valhalla][:tile_dir], 20 | node[:valhalla][:log_dir], 21 | node[:valhalla][:conf_dir], 22 | node[:valhalla][:src_dir], 23 | node[:valhalla][:lock_dir], 24 | node[:valhalla][:extracts_dir], 25 | node[:valhalla][:temp_dir], 26 | node[:valhalla][:elevation_dir], 27 | node[:valhalla][:test_dir], 28 | node[:valhalla][:test_requests], 29 | node[:valhalla][:test_results] 30 | ].each do |dir| 31 | directory dir do 32 | action :create 33 | recursive true 34 | mode 0755 35 | owner node[:valhalla][:user][:name] 36 | end 37 | end 38 | 39 | # move the valhalla config file into place 40 | conf_file = File.basename(node[:valhalla][:config]) 41 | template node[:valhalla][:config] do 42 | source "#{conf_file}.erb" 43 | mode 0644 44 | owner node[:valhalla][:user][:name] 45 | end 46 | 47 | # move the maproulette config file into place 48 | conf_file = File.basename(node[:maproulette][:config]) 49 | template node[:maproulette][:config] do 50 | source "#{conf_file}.erb" 51 | mode 0644 52 | owner node[:valhalla][:user][:name] 53 | end 54 | 55 | # install all of the scripts for data motion 56 | %w(cut_tiles.sh minutely_update.sh push_tiles.py health_check.sh map_roulette.py).each do |script| 57 | template "#{node[:valhalla][:conf_dir]}/#{script}" do 58 | source "#{script}.erb" 59 | mode 0755 60 | owner node[:valhalla][:user][:name] 61 | end 62 | end 63 | 64 | # install all of the scripts for testing 65 | %w(batch.sh run.sh test_tiles.sh).each do |script| 66 | template "#{node[:valhalla][:test_dir]}/#{script}" do 67 | source "#{script}.erb" 68 | mode 0755 69 | owner node[:valhalla][:user][:name] 70 | end 71 | end 72 | 73 | # install all of the test requests 74 | %w(transit_dev_routes.tmpl transit_prod_routes.tmpl).each do |script| 75 | template "#{node[:valhalla][:test_requests]}/#{script}" do 76 | source "#{script}.erb" 77 | mode 0755 78 | owner node[:valhalla][:user][:name] 79 | end 80 | end 81 | 82 | # a few things from ppa 83 | execute 'add ppa' do 84 | action :run 85 | command 'apt-add-repository -y ppa:valhalla-core/valhalla' 86 | end 87 | 88 | # remove previous software 89 | execute 'package purge' do 90 | action :run 91 | command '(apt-get purge -y libprime-serv* prime-serv* || true) && apt-get update -y' 92 | end 93 | 94 | # need a few more deps 95 | %w( 96 | software-properties-common 97 | git 98 | pigz 99 | python-pip 100 | jq 101 | parallel 102 | sendmail 103 | osmosis 104 | osmctools 105 | autoconf 106 | automake 107 | spatialite-bin 108 | autotools-dev 109 | pkg-config 110 | vim-common 111 | locales 112 | libboost1.54-all-dev 113 | libcurl4-openssl-dev 114 | libgeos-dev 115 | libgeos++-dev 116 | lua5.2 117 | liblua5.2-dev 118 | libprime-server0.6.3-dev 119 | libprotobuf-dev 120 | libspatialite-dev 121 | libsqlite3-dev 122 | protobuf-compiler 123 | libboost-filesystem1.54.0 124 | libboost-regex1.54.0 125 | libboost-system1.54.0 126 | libboost-thread1.54.0 127 | liblua5.2-0 128 | libprime-server0.6.3-0 129 | libspatialite5 130 | libspatialite-dev 131 | libsqlite3-0 132 | libprotobuf8 133 | libcurl3 134 | libgeos-3.4.2 135 | libgeos-c1 136 | prime-server0.6.3-bin 137 | ).each do |p| 138 | package p do 139 | options '--force-yes' 140 | action :install 141 | end 142 | end 143 | 144 | # need some python deps 145 | %w( 146 | boto 147 | filechunkio 148 | awscli==1.6.9 149 | requests 150 | ).each do |p| 151 | execute p do 152 | action :run 153 | command "pip install #{p}" 154 | end 155 | end 156 | 157 | # logrotate 158 | template '/etc/logrotate.d/valhalla' do 159 | source 'logrotate.erb' 160 | owner 'root' 161 | group 'root' 162 | mode 0644 163 | end 164 | -------------------------------------------------------------------------------- /templates/default/cut_tiles.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | function mv_stamp() { 3 | local b=$(basename ${1}) 4 | mv ${1} ${b%.*}_${2}.${b##*.} 5 | } 6 | 7 | function cp_stamp() { 8 | local b=$(basename ${1}) 9 | cp -rp ${1} ${b%.*}_${2}.${b##*.} 10 | } 11 | 12 | function clean_s3() { 13 | cutoff=$(date -d "-${2} days" +%s) 14 | aws s3 ls ${1} | tail -n +2 | while read record; do 15 | added=$(date -d "$(echo ${record} | awk '{print $1" "$2}')" +%s) 16 | if [[ ${added} -lt ${cutoff} ]]; then 17 | aws s3 rm ${1}$(echo ${record} | awk '{print $4}') 18 | fi 19 | done 20 | } 21 | 22 | function get_latest_transit() { 23 | file=$(aws s3 ls ${1}transit_ | sort | tail -1) 24 | file_name=$(echo ${file} | awk '{print $4}') 25 | latest_upload=${1}${file_name} 26 | 27 | #use the latest...if not already 28 | if [ ! -f <%= node[:valhalla][:base_dir] %>/${file_name} ]; then 29 | # rm old tarball 30 | rm -f <%= node[:valhalla][:base_dir] %>/transit_*.tgz 31 | aws s3 cp $latest_upload <%= node[:valhalla][:base_dir] %>/${file_name} 32 | # remove old data 33 | rm -rf <%= node[:valhalla][:transit_dir] %> 34 | mkdir <%= node[:valhalla][:transit_dir] %> 35 | tar pxf <%= node[:valhalla][:base_dir] %>/${file_name} -C <%= node[:valhalla][:transit_dir] %> 36 | fi 37 | } 38 | 39 | function fail() { 40 | if [[ $1 -ne 0 ]]; then 41 | if [[ -e <%= node[:valhalla][:lock_dir] %>/cut_transit_tiles.lock ]]; then 42 | echo "Killed by transit tile fetching" 43 | else 44 | /usr/sbin/sendmail -t - << EOF 45 | to:<%= node[:valhalla][:to_email] %> 46 | from:<%= node[:valhalla][:from_email] %> 47 | subject:Cut tiles failed! 48 | 49 | $(tail -n 100 <%= node[:valhalla][:log_dir] %>/cut_tiles.log) 50 | EOF 51 | fi 52 | fi 53 | } 54 | 55 | # make sure only one is running at any time... 56 | LOCK_FILE="<%= node[:valhalla][:lock_dir] %>/cut_tiles.lock" 57 | (set -C; : > ${LOCK_FILE}) 2> /dev/null 58 | if [ $? != "0" ]; then 59 | echo "Lock file exists" 60 | exit 0 61 | fi 62 | trap 'fail $? && rm $LOCK_FILE' EXIT 1 2 3 63 | set -e 64 | 65 | export PATH=$PATH:/usr/sbin:/usr/local/bin 66 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 67 | 68 | # name the dir where this will go 69 | stamp=$(date +%Y_%m_%d-%H_%M_%S) 70 | 71 | # things we need to make if we dont have them 72 | extracts=$(find <%= node[:valhalla][:extracts_dir] %> -type f -name "*.pbf") 73 | admin_file=$(jq -r '.mjolnir.admin' <%= node[:valhalla][:config] %>) 74 | timezone_file=$(jq -r '.mjolnir.timezone' <%= node[:valhalla][:config] %>) 75 | if [ ! -e $admin_file ]; then 76 | valhalla_build_admins -c <%= node[:valhalla][:config] %> $(find <%= node[:valhalla][:extracts_dir] %> -type f -name "*.pbf") 77 | fi 78 | if [ ! -e $timezone_file ]; then 79 | valhalla_build_timezones <%= node[:valhalla][:config] %> 80 | fi 81 | 82 | #transit data 83 | get_latest_transit s3://<%= node[:valhalla][:bucket] %>/<%= node[:valhalla][:bucket_dir] %>/ 84 | 85 | # cut tiles from the data 86 | valhalla_build_tiles -c <%= node[:valhalla][:config] %> $(find <%= node[:valhalla][:extracts_dir] %> -type f -name "*.pbf") 87 | rm -rf *.bin 88 | 89 | # package up the extra stuff 90 | set +e 91 | 92 | # see if these tiles are any good 93 | <%= node[:valhalla][:test_dir] %>/test_tiles.sh 94 | if [ $? -ne 0 ]; then 95 | exit 0 96 | fi 97 | 98 | tile_dir=<%= node[:valhalla][:tile_dir] %> 99 | cur_extras_dir=<%= node[:valhalla][:base_dir] %>/extras_${stamp} 100 | mkdir -p ${cur_extras_dir} 101 | pushd ${cur_extras_dir} 102 | valhalla_build_connectivity -c <%= node[:valhalla][:config] %> 103 | valhalla_build_statistics -c <%= node[:valhalla][:config] %> 104 | valhalla_export_edges --config <%= node[:valhalla][:config] %> > edges_${stamp}.0sv 105 | # do we want to run map roulette tool 106 | if [ "<%= node[:valhalla][:with_map_roulette] %>" == true ]; then 107 | <%= node[:valhalla][:conf_dir] %>/map_roulette.py -c <%= node[:maproulette][:config] %> -i maproulette_tasks.geojson 108 | fi 109 | for f in connectivity*; do mv_stamp $f ${stamp}; done 110 | mv_stamp statistics.sqlite ${stamp} 111 | mv_stamp maproulette_tasks.geojson ${stamp} 112 | cp_stamp ${tile_dir}/$(basename ${admin_file}) ${stamp} 113 | cp_stamp ${tile_dir}/$(basename ${timezone_file}) ${stamp} 114 | pushd ${tile_dir} 115 | find . | sort -n | tar -cf ${cur_extras_dir}/planet_${stamp}.tar --no-recursion -T - 116 | popd 117 | popd 118 | 119 | # do we want to send this update to s3 (do so in the background) 120 | if [ "<%= node[:valhalla][:with_updates] %>" == true ]; then 121 | { 122 | #clean up s3 old files 123 | clean_s3 s3://<%= node[:valhalla][:bucket] %>/<%= node[:valhalla][:bucket_dir] %>/ 30 124 | #push up s3 new files 125 | for f in ${cur_extras_dir}/*; do 126 | aws s3 mv ${f} s3://<%= node[:valhalla][:bucket] %>/<%= node[:valhalla][:bucket_dir] %>/ --acl public-read 127 | done 128 | #signal other stacks to get new data 129 | aws s3 ls s3://<%= node[:valhalla][:bucket] %>/<%= node[:valhalla][:bucket_dir] %>/planet_${stamp}.tar 130 | if [[ $? -eq 0 ]]; then 131 | <%= node[:valhalla][:conf_dir] %>/push_tiles.py 132 | fi 133 | #clean it up the new stuff 134 | rm -rf ${cur_extras_dir} 135 | }& 136 | fi 137 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.3.8) 5 | ast (2.0.0) 6 | berkshelf (3.1.4) 7 | addressable (~> 2.3.4) 8 | berkshelf-api-client (~> 1.2) 9 | buff-config (~> 1.0) 10 | buff-extensions (~> 1.0) 11 | buff-shell_out (~> 0.1) 12 | celluloid (~> 0.16.0.pre) 13 | celluloid-io (~> 0.16.0.pre) 14 | faraday (~> 0.9.0) 15 | minitar (~> 0.5.4) 16 | octokit (~> 3.0) 17 | retryable (~> 1.3.3) 18 | ridley (~> 4.0) 19 | solve (~> 1.1) 20 | thor (~> 0.18) 21 | berkshelf-api-client (1.2.1) 22 | faraday (~> 0.9.0) 23 | buff-config (1.0.1) 24 | buff-extensions (~> 1.0) 25 | varia_model (~> 0.4) 26 | buff-extensions (1.0.0) 27 | buff-ignore (1.1.1) 28 | buff-ruby_engine (0.1.0) 29 | buff-shell_out (0.2.0) 30 | buff-ruby_engine (~> 0.1.0) 31 | celluloid (0.16.0) 32 | timers (~> 4.0.0) 33 | celluloid-io (0.16.2) 34 | celluloid (>= 0.16.0) 35 | nio4r (>= 1.1.0) 36 | chef (11.18.6) 37 | chef-zero (~> 2.2, >= 2.2.1) 38 | diff-lcs (~> 1.2, >= 1.2.4) 39 | erubis (~> 2.7) 40 | ffi-yajl (~> 1.2) 41 | highline (~> 1.6, >= 1.6.9) 42 | mime-types (~> 1.16) 43 | mixlib-authentication (~> 1.3) 44 | mixlib-cli (~> 1.4) 45 | mixlib-config (~> 2.0) 46 | mixlib-log (~> 1.3) 47 | mixlib-shellout (~> 1.4) 48 | net-ssh (~> 2.6) 49 | net-ssh-multi (~> 1.1) 50 | ohai (~> 7.4) 51 | plist (~> 3.1.0) 52 | pry (~> 0.9) 53 | rest-client (>= 1.0.4, <= 1.6.7) 54 | chef-zero (2.2.1) 55 | ffi-yajl (~> 1.1) 56 | hashie (~> 2.0) 57 | mixlib-log (~> 1.3) 58 | rack 59 | chefspec (4.0.1) 60 | chef (~> 11.12) 61 | fauxhai (~> 2.0) 62 | rspec (~> 3.0) 63 | coderay (1.1.0) 64 | dep-selector-libgecode (1.0.2) 65 | dep_selector (1.0.3) 66 | dep-selector-libgecode (~> 1.0) 67 | ffi (~> 1.9) 68 | diff-lcs (1.2.5) 69 | erubis (2.7.0) 70 | faraday (0.9.1) 71 | multipart-post (>= 1.2, < 3) 72 | fauxhai (2.3.0) 73 | net-ssh 74 | ohai 75 | ffi (1.9.8) 76 | ffi-yajl (1.4.0) 77 | ffi (~> 1.5) 78 | libyajl2 (~> 1.2) 79 | foodcritic (4.0.0) 80 | erubis 81 | gherkin (~> 2.11) 82 | nokogiri (~> 1.5) 83 | rake 84 | rufus-lru (~> 1.0) 85 | treetop (~> 1.4) 86 | yajl-ruby (~> 1.1) 87 | gherkin (2.12.2) 88 | multi_json (~> 1.3) 89 | hashie (2.1.2) 90 | highline (1.7.1) 91 | hitimes (1.2.2) 92 | ipaddress (0.8.0) 93 | json (1.8.2) 94 | libyajl2 (1.2.0) 95 | method_source (0.8.2) 96 | mime-types (1.25.1) 97 | mini_portile (0.6.2) 98 | minitar (0.5.4) 99 | mixlib-authentication (1.3.0) 100 | mixlib-log 101 | mixlib-cli (1.5.0) 102 | mixlib-config (2.1.0) 103 | mixlib-log (1.6.0) 104 | mixlib-shellout (1.6.1) 105 | multi_json (1.11.0) 106 | multipart-post (2.0.0) 107 | net-http-persistent (2.9.4) 108 | net-ssh (2.9.2) 109 | net-ssh-gateway (1.2.0) 110 | net-ssh (>= 2.6.5) 111 | net-ssh-multi (1.2.1) 112 | net-ssh (>= 2.6.5) 113 | net-ssh-gateway (>= 1.2.0) 114 | nio4r (1.1.0) 115 | nokogiri (1.6.6.2) 116 | mini_portile (~> 0.6.0) 117 | octokit (3.8.0) 118 | sawyer (~> 0.6.0, >= 0.5.3) 119 | ohai (7.4.1) 120 | ffi (~> 1.9) 121 | ffi-yajl (~> 1.1) 122 | ipaddress 123 | mime-types (~> 1.16) 124 | mixlib-cli 125 | mixlib-config (~> 2.0) 126 | mixlib-log 127 | mixlib-shellout (~> 1.2) 128 | systemu (~> 2.6.4) 129 | wmi-lite (~> 1.0) 130 | parser (2.2.2.0) 131 | ast (>= 1.1, < 3.0) 132 | plist (3.1.0) 133 | polyglot (0.3.5) 134 | powerpack (0.0.9) 135 | pry (0.10.1) 136 | coderay (~> 1.1.0) 137 | method_source (~> 0.8.1) 138 | slop (~> 3.4) 139 | rack (1.6.0) 140 | rainbow (2.0.0) 141 | rake (10.4.2) 142 | rest-client (1.6.7) 143 | mime-types (>= 1.16) 144 | retryable (1.3.6) 145 | ridley (4.1.1) 146 | addressable 147 | buff-config (~> 1.0) 148 | buff-extensions (~> 1.0) 149 | buff-ignore (~> 1.1) 150 | buff-shell_out (~> 0.1) 151 | celluloid (~> 0.16.0) 152 | celluloid-io (~> 0.16.1) 153 | erubis 154 | faraday (~> 0.9.0) 155 | hashie (>= 2.0.2, < 3.0.0) 156 | json (>= 1.7.7) 157 | mixlib-authentication (>= 1.3.0) 158 | net-http-persistent (>= 2.8) 159 | retryable 160 | semverse (~> 1.1) 161 | varia_model (~> 0.4) 162 | rspec (3.2.0) 163 | rspec-core (~> 3.2.0) 164 | rspec-expectations (~> 3.2.0) 165 | rspec-mocks (~> 3.2.0) 166 | rspec-core (3.2.3) 167 | rspec-support (~> 3.2.0) 168 | rspec-expectations (3.2.1) 169 | diff-lcs (>= 1.2.0, < 2.0) 170 | rspec-support (~> 3.2.0) 171 | rspec-mocks (3.2.1) 172 | diff-lcs (>= 1.2.0, < 2.0) 173 | rspec-support (~> 3.2.0) 174 | rspec-support (3.2.2) 175 | rubocop (0.24.0) 176 | json (>= 1.7.7, < 2) 177 | parser (>= 2.2.0.pre.2, < 3.0) 178 | powerpack (~> 0.0.6) 179 | rainbow (>= 1.99.1, < 3.0) 180 | ruby-progressbar (~> 1.4) 181 | ruby-progressbar (1.7.5) 182 | rufus-lru (1.0.5) 183 | sawyer (0.6.0) 184 | addressable (~> 2.3.5) 185 | faraday (~> 0.8, < 0.10) 186 | semverse (1.2.1) 187 | slop (3.6.0) 188 | solve (1.2.1) 189 | dep_selector (~> 1.0) 190 | semverse (~> 1.1) 191 | systemu (2.6.5) 192 | thor (0.19.1) 193 | timers (4.0.1) 194 | hitimes 195 | treetop (1.6.2) 196 | polyglot (~> 0.3) 197 | varia_model (0.4.0) 198 | buff-extensions (~> 1.0) 199 | hashie (>= 2.0.2, < 3.0.0) 200 | wmi-lite (1.0.0) 201 | yajl-ruby (1.2.1) 202 | 203 | PLATFORMS 204 | ruby 205 | 206 | DEPENDENCIES 207 | berkshelf (= 3.1.4) 208 | chefspec (= 4.0.1) 209 | foodcritic (= 4.0.0) 210 | rainbow (= 2.0.0) 211 | rubocop (= 0.24.0) 212 | -------------------------------------------------------------------------------- /templates/default/valhalla.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "mjolnir": { 3 | "concurrency": <%= node[:valhalla][:mjolnir][:concurrency] %>, 4 | "tile_dir": "<%= node[:valhalla][:tile_dir] %>", 5 | "tile_extract": "<%= node[:valhalla][:base_dir] %>/planet.tar", 6 | "max_cache_size": <%= node[:valhalla][:max_cache_size] %>, 7 | "statistics": "<%= node[:valhalla][:tile_dir] %>/statistics.sqlite", 8 | "admin": "<%= node[:valhalla][:tile_dir] %>/admin.sqlite", 9 | "timezone": "<%= node[:valhalla][:tile_dir] %>/tz_world.sqlite", 10 | "transit_dir": "<%= node[:valhalla][:transit_dir] %>", 11 | "logging": { 12 | "type": "std_out", 13 | "color": false 14 | } 15 | }, 16 | "additional_data": { 17 | "elevation": "<%= node[:valhalla][:elevation_dir] %>" 18 | }, 19 | "skadi": { 20 | "actions": <%= node[:valhalla][:elevation][:actions] %>, 21 | "logging": { 22 | "type": "std_out", 23 | "color": true, 24 | "long_request": 5.0 25 | }, 26 | "service": { 27 | "proxy": "ipc://<%= node[:valhalla][:conf_dir] %>/skadi" 28 | } 29 | }, 30 | "loki": { 31 | "actions": <%= node[:valhalla][:actions] %>, 32 | "use_connectivity": true, 33 | "logging": { 34 | "type": "std_out", 35 | "color": false, 36 | "long_request": 100.0 37 | }, 38 | "service": { 39 | "proxy": "ipc://<%= node[:valhalla][:conf_dir] %>/loki" 40 | }, 41 | "service_defaults": { 42 | "minimum_reachability": 50, 43 | "radius": 0 44 | } 45 | }, 46 | "thor": { 47 | "logging": { 48 | "type": "std_out", 49 | "color": false, 50 | "long_request": 110.0 51 | }, 52 | "service": { 53 | "proxy": "ipc://<%= node[:valhalla][:conf_dir] %>/thor" 54 | } 55 | }, 56 | "odin": { 57 | "logging": { 58 | "type": "std_out", 59 | "color": false 60 | }, 61 | "service": { 62 | "proxy": "ipc://<%= node[:valhalla][:conf_dir] %>/odin" 63 | } 64 | }, 65 | "meili": { 66 | "mode": "auto", 67 | "customizable": ["mode", "search_radius", "turn_penalty_factor", "gps_accuracy", "sigma_z", "beta", "max_route_distance_factor", "max_route_time_factor"], 68 | "verbose": false, 69 | "default": { 70 | "sigma_z": 4.07, 71 | "gps_accuracy": 5.0, 72 | "beta": 3, 73 | "max_route_distance_factor": 5, 74 | "max_route_time_factor": 5, 75 | "max_search_radius": 100, 76 | "breakage_distance": 2000, 77 | "interpolation_distance": 10, 78 | "search_radius": 50, 79 | "geometry": false, 80 | "route": true, 81 | "turn_penalty_factor": 0 82 | }, 83 | "auto": { 84 | "turn_penalty_factor": 200, 85 | "search_radius": 50 86 | }, 87 | "pedestrian": { 88 | "turn_penalty_factor": 100, 89 | "search_radius": 50 90 | }, 91 | "bicycle": { 92 | "turn_penalty_factor": 140 93 | }, 94 | "multimodal": { 95 | "turn_penalty_factor": 70 96 | }, 97 | "logging": { 98 | "type": "std_out", 99 | "color": true 100 | }, 101 | "service": { 102 | "proxy": "ipc://<%= node[:valhalla][:conf_dir] %>/meili" 103 | }, 104 | "grid": { 105 | "size": 500, 106 | "cache_size": 100240 107 | } 108 | }, 109 | "httpd": { 110 | "service": { 111 | "listen": "tcp://<%= node[:valhalla][:httpd][:listen_address] %>:<%= node[:valhalla][:httpd][:port] %>", 112 | "loopback": "ipc://<%= node[:valhalla][:conf_dir] %>/loopback", 113 | "interrupt": "ipc://<%= node[:valhalla][:conf_dir] %>/interrupt" 114 | } 115 | }, 116 | "service_limits": { 117 | "auto": { 118 | "max_distance": 5000000.0, 119 | "max_locations": 20, 120 | "max_matrix_distance": 400000.0, 121 | "max_matrix_locations": 50 122 | }, 123 | "auto_shorter": { 124 | "max_distance": 5000000.0, 125 | "max_locations": 20, 126 | "max_matrix_distance": 400000.0, 127 | "max_matrix_locations": 50 128 | }, 129 | "bus": { 130 | "max_distance": 5000000.0, 131 | "max_locations": 50, 132 | "max_matrix_distance": 400000.0, 133 | "max_matrix_locations": 50 134 | }, 135 | "hov": { 136 | "max_distance": 5000000.0, 137 | "max_locations": 20, 138 | "max_matrix_distance": 400000.0, 139 | "max_matrix_locations": 50 140 | }, 141 | "pedestrian": { 142 | "max_distance": 250000.0, 143 | "max_locations": 50, 144 | "max_matrix_distance": 200000.0, 145 | "max_matrix_locations": 50, 146 | "min_transit_walking_distance": 1, 147 | "max_transit_walking_distance": 10000 148 | }, 149 | "motor_scooter": { 150 | "max_distance": 500000.0, 151 | "max_locations": 50, 152 | "max_matrix_distance": 200000.0, 153 | "max_matrix_locations": 50 154 | }, 155 | "bicycle": { 156 | "max_distance": 500000.0, 157 | "max_locations": 50, 158 | "max_matrix_distance": 200000.0, 159 | "max_matrix_locations": 50 160 | }, 161 | "multimodal": { 162 | "max_distance": 500000.0, 163 | "max_locations": 50, 164 | "max_matrix_distance": 0.0, 165 | "max_matrix_locations": 0 166 | }, 167 | "transit": { 168 | "max_distance": 500000.0, 169 | "max_locations": 50, 170 | "max_matrix_distance": 200000.0, 171 | "max_matrix_locations": 50 172 | }, 173 | "truck": { 174 | "max_distance": 5000000.0, 175 | "max_locations": 20, 176 | "max_matrix_distance": 400000.0, 177 | "max_matrix_locations": 50 178 | }, 179 | "skadi": { 180 | "max_shape": 750000, 181 | "min_resample": 10.0 182 | }, 183 | "isochrone": { 184 | "max_contours": 4, 185 | "max_time": 120, 186 | "max_distance": 25000.0, 187 | "max_locations": 1 188 | }, 189 | "trace": { 190 | "max_distance": 200000.0, 191 | "max_gps_accuracy": 100.0, 192 | "max_search_radius": 100.0, 193 | "max_shape": 16000, 194 | "max_best_paths": 4, 195 | "max_best_paths_shape": 100 196 | }, 197 | "max_avoid_locations": 70, 198 | "max_reachability": 100, 199 | "max_radius": 200 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # 3 | # Cookbook Name:: valhalla 4 | # Attributes:: default 5 | # 6 | 7 | # where do some things belong 8 | default[:valhalla][:base_dir] = '/opt/valhalla' 9 | default[:valhalla][:tile_dir] = "#{node[:valhalla][:base_dir]}/tiles" 10 | default[:valhalla][:log_dir] = "#{node[:valhalla][:base_dir]}/log" 11 | default[:valhalla][:conf_dir] = "#{node[:valhalla][:base_dir]}/etc" 12 | default[:valhalla][:temp_dir] = "#{node[:valhalla][:base_dir]}/temp" 13 | default[:valhalla][:src_dir] = "#{node[:valhalla][:base_dir]}/src" 14 | default[:valhalla][:lock_dir] = "#{node[:valhalla][:base_dir]}/lock" 15 | default[:valhalla][:extracts_dir] = "#{node[:valhalla][:base_dir]}/extracts" 16 | default[:valhalla][:elevation_dir] = "#{node[:valhalla][:base_dir]}/elevation" 17 | default[:valhalla][:transit_dir] = "#{node[:valhalla][:base_dir]}/transit" 18 | default[:valhalla][:test_dir] = "#{node[:valhalla][:conf_dir]}/tests" 19 | default[:valhalla][:test_requests] = "#{node[:valhalla][:test_dir]}/test_requests" 20 | default[:valhalla][:test_results] = "#{node[:valhalla][:test_dir]}/results" 21 | 22 | # the repos 23 | default[:valhalla][:github][:base] = 'https://github.com/valhalla' 24 | default[:valhalla][:github][:revision] = 'master' 25 | default[:valhalla][:ppa_version] = '' 26 | 27 | # valhalla user to create 28 | default[:valhalla][:user][:name] = 'valhalla' 29 | default[:valhalla][:user][:home] = '/home/valhalla' 30 | default[:valhalla][:from_email] = '' 31 | default[:valhalla][:to_email] = '' 32 | 33 | # the data to create tiles 34 | default[:valhalla][:extracts] = %w( 35 | http://download.geofabrik.de/europe/liechtenstein-latest.osm.pbf 36 | ) 37 | default[:valhalla][:with_updates] = false 38 | default[:valhalla][:with_transit] = false 39 | default[:valhalla][:with_map_roulette] = false 40 | default[:valhalla][:with_testing] = true 41 | default[:valhalla][:transit_test_file] = 'transit_dev_routes.tmpl' 42 | default[:valhalla][:transitland_url] = 'http://transit.land' 43 | default[:valhalla][:transitland_api_key] = '' 44 | default[:valhalla][:transitland_import_level] = '4' 45 | 46 | # map roulette 47 | default[:maproulette][:recurring_tasks_file] = 'recurring_tasks_file.txt' 48 | default[:maproulette][:server_url] = 'http://localhost:9000' 49 | default[:maproulette][:api_key] = 'YOUR_KEY' 50 | default[:maproulette][:config] = "#{node[:valhalla][:conf_dir]}/maproulette.json" 51 | 52 | # where to put fresh tiles and who wants them 53 | default[:valhalla][:bucket] = 'YOUR_BUCKET' 54 | default[:valhalla][:bucket_dir] = 'YOUR_DIR' 55 | default[:valhalla][:routing_service_stack] = 'YOUR_STACK_ID' 56 | default[:valhalla][:routing_service_layers] = 'YOUR_LAYER_IDS' 57 | default[:valhalla][:routing_service_elbs] = 'YOUR_ELB_NAMES' 58 | default[:valhalla][:routing_service_recipes] = 'valhalla::get_routing_tiles' 59 | default[:valhalla][:min_layers_instances] = '1' 60 | default[:valhalla][:health_check_timeout] = 300 61 | if node[:opsworks] && node[:opsworks][:layers][:'matrix'] && node[:opsworks][:instance][:layers].include?('matrix') 62 | default[:valhalla][:health_check][:route_action] = 'one_to_many' 63 | default[:valhalla][:health_check][:route_request] = '{"locations":[{"lat":40.755713,"lon":-73.984010},{"lat":40.756522,"lon":-73.983978},{"lat":40.757448,"lon":-73.984187}],"costing":"pedestrian"}' 64 | else 65 | default[:valhalla][:health_check][:route_action] = 'route' 66 | default[:valhalla][:health_check][:route_request] = '{"locations":[{"lat":40.402918,"lon":-76.535017},{"lat":40.403654,"lon": -76.529846}],"costing":"auto"}' 67 | end 68 | 69 | # configuration 70 | default[:valhalla][:config] = "#{node[:valhalla][:conf_dir]}/valhalla.json" 71 | if !node[:opsworks] || (node[:opsworks][:layers][:'data-producer'] && node[:opsworks][:instance][:layers].include?('data-producer')) 72 | default[:valhalla][:max_cache_size] = 1024 * 1024 * 1024 73 | else 74 | default[:valhalla][:max_cache_size] = "#{((node.memory.total.to_f / (node.cpu.total.to_f * 2)) * 0.9).floor * 1024}" 75 | end 76 | if !node[:opsworks] 77 | default[:valhalla][:actions] = '["locate","route","one_to_many","many_to_one","many_to_many","optimized_route","isochrone","sources_to_targets","trace_route","trace_attributes"]' 78 | elsif node[:opsworks][:layers][:'matrix'] && node[:opsworks][:instance][:layers].include?('matrix') 79 | default[:valhalla][:actions] = '["one_to_many","many_to_one","many_to_many","optimized_route","isochrone","sources_to_targets"]' 80 | else 81 | default[:valhalla][:actions] = '["locate","route","trace_route","trace_attributes"]' 82 | end 83 | default[:valhalla][:elevation][:actions] = '["height"]' 84 | default[:valhalla][:mjolnir][:concurrency] = node[:cpu][:total] 85 | default[:valhalla][:httpd][:listen_address] = '0.0.0.0' 86 | default[:valhalla][:httpd][:port] = 8080 87 | 88 | # workers 89 | default[:valhalla][:workers][:count] = node[:cpu][:total] 90 | -------------------------------------------------------------------------------- /templates/default/map_roulette.py.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os.path 5 | import getopt 6 | import requests 7 | import json 8 | 9 | class Status: 10 | def __init__(self): 11 | self.created = 0 12 | self.fixed = 1 13 | self.false_positive = 2 14 | self.skipped = 3 15 | self.deleted = 4 16 | 17 | status = Status() 18 | 19 | class Challenge: 20 | def __init__(self, id): 21 | self.id = id 22 | self.tasks = {} 23 | self.types = [] 24 | 25 | def add_task(self, task): 26 | if task['name'] not in self.tasks: 27 | self.tasks[task['name']] = task 28 | 29 | def contains_task(self, task): 30 | try: 31 | ret = self.tasks[task['name']] 32 | except KeyError: 33 | return None 34 | 35 | return ret 36 | 37 | 38 | def set_types(self, lst): 39 | self.types = lst 40 | 41 | 42 | class admin_tool: 43 | def __init__(self, config, geojson): 44 | 45 | print 'Initializing' 46 | 47 | # Read the config file into an object for access 48 | with open(config) as conf_file: 49 | conf = json.loads(conf_file.read()) 50 | 51 | # Set up header for API interactions 52 | self.header = {'Content-Type': 'application/json', 'apiKey': conf['api_key']} 53 | # Get server url from config 54 | self.server_url = conf['server_url'] 55 | 56 | # the challenges to hold the tasks 57 | self.challenges = {} 58 | 59 | # A list to hold the new tasks to be uploaded 60 | self.new_tasks = [] 61 | 62 | # A list of tasks that exist but should have been fixed 63 | self.flagged_tasks = [] 64 | 65 | # Location of saved recurring fixed tasks 66 | self.recurring_tasks_file = conf['recurring_tasks_file'] 67 | 68 | # Bool for whether or not to update the recurring tasks, off by default 69 | self.resubmit = False 70 | 71 | for challenge in conf['challenges']: 72 | # Build dictionary of challenges 73 | self.challenges[challenge] = Challenge(challenge) 74 | # add the types it handles 75 | self.challenges[challenge].set_types(conf['challenges'][challenge]) 76 | 77 | # Read in the new geojson 78 | with open(geojson) as geojson_file: 79 | self.geojson = json.loads(geojson_file.read()) 80 | 81 | print 'DONE' 82 | 83 | 84 | def init_challenges(self): 85 | for challenge in self.challenges: 86 | # Download tasks for each challenge 87 | print 'Downloading tasks from challenge ' + challenge 88 | tasks = self.get_tasks_from_api(challenge).json() 89 | 90 | # Build a dictionary of challenge ids (name field from MR) 91 | for task in tasks: 92 | self.challenges[challenge].add_task(task) 93 | 94 | print 'DONE' 95 | 96 | 97 | def set_resubmit(self): 98 | self.resubmit = True 99 | 100 | 101 | def generate_tasks(self, geojson): 102 | '''generates tasks for the given geojson for the parent id specified''' 103 | # build a task for each item in the geojson array 104 | task_num = 1 105 | tasks = [] 106 | for feature in geojson['features']: 107 | task = { 108 | 'name': str(feature['properties']['key']), 109 | 'parent': int(self.get_task_parent(feature)), 110 | 'status': 0, 111 | 'geometries': 112 | { 113 | 'type': 'FeatureCollection', 114 | 'features': 115 | [{ 116 | 'type': 'Feature', 117 | 'geometry': feature['geometry'], 118 | 'properties': {} 119 | }] 120 | }, 121 | 'instruction': feature['instruction'] 122 | } 123 | 124 | tasks.append(task) 125 | task_num += 1 126 | return tasks 127 | 128 | 129 | def is_ok(self, response): 130 | if response.status_code == 200: 131 | return response 132 | raise Exception('%d: %s' % (response.status_code, response.text)) 133 | 134 | 135 | def get_tasks_from_api(self, challenge_id): 136 | '''http get up to 10000 tasks from maproulette''' 137 | payload = {'limit': 10000} 138 | response = requests.get('{}/api/v2/challenge/{}/tasks'.format(self.server_url, challenge_id), headers=self.header, params=payload) 139 | return self.is_ok(response) 140 | 141 | 142 | def upload_tasks(self, tasks): 143 | '''http POST a list of tasks to maproulette''' 144 | print 'Uploading {} tasks'.format(len(tasks)) 145 | response = requests.post('{}/api/v2/tasks'.format(self.server_url), data=json.dumps(tasks), headers=self.header) 146 | print 'DONE' 147 | return self.is_ok(response) 148 | 149 | 150 | def update_tasks(self, tasks): 151 | '''http PUT a list of tasks to maproulette''' 152 | print 'Updating {} tasks'.format(len(tasks)) 153 | for task in tasks: 154 | response = requests.put('{}/api/v2/task/{}'.format(self.server_url, task['id']), data=json.dumps(tasks), headers=self.header) 155 | print 'DONE' 156 | return self.is_ok(response) 157 | 158 | 159 | def headless_start(self): 160 | '''Begin a headless run (Requires that init_challenges has been called)''' 161 | # Get a list of the tasks from the new geojson 162 | tasks = self.generate_tasks(self.geojson) 163 | 164 | for challenge in self.challenges: 165 | if len(self.challenges[challenge].tasks) == 0: 166 | # If we haven't created any tasks for our challenges yet, do so now 167 | self.upload_tasks(tasks) 168 | else: 169 | # If we have tasks, compare them to the new tasks 170 | print 'Gathering new tasks' 171 | self.compare_tasks(tasks) 172 | print 'DONE' 173 | if self.resubmit: 174 | # then check for any erroneous 'fixed' tasks is the flag is set 175 | self.process_recurring_tasks() 176 | if len(self.new_tasks) != 0: 177 | # if there are any new tasks to be uploaded, do it 178 | self.upload_tasks(self.new_tasks) 179 | 180 | 181 | def get_task_parent(self, task): 182 | '''Given a task, check it's type and match it against a challenge ID''' 183 | task_type = task['properties']['type'] 184 | for challenge in self.challenges: 185 | if task_type in self.challenges[challenge].types: 186 | return challenge 187 | else: 188 | return None 189 | 190 | 191 | def compare_tasks(self, new_tasks): 192 | '''Compare the existing tasks downloaded from the api to the new tasks that have been generated''' 193 | for task in new_tasks: 194 | existing = self.challenges[str(task['parent'])].contains_task(task) 195 | if not existing: 196 | # if the task hasn't existed recently, put it in a list for creation 197 | self.new_tasks.append(task) 198 | elif existing['status'] == status.fixed: 199 | # if we got a new task that should have been fixed, flag it 200 | self.flagged_tasks.append(existing) 201 | 202 | 203 | def process_recurring_tasks(self): 204 | '''Compare the flagged tasks against the one already saved and modify them accordingly''' 205 | # list of task ids that are not truly fixed 206 | not_fixed = [] 207 | # make sure the file exists before we try to open it 208 | if not os.path.isfile(self.recurring_tasks_file): 209 | open(self.recurring_tasks_file, 'w').close() 210 | 211 | # open the file and read through the data 212 | with open(self.recurring_tasks_file) as in_file: 213 | previous_tasks = [] 214 | # each line represents a task id 215 | for line in in_file: 216 | previous_tasks.append(line.strip()) 217 | 218 | # if the same task id exists in the flagged tasks it isn't fixed after all 219 | not_fixed = [task for task in self.flagged_tasks if task['name'] in previous_tasks] 220 | 221 | # remove any tasks from the flagged tasks that are not fixed 222 | for task in not_fixed: 223 | self.flagged_tasks.remove(task) 224 | 225 | # write the flagged tasks out to the file so they can be verified next runtime 226 | self.write_recurring_tasks(self.flagged_tasks) 227 | # update the tasks that were supposed to be fixed, but weren't 228 | if len(not_fixed) != 0: 229 | for task in not_fixed: 230 | task['status'] = status.created 231 | self.update_tasks(not_fixed) 232 | 233 | 234 | def write_recurring_tasks(self, tasks): 235 | '''Writes out the task ids to the recurring tasks file''' 236 | with open(self.recurring_tasks_file, 'w') as out_file: 237 | for task in tasks: 238 | out_file.write(task['name'] + '\n') 239 | 240 | def usage(code = None): 241 | print('%s --config maproulette.json --geojson maproulette.geojson' % sys.argv[0]) 242 | sys.exit(code) 243 | 244 | if __name__ == '__main__': 245 | 246 | config = '' 247 | geojson = '' 248 | resubmit = False 249 | 250 | # Set expected options and parse 251 | try: 252 | opts, args = getopt.getopt(sys.argv[1:], 'hc:i:r',['help','config=','geojson=','resubmit']) 253 | except getopt.GetoptError: 254 | usage(2) 255 | 256 | # Use arguments and flags to set program parameters 257 | for opt, arg in opts: 258 | if opt in ('-h', '--help'): 259 | usage() 260 | elif opt in ('-c', '--config'): 261 | config = arg 262 | elif opt in ('-i', '--geojson'): 263 | geojson = arg 264 | elif opt in ('-r', '--resubmit'): 265 | resubmit = True 266 | 267 | if not geojson or not config: 268 | usage(2) 269 | 270 | admin = admin_tool(config, geojson) 271 | if resubmit: 272 | admin.set_resubmit() 273 | 274 | # Initialize the challenges from the geojson and MR API 275 | admin.init_challenges() 276 | 277 | # start the main part 278 | admin.headless_start() 279 | -------------------------------------------------------------------------------- /templates/default/transit_prod_routes.tmpl.erb: -------------------------------------------------------------------------------- 1 | -j '{"locations":[{"lat":37.839682,"lon":-122.485284,"name":"Vista Point"},{"lat":37.80927,"lon":-122.25981,"name":"Fairyland"}],"costing":"multimodal","directions_options":{"units":"miles"},"costing_options":{"transit":{"use_bus":"0.5","use_rail":"0.6","use_transfers":"0.4"}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 2 | -j '{"locations":[{"lat":50.095944,"lon":14.418439,"name":"Letenske sady"},{"lat":50.07868,"lon":14.441871,"name":"Riegrovy sady"}],"costing":"multimodal","directions_options":{"units":"miles"},"costing_options":{"transit":{"use_bus":"0.5","use_rail":"0.6","use_transfers":"0.4"}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 3 | -j '{"locations":[{"lat":40.744273,"lon":-73.990328,"name":"Mapzen"},{"lat":40.704651,"lon":-73.9361,"name":"CartoDB"}],"costing":"multimodal","directions_options":{"units":"miles"},"costing_options":{"transit":{"use_bus":"0.5","use_rail":"0.6","use_transfers":"0.4"}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 4 | -j '{"locations":[{"lat":41.907515,"lon":12.455492,"name":"Vatican"},{"lat":41.890841,"lon":12.491025,"name":"Colosseum"}],"costing":"multimodal","directions_options":{"units":"miles"},"costing_options":{"transit":{"use_bus":"0.5","use_rail":"0.6","use_transfers":"0.4"}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 5 | -j '{"locations":[{"lat":39.995533,"lon":-75.304981,"name":"Yards Brewing"},{"lat":39.918162,"lon":-75.158039,"name":"Dalessandro Cheesesteaks"}],"costing":"multimodal","directions_options":{"units":"miles"},"costing_options":{"transit":{"use_bus":"0.5","use_rail":"0.6","use_transfers":"0.4"}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 6 | -j '{"locations":[{"lat":49.194156,"lon":-123.178882,"name":"YVR"},{"lat":49.37223,"lon":-123.098673,"name":"Grouse Mountain Gondola"}],"costing":"multimodal","directions_options":{"units":"miles"},"costing_options":{"transit":{"use_bus":"0.4","use_rail":"0.6","use_transfers":"0.4"}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 7 | -j '{"locations":[{"lat":40.70523754289766,"lon":-74.01114464155398},{"lat":40.63974890854884,"lon":-74.08361435635015}],"costing":"multimodal","directions_options":{"units":"miles"},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 8 | -j '{"locations":[{"lon":-122.394935,"lat":37.776348,"type":"break"},{"lon":-121.903173,"lat":37.329231,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 9 | -j '{"locations":[{"lon":-122.394935,"lat":37.776348,"type":"break"},{"lon":-121.883999,"lat":37.31175,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 10 | -j '{"locations":[{"lon":-121.9446515,"lat":38.019507,"type":"break"},{"lon":-122.38666,"lat":37.599787,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 11 | -j '{"locations":[{"lon":-122.418466,"lat":37.752254,"type":"break"},{"lon":-121.900367,"lat":37.701695,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 12 | -j '{"locations":[{"lon":-121.832780698,"lat":37.358275965,"type":"break"},{"lon":-122.037214434,"lat":37.324093554,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 13 | -j '{"locations":[{"lon":-121.760734,"lat":37.299843,"type":"break"},{"lon":-121.810753,"lat":37.327058,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 14 | -j '{"locations":[{"lon":-122.26649,"lat":37.79786,"type":"break"},{"lon":-122.268817,"lat":37.869319,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 15 | -j '{"locations":[{"lon":-122.258931,"lat":37.868826,"type":"break"},{"lon":-122.297787,"lat":37.884573,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 16 | -j '{"locations":[{"lon":-74.191794,"lat":40.533674,"type":"break"},{"lon":-74.073643,"lat":40.643748,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 17 | -j '{"locations":[{"lon":-73.944216,"lat":40.824783,"type":"break"},{"lon":-73.961376,"lat":40.577621,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 18 | -j '{"locations":[{"lon":-74.00647,"lat":40.716633,"type":"break"},{"lon":-74.035202,"lat":40.611843,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 19 | -j '{"locations":[{"lon":-73.926826,"lat":40.619896,"type":"break"},{"lon":-73.931076,"lat":40.668476,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 20 | -j '{"locations":[{"lon":-74.005867,"lat":40.737072,"type":"break"},{"lon":-73.955475,"lat":40.826702,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 21 | -j '{"locations":[{"lon":-73.944534,"lat":40.779755,"type":"break"},{"lon":-73.992546,"lat":40.768192,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 22 | -j '{"locations":[{"lon":-74.171165,"lat":40.560669,"type":"break"},{"lon":-74.178276,"lat":40.526684,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 23 | -j '{"locations":[{"lon":-74.11544,"lat":40.5681,"type":"break"},{"lon":-74.028343,"lat":40.62252,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 24 | -j '{"locations":[{"lon":-73.734749,"lat":40.771725,"type":"break"},{"lon":-73.828537,"lat":40.760021,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 25 | -j '{"locations":[{"lon":-73.829529,"lat":40.761745,"type":"break"},{"lon":-73.735283,"lat":40.718987,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 26 | -j '{"locations":[{"lon":-73.904709,"lat":40.827328,"type":"break"},{"lon":-73.84314,"lat":40.839817,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 27 | -j '{"locations":[{"lon":-73.93856,"lat":40.848492,"type":"break"},{"lon":-73.909531,"lat":40.822971,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 28 | -j '{"locations":[{"lon":-74.02922,"lat":40.73586,"type":"break"},{"lon":-74.06289,"lat":40.73301,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 29 | -j '{"locations":[{"lon":-73.98827,"lat":40.74912,"type":"break"},{"lon":-74.06289,"lat":40.73301,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 30 | -j '{"locations":[{"lon":-122.47883,"lat":37.6606,"type":"break"},{"lon":-122.48312,"lat":37.67604,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 31 | -j '{"locations":[{"lon":-122.232053,"lat":37.48582,"type":"break"},{"lon":-122.416925,"lat":37.638953,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 32 | -j '{"locations":[{"lon":-74.012666,"lat":40.70136,"type":"break"},{"lon":-74.073145,"lat":40.644055,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 33 | -j '{"locations":[{"lon":-74.073145,"lat":40.644055,"type":"break"},{"lon":-74.012666,"lat":40.70136,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 34 | -j '{"locations":[{"lon":-122.434607,"lat":37.792386,"type":"break"},{"lon":-122.390928,"lat":37.734091,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 35 | -j '{"locations":[{"lon":-122.394631,"lat":37.777278,"type":"break"},{"lon":-122.425351,"lat":37.805063,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 36 | -j '{"locations":[{"lon":-84.389407,"lat":33.753346,"type":"break"},{"lon":-84.271222,"lat":33.711531,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 37 | -j '{"locations":[{"lon":-84.448465,"lat":33.651937,"type":"break"},{"lon":-84.471091,"lat":33.588778,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 38 | -j '{"locations":[{"lon":-122.847393,"lat":49.277809,"type":"break"},{"lon":-122.855795,"lat":49.322138,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 39 | -j '{"locations":[{"lon":-123.186028,"lat":49.26367,"type":"break"},{"lon":-123.067725,"lat":49.262833,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 40 | -j '{"locations":[{"lon":-71.095169,"lat":42.348949,"type":"break"},{"lon":-71.153921,"lat":42.347693,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 41 | -j '{"locations":[{"lon":-71.05479665,"lat":42.35176309,"type":"break"},{"lon":-71.103627,"lat":42.124084,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 42 | -j '{"locations":[{"lon":-74.002139,"lat":40.709265,"type":"break"},{"lon":-73.71228,"lat":40.747048,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 43 | -j '{"locations":[{"lon":-73.939307,"lat":40.748682,"type":"break"},{"lon":-73.89329,"lat":40.773573,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 44 | -j '{"locations":[{"lon":-73.624,"lat":41.412,"type":"break"},{"lon":-73.562,"lat":41.814,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 45 | -j '{"locations":[{"lon":-73.977056,"lat":40.752998,"type":"break"},{"lon":-73.937946,"lat":41.705839,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 46 | -j '{"locations":[{"lon":-73.99358,"lat":40.75058,"type":"break"},{"lon":-73.32405,"lat":40.70068,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 47 | -j '{"locations":[{"lon":-118.287308,"lat":33.869339,"type":"break"},{"lon":-118.045128,"lat":34.072189,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 48 | -j '{"locations":[{"lon":-118.391869,"lat":33.949165,"type":"break"},{"lon":-118.188629,"lat":33.769176,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 49 | -j '{"locations":[{"lon":-75.167792,"lat":39.948635,"type":"break"},{"lon":-75.000325,"lat":39.833809,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 50 | -j '{"locations":[{"lon":-75.167792,"lat":39.948635,"type":"break"},{"lon":-75.037141,"lat":39.897609,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 51 | -j '{"locations":[{"lon":-79.406237,"lat":43.745366,"type":"break"},{"lon":-79.277195,"lat":43.766963,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 52 | -j '{"locations":[{"lon":-79.402368,"lat":43.725141,"type":"break"},{"lon":-79.42904,"lat":43.795568,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 53 | -j '{"locations":[{"lon":-122.522659,"lat":37.971092,"type":"break"},{"lon":-122.31739,"lat":37.925526,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 54 | -j '{"locations":[{"lon":-116.951820552349,"lat":33.9232223308761,"type":"break"},{"lon":-116.96615561843,"lat":33.932677871308,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 55 | -j '{"locations":[{"lon":-116.998899906473,"lat":33.9468692130059,"type":"break"},{"lon":-117.008917278643,"lat":33.9440176154221,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 56 | -j '{"locations":[{"lon":-71.0152682367706,"lat":42.0852906746392,"type":"break"},{"lon":-71.0640851643486,"lat":42.2844370483635,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 57 | -j '{"locations":[{"lon":-70.61738533,"lat":42.65857813,"type":"break"},{"lon":-70.6592043,"lat":42.6136316,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 58 | -j '{"locations":[{"lon":-74.0111446,"lat":40.7052375,"type":"break"},{"lon":-74.0836143,"lat":40.6397489,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 59 | -j '{"locations":[{"lon":-117.59545,"lat":33.87843333,"type":"break"},{"lon":-117.5168333,"lat":33.82611667,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 60 | -j '{"locations":[{"lon":-117.59545,"lat":33.87843333,"type":"break"},{"lon":-117.5168333,"lat":33.82611667,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 61 | -j '{"locations":[{"lon":-118.371895,"lat":33.770515,"type":"break"},{"lon":-118.423141,"lat":33.778248,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 62 | -j '{"locations":[{"lon":-118.423141,"lat":33.778248,"type":"break"},{"lon":-118.314545,"lat":33.776356,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 63 | -j '{"locations":[{"lon":-122.523102,"lat":37.971081,"type":"break"},{"lon":-122.603691,"lat":38.00032,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 64 | -j '{"locations":[{"lon":-122.513275,"lat":37.898609,"type":"break"},{"lon":-122.455788,"lat":37.873722,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 65 | -j '{"locations":[{"lon":-79.719198,"lat":43.485887,"type":"break"},{"lon":-79.828892,"lat":43.389191,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 66 | -j '{"locations":[{"lon":-79.6824,"lat":43.4558,"type":"break"},{"lon":-79.71088,"lat":43.479278,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 67 | -j '{"locations":[{"lon":-75.1580556,"lat":39.9525,"type":"break"},{"lon":-75.7636111,"lat":39.9927778,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 68 | -j '{"locations":[{"lon":-75.1902778,"lat":39.9480556,"type":"break"},{"lon":-75.5511111,"lat":39.7372222,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 69 | -j '{"locations":[{"lon":-75.142305,"lat":39.949783,"type":"break"},{"lon":-75.166068,"lat":40.009811,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 70 | -j '{"locations":[{"lon":-75.259689,"lat":39.962161,"type":"break"},{"lon":-75.546186,"lat":39.878706,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 71 | -j '{"locations":[{"lon":14.244423,"lat":49.839643,"type":"break"},{"lon":14.218474,"lat":49.849991,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 72 | -j '{"locations":[{"lon":14.488839,"lat":50.350269,"type":"break"},{"lon":14.519666,"lat":50.252818,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 73 | -j '{"locations":[{"lon":-122.303025,"lat":49.135246,"type":"break"},{"lon":-122.255034,"lat":49.155008,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 74 | -j '{"locations":[{"lon":-121.955484,"lat":49.168568,"type":"break"},{"lon":-121.963371,"lat":49.139669,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 75 | -j '{"locations":[{"lon":-121.95,"lat":49.129,"type":"break"},{"lon":-121.976,"lat":49.104,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 76 | -j '{"locations":[{"lon":-125.02627,"lat":49.619319,"type":"break"},{"lon":-125.031328,"lat":49.62128,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 77 | -j '{"locations":[{"lon":-124.998137,"lat":49.692378,"type":"break"},{"lon":-124.923863,"lat":49.672578,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 78 | -j '{"locations":[{"lon":-120.325779,"lat":50.67677,"type":"break"},{"lon":-120.119051,"lat":50.65955,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 79 | -j '{"locations":[{"lon":-120.357331,"lat":50.745253,"type":"break"},{"lon":-120.332802,"lat":50.775441,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 80 | -j '{"locations":[{"lon":-119.444355,"lat":49.882035,"type":"break"},{"lon":-119.631095,"lat":49.828565,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 81 | -j '{"locations":[{"lon":-119.494485,"lat":49.887368,"type":"break"},{"lon":-119.386029,"lat":49.888907,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 82 | -j '{"locations":[{"lon":-124.450119,"lat":49.345888,"type":"break"},{"lon":-123.953189,"lat":49.19172,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 83 | -j '{"locations":[{"lon":-124.052669,"lat":49.236382,"type":"break"},{"lon":-124.450119,"lat":49.345888,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 84 | -j '{"locations":[{"lon":-122.786144,"lat":53.86517,"type":"break"},{"lon":-122.789089,"lat":53.988882,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 85 | -j '{"locations":[{"lon":-122.813641,"lat":53.891281,"type":"break"},{"lon":-122.782284,"lat":53.918698,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 86 | -j '{"locations":[{"lon":-123.110404,"lat":49.747327,"type":"break"},{"lon":-123.132433,"lat":49.740851,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 87 | -j '{"locations":[{"lon":-123.145697,"lat":49.760578,"type":"break"},{"lon":-123.153217,"lat":49.77059,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 88 | -j '{"locations":[{"lon":-123.759515,"lat":49.472326,"type":"break"},{"lon":-123.475114,"lat":49.43376,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 89 | -j '{"locations":[{"lon":-123.759515,"lat":49.472326,"type":"break"},{"lon":-123.475114,"lat":49.43376,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 90 | -j '{"locations":[{"lon":-123.388654,"lat":48.498475,"type":"break"},{"lon":-123.38329,"lat":48.421579,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 91 | -j '{"locations":[{"lon":-123.428139,"lat":48.427636,"type":"break"},{"lon":-123.371536,"lat":48.455318,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 92 | -j '{"locations":[{"lon":-122.928183,"lat":50.159079,"type":"break"},{"lon":-122.95256,"lat":50.113654,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 93 | -j '{"locations":[{"lon":-123.041417,"lat":50.079826,"type":"break"},{"lon":-122.952385,"lat":50.113523,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 94 | -j '{"locations":[{"lon":-121.955484,"lat":49.168568,"type":"break"},{"lon":-122.660324,"lat":49.160786,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 95 | -j '{"locations":[{"lon":-121.955484,"lat":49.168568,"type":"break"},{"lon":-122.660324,"lat":49.160786,"type":"break"}],"costing":"multimodal","costing_options":{"transit":{"use_bus":0.3,"use_rail":0.6,"use_transfers":0.3}},"date_time":{"type":1,"value":"DATE_TIME_TAG"}}' 96 | --------------------------------------------------------------------------------