From b27dd5fe2f061e2ef94d250fc5a06c1d8e84179a Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Fri, 9 Mar 2018 15:55:49 +0100 Subject: Add `TomTom` class to communicate with their API Provides an interface to communicate with the TomTom API. Currently includes a method `#batch` to make a batch routing request (https://developer.tomtom.com/online-routing/online-routing-documentation/batch-routing). Left a bunch of development-related code in just to preserve my in-progress stages. Originally I was told to use the matrix routing API, but that turned out to not match what we wanted. Namely, matrix routing would produce a table with every point routed with every other point. We instead want routes of each segment in a line, in order (or, just the diagonal of the matrix). Using the batch API allows us to get the routes we need without doing unnecessary work. Update the `WayCost` class to provide `departure` and `arrival` methods as readers, and make `id` an optional parameter for now. (We still need to figure out how we're dealing with ID.) Refs #6095 --- lib/tom_tom.rb | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/tom_tom.rb (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb new file mode 100644 index 000000000..5343519fd --- /dev/null +++ b/lib/tom_tom.rb @@ -0,0 +1,71 @@ +class TomTom + BASE_URL = 'https://api.tomtom.com' +# https://api.tomtom.com/routing/1/matrix/xml?key=&routeType=shortest&travelMode=truck + API_KEY = '' + + def initialize + @connection = Faraday.new( + url: BASE_URL, + params: { + key: API_KEY + } + ) do |faraday| + faraday.use FaradayMiddleware::FollowRedirects, limit: 1 + faraday.adapter Faraday.default_adapter + end + end + + # Maximum size of matrix is 700 (number of origins multiplied by number of destinations), so examples of matrix dimensions are: 5x10, 10x10, 28x25 (it does not need to be square). + # def matrix(opts) + # @connection.post do |req| + # req.url '/routing/1/matrix/json' + # req.params { + # routeType: 'shortest', + # travelMode: opts[:travel_mode], + # routingOption: 'TODO' + # } + # req.body { + # origins: [], + # destinations: [] + # } + # ) + # end + +# Maximum number of batch items is 700. +# { +# "batchItems": [ +# {"query": "/calculateRoute/52.36006039665441,4.851064682006836:52.36187528311709,4.850560426712036/json?travelMode=car&routeType=shortest&traffic=true&departAt=now&maxAlternatives=0"}, +# {"query": "/calculateRoute/52.36241907934766,4.850034713745116:52.36173769505809,4.852169752120972/json?travelMode=teleport&routeType=shortest&traffic=true&departAt=now"} +# ] +# } + def batch(way_costs) + # TODO: figure out param assembly (maybe Net::HTTP has something?) + params = URI.encode_www_form({ + travelMode: 'car', + routeType: 'shortest', + traffic: 'true', + departAt: 'now', + maxAlternatives: 0 + }) + batch_items = convert_way_costs_for_batch(way_costs).map do |locations| + { + query: "/calculateRoute/#{locations}/json?#{params}" + } + end + + response = @connection.post do |req| + req.url '/routing/1/batch/json' + req.headers['Content-Type'] = 'application/json' + req.body = { + batchItems: batch_items + }.to_json + end + end + + def convert_way_costs_for_batch(way_costs) + way_costs.map do |way_cost| + "#{way_cost.departure.lat},#{way_cost.departure.lng}" \ + ":#{way_cost.arrival.lat},#{way_cost.arrival.lng}" + end + end +end -- cgit v1.2.3 From 35e6d2691ddd06041ad8c55a850651a895dcbe0f Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Fri, 9 Mar 2018 17:03:35 +0100 Subject: TomTom: Remove old commented code These served during development but are no longer needed. Refs #6095 --- lib/tom_tom.rb | 25 ------------------------- 1 file changed, 25 deletions(-) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 5343519fd..d5cdb15a4 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -1,6 +1,5 @@ class TomTom BASE_URL = 'https://api.tomtom.com' -# https://api.tomtom.com/routing/1/matrix/xml?key=&routeType=shortest&travelMode=truck API_KEY = '' def initialize @@ -15,31 +14,7 @@ class TomTom end end - # Maximum size of matrix is 700 (number of origins multiplied by number of destinations), so examples of matrix dimensions are: 5x10, 10x10, 28x25 (it does not need to be square). - # def matrix(opts) - # @connection.post do |req| - # req.url '/routing/1/matrix/json' - # req.params { - # routeType: 'shortest', - # travelMode: opts[:travel_mode], - # routingOption: 'TODO' - # } - # req.body { - # origins: [], - # destinations: [] - # } - # ) - # end - -# Maximum number of batch items is 700. -# { -# "batchItems": [ -# {"query": "/calculateRoute/52.36006039665441,4.851064682006836:52.36187528311709,4.850560426712036/json?travelMode=car&routeType=shortest&traffic=true&departAt=now&maxAlternatives=0"}, -# {"query": "/calculateRoute/52.36241907934766,4.850034713745116:52.36173769505809,4.852169752120972/json?travelMode=teleport&routeType=shortest&traffic=true&departAt=now"} -# ] -# } def batch(way_costs) - # TODO: figure out param assembly (maybe Net::HTTP has something?) params = URI.encode_www_form({ travelMode: 'car', routeType: 'shortest', -- cgit v1.2.3 From 8f659c096470d97cc4a41596b37d1f629c96e25b Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Fri, 9 Mar 2018 17:04:44 +0100 Subject: TomTom: Use travelMode='bus' For now, hard-code 'bus' as the travel mode. This feature is currently only going to be used for Ouibus, so we want to be able to request in 'bus' mode, but for now don't have time to think about how to make it more extensible to other travel modes and operators. Refs #6095 --- lib/tom_tom.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index d5cdb15a4..60472c3be 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -16,7 +16,7 @@ class TomTom def batch(way_costs) params = URI.encode_www_form({ - travelMode: 'car', + travelMode: 'bus', routeType: 'shortest', traffic: 'true', departAt: 'now', -- cgit v1.2.3 From d76fa905ede93b8943e2fff372f7afa93f22b524 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Fri, 9 Mar 2018 17:42:08 +0100 Subject: TomTom#batch: Remove unwanted params I had copy-pasted these from the example in the docs (https://developer.tomtom.com/online-routing/online-routing-documentation/batch-routing), but I don't think we want some of these. For `maxAlternatives`, it's already 0 by default according to their docs, and the others don't really make sense for us because we want a general routing instead of one based on traffic or time of day. Refs #6095 --- lib/tom_tom.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 60472c3be..4a6a58df0 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -17,10 +17,7 @@ class TomTom def batch(way_costs) params = URI.encode_www_form({ travelMode: 'bus', - routeType: 'shortest', - traffic: 'true', - departAt: 'now', - maxAlternatives: 0 + routeType: 'shortest' }) batch_items = convert_way_costs_for_batch(way_costs).map do |locations| { -- cgit v1.2.3 From b55fcf26a4b71ad79737bc2a0d291f6ce6944dec Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 12 Mar 2018 11:36:07 +0100 Subject: TomTom: Add method for `calculateRoute` endpoint A new method to provide access to the `/calculateRoute` endpoint, which calculates costs for a single route through a bunch of waypoints. This doesn't work currently because the API requires that I send `supportingPoints` in the body of the request. Since I'm not too clear on what those are, I'm going to cut off development of this method here and just use `#batch` instead for my purposes. My idea was to use this endpoint instead of `/batch` to allow us to calculate a single route instead of having the API do `/calculateRoute` for each of our point pairs. This seemed like it would be more efficient, and give us our distance and time costs between each waypoint all in one go, but since I'm not clear on how to use this API and whether it will give us the correct data, I'm going to stick with `/batch`. I'll probably be reverting this code. Just committing it now in case it becomes useful in the future. Refs #6095 --- lib/tom_tom.rb | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 4a6a58df0..a2ebeb1a8 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -14,6 +14,19 @@ class TomTom end end + def calculate_route(way_costs) + params = URI.encode_www_form({ + travelMode: 'bus', + routeType: 'shortest' + }) + locations = convert_way_costs_for_calculate_route(way_costs) + + response = @connection.post do |req| + req.url "/routing/1/calculateRoute/#{locations}/json?#{params}" + req.headers['Content-Type'] = 'application/json' + end + end + def batch(way_costs) params = URI.encode_www_form({ travelMode: 'bus', @@ -32,6 +45,16 @@ class TomTom batchItems: batch_items }.to_json end + + response = JSON.parse(response.body) + + calculated_routes = response['batchItems'] + + calculated_routes.each do |route| + next if route['statusCode'] != 200 + + distance = route['response']['routes'] + end end def convert_way_costs_for_batch(way_costs) @@ -40,4 +63,17 @@ class TomTom ":#{way_cost.arrival.lat},#{way_cost.arrival.lng}" end end + + def convert_way_costs_for_calculate_route(way_costs) + coordinates = [] + + way_costs.map do |way_cost| + coordinates << "#{way_cost.departure.lat},#{way_cost.departure.lng}" + coordinates << "#{way_cost.arrival.lat},#{way_cost.arrival.lng}" + end + + coordinates + .uniq + .join(':') + end end -- cgit v1.2.3 From af4e70fc1c0ef7d74429b0a6507182a6b34ec721 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 12 Mar 2018 11:46:03 +0100 Subject: Revert "TomTom: Add method for `calculateRoute` endpoint" This reverts commit f28a4b2c5b348bc12b455aa0cd76a9513103aea7. As stated in that commit, I'm going to use the `/batch` endpoint instead of `/calculateRoute` because I know that's already working. --- lib/tom_tom.rb | 36 ------------------------------------ 1 file changed, 36 deletions(-) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index a2ebeb1a8..4a6a58df0 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -14,19 +14,6 @@ class TomTom end end - def calculate_route(way_costs) - params = URI.encode_www_form({ - travelMode: 'bus', - routeType: 'shortest' - }) - locations = convert_way_costs_for_calculate_route(way_costs) - - response = @connection.post do |req| - req.url "/routing/1/calculateRoute/#{locations}/json?#{params}" - req.headers['Content-Type'] = 'application/json' - end - end - def batch(way_costs) params = URI.encode_www_form({ travelMode: 'bus', @@ -45,16 +32,6 @@ class TomTom batchItems: batch_items }.to_json end - - response = JSON.parse(response.body) - - calculated_routes = response['batchItems'] - - calculated_routes.each do |route| - next if route['statusCode'] != 200 - - distance = route['response']['routes'] - end end def convert_way_costs_for_batch(way_costs) @@ -63,17 +40,4 @@ class TomTom ":#{way_cost.arrival.lat},#{way_cost.arrival.lng}" end end - - def convert_way_costs_for_calculate_route(way_costs) - coordinates = [] - - way_costs.map do |way_cost| - coordinates << "#{way_cost.departure.lat},#{way_cost.departure.lng}" - coordinates << "#{way_cost.arrival.lat},#{way_cost.arrival.lng}" - end - - coordinates - .uniq - .join(':') - end end -- cgit v1.2.3 From ac2f38518f6c0aa81fcc622ce5f0ab3b3837dbe0 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 12 Mar 2018 12:01:28 +0100 Subject: secrets.yml: Add `tomtom_api_key` Make the TomTom API key accessible to the application. It gets set in Docker via an environment variable. Still need to work out how we're setting it in development. For now I'm just saving it in `secrets.yml` without committing it. Refs #6095 --- lib/tom_tom.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 4a6a58df0..58604c347 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -1,6 +1,6 @@ class TomTom BASE_URL = 'https://api.tomtom.com' - API_KEY = '' + API_KEY = Rails.application.secrets.tomtom_api_key def initialize @connection = Faraday.new( -- cgit v1.2.3 From 0d5e6f92f74a06807c67c9d0427dd5b5543a6400 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 12 Mar 2018 12:28:19 +0100 Subject: TomTom#batch: Populate WayCosts with distance & time from API Look through the API response from `/batch` and extract the distance & time values given to us by TomTom. These are inserted in the `WayCost`s given to use in the argument to `#batch`. At the end, we get a list of `WayCost`s that are completely filled with distance & time values. We need to split up this code a bit to make it more testable, but already this seems to function correctly. Refs #6095 --- lib/tom_tom.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 58604c347..415bba54b 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -32,6 +32,21 @@ class TomTom batchItems: batch_items }.to_json end + + response = JSON.parse(response.body) + + calculated_routes = response['batchItems'] + calculated_routes.each_with_index do |route, i| + next if route['statusCode'] != 200 + + distance = route['response']['routes'][0]['summary']['lengthInMeters'] + time = route['response']['routes'][0]['summary']['travelTimeInSeconds'] + + way_costs[i].distance = distance + way_costs[i].time = time + end + + way_costs end def convert_way_costs_for_batch(way_costs) -- cgit v1.2.3 From f04a89b3af581c8956add61e4a05edc0ae9405f5 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 12 Mar 2018 12:41:28 +0100 Subject: Move `lib/tom_tom.rb` to `lib/tom_tom/batch.rb` Separate the functionality a little better by moving the `/batch` endpoint code into a new class. The goal here is also to lay the foundation for being able to call `TomTom.batch()` instead of `TomTom.new.batch()`. Refs #6095 --- lib/tom_tom.rb | 58 +--------------------------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 415bba54b..2947961c3 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -1,58 +1,2 @@ -class TomTom - BASE_URL = 'https://api.tomtom.com' - API_KEY = Rails.application.secrets.tomtom_api_key - - def initialize - @connection = Faraday.new( - url: BASE_URL, - params: { - key: API_KEY - } - ) do |faraday| - faraday.use FaradayMiddleware::FollowRedirects, limit: 1 - faraday.adapter Faraday.default_adapter - end - end - - def batch(way_costs) - params = URI.encode_www_form({ - travelMode: 'bus', - routeType: 'shortest' - }) - batch_items = convert_way_costs_for_batch(way_costs).map do |locations| - { - query: "/calculateRoute/#{locations}/json?#{params}" - } - end - - response = @connection.post do |req| - req.url '/routing/1/batch/json' - req.headers['Content-Type'] = 'application/json' - req.body = { - batchItems: batch_items - }.to_json - end - - response = JSON.parse(response.body) - - calculated_routes = response['batchItems'] - calculated_routes.each_with_index do |route, i| - next if route['statusCode'] != 200 - - distance = route['response']['routes'][0]['summary']['lengthInMeters'] - time = route['response']['routes'][0]['summary']['travelTimeInSeconds'] - - way_costs[i].distance = distance - way_costs[i].time = time - end - - way_costs - end - - def convert_way_costs_for_batch(way_costs) - way_costs.map do |way_cost| - "#{way_cost.departure.lat},#{way_cost.departure.lng}" \ - ":#{way_cost.arrival.lat},#{way_cost.arrival.lng}" - end - end +module TomTom end -- cgit v1.2.3 From 05d2014dff30901feba5c9aab3e066d89d3771ca Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Mon, 12 Mar 2018 12:50:43 +0100 Subject: TomTom: Provide `TomTom.batch(...)` method This change enables us to call `TomTom.batch(...)` instead of `TomTom.new.batch(...)` a couple commits ago. This is nice because the initialisation was kind of unnecessary for users of the class. Refs #6095 --- lib/tom_tom.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 2947961c3..97f914f28 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -1,2 +1,18 @@ module TomTom + BASE_URL = 'https://api.tomtom.com' + API_KEY = Rails.application.secrets.tomtom_api_key + + @connection = Faraday.new( + url: BASE_URL, + params: { + key: API_KEY + } + ) do |faraday| + faraday.use FaradayMiddleware::FollowRedirects, limit: 1 + faraday.adapter Faraday.default_adapter + end + + def self.batch(way_costs) + TomTom::Batch.new(@connection).batch(way_costs) + end end -- cgit v1.2.3 From 9291d45e825edbaf52cb556c102498366985496f Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Thu, 15 Mar 2018 11:52:16 +0100 Subject: Route: Don't run `#calculate_costs!` on callback if TomTom disabled We say `TomTom` is disabled when no API key is present. If this is the case, the `after_save` callback that uses it shouldn't be executed. I had to change my `API_KEY` constant to an instance variable to be able to change it for testing. Refs #6095 --- lib/tom_tom.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/tom_tom.rb') diff --git a/lib/tom_tom.rb b/lib/tom_tom.rb index 97f914f28..a1a2bda43 100644 --- a/lib/tom_tom.rb +++ b/lib/tom_tom.rb @@ -1,17 +1,21 @@ module TomTom BASE_URL = 'https://api.tomtom.com' - API_KEY = Rails.application.secrets.tomtom_api_key + @api_key = Rails.application.secrets.tomtom_api_key @connection = Faraday.new( url: BASE_URL, params: { - key: API_KEY + key: @api_key } ) do |faraday| faraday.use FaradayMiddleware::FollowRedirects, limit: 1 faraday.adapter Faraday.default_adapter end + def self.enabled? + @api_key.present? + end + def self.batch(way_costs) TomTom::Batch.new(@connection).batch(way_costs) end -- cgit v1.2.3