google_distance_matrix 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90be905a03cf4356641158bddfc9d73e8dbcd30a
4
- data.tar.gz: aa34d8e0ef5d84c3041105c6e07e5a05e8fbf30a
3
+ metadata.gz: 3ca9d7d93abd6340a3cb5d36d03cbadfda5e4e1c
4
+ data.tar.gz: 2cb7956404bd763af695eae8c10b81de5db48450
5
5
  SHA512:
6
- metadata.gz: 99df75867c1dee0fc7c2fea2d9d1811cb236d2a9acbfbddce2e4ad0bff73a62a28ae9fadfcc8eec8b18b0cdf3739cf0047f0876178d0ddcc211020c4e822fa66
7
- data.tar.gz: a27b71536a839707da8e62bb1d58e76c0cb8f255698f62e084938c002adb7eda0d81a5bee3dec8af74fc9903ace9e674640cc6ca23735d03bae60c9346363c54
6
+ metadata.gz: 2c5364924b1a055ce8c67c1508b219a1fc187ee9ecfa95d683775c21888f1076518bd1617b6a2e5be0020f5ef862e6707341bf8c71fff60e12e011d4dad719b9
7
+ data.tar.gz: 59c211df2561ea00a1da7858051fb87de4a02e35642faba695852baf974e9e3d30d34c9b710e3078e05a2cf1ea1caefb6411f35a0604a90a6f01c37f9c9af715
data/.travis.yml CHANGED
@@ -1,7 +1,6 @@
1
1
  before_install:
2
2
  - gem update bundler
3
3
  rvm:
4
- - 2.0.0
5
4
  - 2.1.0
6
5
  - 2.1.2
7
6
  - 2.1.5
data/CHANGELOG.md CHANGED
@@ -1,4 +1,13 @@
1
- ## v.0.3.0 (unreleased)
1
+ ## v.0.4.0
2
+ * When mode is `driving` and `departure_time` is set all `route` objects will contain
3
+ `duration_in_traffic_in_seconds` and `duration_in_traffic_text`.
4
+ You can also query the matrix by `shortest_route_by_duration_in_traffic_to(place)`.
5
+ * Added option to encode origins and destinations as polylines to save characters in the URL.
6
+ * Filter sensitive GET params when logging the URL we query Google Matrix with.
7
+ * departure_time can now be set to 'now'.
8
+ * Dropped support for Ruby 2.0.0
9
+
10
+ ## v.0.3.0
2
11
  This release includes one breaking change, the removal of sensor parameter.
3
12
  This parameter is no longer used - see:
4
13
  https://developers.google.com/maps/documentation/distance-matrix/intro#Sensor
data/README.md CHANGED
@@ -56,14 +56,14 @@ matrix.configure do |config|
56
56
  config.google_business_api_client_id = "123"
57
57
  config.google_business_api_private_key = "your-secret-key"
58
58
 
59
- # If you have an API key, you can specify that as well.
59
+ # If you have an API key, you can specify that as well.
60
60
  config.google_api_key = "YOUR_API_KEY"
61
61
  end
62
62
  ```
63
63
  ### Get the data for the matrix
64
64
 
65
65
  `matrix.data` returns the data, loaded from Google, for this matrix.
66
-
66
+
67
67
  It is a multi dimensional array. Rows are ordered according to the values in the origins.
68
68
  Each row corresponds to an origin, and each element within that row corresponds to a pairing of the origin with a destination.
69
69
 
@@ -80,19 +80,25 @@ Returns Google::DistanceMatrix::Route with given origin and destination
80
80
 
81
81
  ```ruby
82
82
  matrix.route_for origin: lat_lng, destination: dest_address
83
- # Google::DistanceMatrix::Route with one origin and a destination, together with route data
84
- matrix.shortest_route_by_distance_to(dest_address)
85
- # Google::DistanceMatrix::Route with one origin and a destination, together with route data
86
- matrix.shortest_route_by_duration_to(dest_address)
83
+
84
+ # Returns the shortest route to given destination, either by distance or duration
85
+ matrix.shortest_route_by_distance_to(dest_address)
86
+ matrix.shortest_route_by_duration_to(dest_address)
87
+
88
+ # If your matrix is for driving and you provided a departure_time all Route objects within
89
+ # the matrix will have duration_in_traffic_in_seconds. We can query the matrix for this data as well:
90
+ matrix.shortest_route_by_duration_in_traffic_to(dest_address)
87
91
  ```
88
92
 
89
- In cases where you built the place with an object (not hash with attributes) you may provide that object as well asking for routes. This is true for `route_for` and `shortest_route_by_*` as well.
93
+ In cases where you built the place with an object (not hash with attributes) you may provide that object
94
+ as well asking for routes. This is true for `route_for` and `shortest_route_by_*` as well.
90
95
 
91
96
  ```ruby
92
97
  matrix.routes_for point_dest # Returns routes for dest_object
93
98
  ```
94
99
 
95
- You may call query methods with a bang, in which case it will fail with an error if not all of the routes in your result set for the called method are ok.
100
+ You may call query methods with a bang, in which case it will fail with an error if not all of the
101
+ routes in your result set for the called method are ok.
96
102
 
97
103
 
98
104
  ## Installation
@@ -117,6 +123,19 @@ Configuration is done directly on a matrix or via `GoogleDistanceMatrix.configur
117
123
  Apart from configuration on requests it is also possible to provide your own logger class and
118
124
  set a cache.
119
125
 
126
+ ### Shorting the URL using encoded coordinates
127
+ Instead of lat and lng values in the URL it is possible to use encoded set of coordinates
128
+ using the Encoded Polyline Algorithm. This is particularly useful if you have a large
129
+ number of origin points, because the URL is significantly shorter when
130
+ using an encoded polyline.
131
+
132
+ ```ruby
133
+ GoogleDistanceMatrix.configure_defaults do |config|
134
+ config.use_encoded_polylines = true
135
+ end
136
+ ```
137
+
138
+
120
139
  ### Request cache
121
140
 
122
141
  Given Google's limit to the service you may have the need to cache requests. This is done by simply
@@ -124,10 +143,10 @@ using URL as cache keys. Cache we'll accept should provide a default ActiveSuppo
124
143
 
125
144
  ```ruby
126
145
  GoogleDistanceMatrix.configure_defaults do |config|
127
- config.cache = ActiveSupport::Cache.lookup_store :your_store, {
128
- expires_in: 12.hours
129
- # ..or other options you like for your store
130
- }
146
+ config.cache = ActiveSupport::Cache.lookup_store :your_store, {
147
+ expires_in: 12.hours
148
+ # ..or other options you like for your store
149
+ }
131
150
  end
132
151
  ```
133
152
 
@@ -17,8 +17,8 @@ require "google_distance_matrix/matrix"
17
17
  require "google_distance_matrix/places"
18
18
  require "google_distance_matrix/place"
19
19
  require "google_distance_matrix/route"
20
+ require "google_distance_matrix/polyline_encoder"
20
21
 
21
- require "google_distance_matrix/log_subscriber"
22
22
  require 'google_distance_matrix/railtie' if defined? Rails
23
23
 
24
24
 
@@ -37,3 +37,5 @@ module GoogleDistanceMatrix
37
37
  @logger ||= Logger.new default_configuration.logger
38
38
  end
39
39
  end
40
+
41
+ require "google_distance_matrix/log_subscriber"
@@ -9,6 +9,7 @@ module GoogleDistanceMatrix
9
9
  class Configuration
10
10
  include ActiveModel::Validations
11
11
 
12
+ # Attributes we'll include building URL for our matrix
12
13
  ATTRIBUTES = %w[
13
14
  mode avoid units language
14
15
  departure_time arrival_time
@@ -19,19 +20,41 @@ module GoogleDistanceMatrix
19
20
  API_DEFAULTS = {
20
21
  mode: "driving",
21
22
  units: "metric",
22
- traffic_model: "best_guess"
23
+ traffic_model: "best_guess",
24
+ use_encoded_polylines: false,
25
+ protocol: 'https',
26
+ lat_lng_scale: 5,
27
+ filter_parameters_in_logged_url: ['key', 'signature'].freeze
23
28
  }.with_indifferent_access
24
29
 
25
- attr_accessor *ATTRIBUTES, :protocol, :logger, :lat_lng_scale
30
+ attr_accessor *ATTRIBUTES
31
+
32
+ # The protocol to use, either http or https
33
+ attr_accessor :protocol
34
+
35
+ # lat_lng_scale is used for each Place when we include it's lat and lng values in the URL.
36
+ # Defaults to 5 decimals, but you can set it lower to save characters in the URL.
37
+ #
38
+ # Speaking of saving characters. If you use_encoded_polylines all Places which has lat/lng
39
+ # will use encoded set of coordinates using the Encoded Polyline Algorithm.
40
+ # This is particularly useful if you have a large number of origin points,
41
+ # because the URL is significantly shorter when using an encoded polyline.
42
+ # See: https://developers.google.com/maps/documentation/distance-matrix/intro#RequestParameters
43
+ attr_accessor :lat_lng_scale, :use_encoded_polylines
44
+
45
+ # Google credentials
26
46
  attr_accessor :google_business_api_client_id, :google_business_api_private_key, :google_api_key
27
- attr_accessor :cache
28
47
 
48
+ attr_accessor :cache, :logger
49
+
50
+ # When logging we filter sensitive parameters
51
+ attr_accessor :filter_parameters_in_logged_url
29
52
 
30
53
  validates :mode, inclusion: {in: ["driving", "walking", "bicycling", "transit"]}, allow_blank: true
31
54
  validates :avoid, inclusion: {in: ["tolls", "highways", "ferries", "indoor"]}, allow_blank: true
32
55
  validates :units, inclusion: {in: ["metric", "imperial"]}, allow_blank: true
33
56
 
34
- validates :departure_time, numericality: true, allow_blank: true
57
+ validates :departure_time, format: /\A(\d+|now)\Z/, allow_blank: true
35
58
  validates :arrival_time, numericality: true, allow_blank: true
36
59
 
37
60
  validates :transit_mode, inclusion: {in: %w[bus subway train tram rail]}, allow_blank: true
@@ -41,11 +64,8 @@ module GoogleDistanceMatrix
41
64
  validates :protocol, inclusion: {in: ["http", "https"]}, allow_blank: true
42
65
 
43
66
  def initialize
44
- self.protocol = "https"
45
- self.lat_lng_scale = 5
46
-
47
67
  API_DEFAULTS.each_pair do |attr_name, value|
48
- self[attr_name] = value
68
+ self[attr_name] = value.dup rescue value
49
69
  end
50
70
  end
51
71
 
@@ -14,6 +14,13 @@ module GoogleDistanceMatrix
14
14
  end
15
15
  end
16
16
 
17
+ # Public: Raised when we query the matrix for something it cannot answer.
18
+ #
19
+ # Example: Asking it for shortest_route_by_duration_in_traffic when the
20
+ # matrix data has no such data.
21
+ class InvalidQuery < Error
22
+ end
23
+
17
24
  # Public: Route seems invalid
18
25
  #
19
26
  # Fails if a route is built, but it's status from
@@ -1,11 +1,27 @@
1
1
  module GoogleDistanceMatrix
2
2
  class LogSubscriber < ActiveSupport::LogSubscriber
3
+ attr_reader :logger, :config
4
+
5
+ def initialize(logger: GoogleDistanceMatrix.logger, config: GoogleDistanceMatrix.default_configuration)
6
+ super()
7
+
8
+ @logger = logger
9
+ @config = config
10
+ end
11
+
3
12
  def client_request_matrix_data(event)
4
- logger.info "(#{event.duration}ms) (elements: #{event.payload[:elements]}) GET #{event.payload[:url]}", tag: :client
13
+ url = filter_url! event.payload[:url]
14
+ logger.info "(#{event.duration}ms) (elements: #{event.payload[:elements]}) GET #{url}", tag: :client
5
15
  end
6
16
 
7
- def logger
8
- GoogleDistanceMatrix.logger
17
+ private
18
+
19
+ def filter_url!(url)
20
+ config.filter_parameters_in_logged_url.each do |param|
21
+ url.gsub! %r{(#{param})=.*?(&|$)}, '\1=[FILTERED]\2'
22
+ end
23
+
24
+ url
9
25
  end
10
26
  end
11
27
  end
@@ -54,10 +54,12 @@ module GoogleDistanceMatrix
54
54
  end
55
55
 
56
56
 
57
- delegate :route_for, :routes_for, to: :routes_finder
57
+ delegate :route_for, :routes_for, to: :routes_finder
58
58
  delegate :route_for!, :routes_for!, to: :routes_finder
59
- delegate :shortest_route_by_distance_to, :shortest_route_by_duration_to, to: :routes_finder
60
- delegate :shortest_route_by_distance_to!, :shortest_route_by_duration_to!, to: :routes_finder
59
+ delegate :shortest_route_by_distance_to, :shortest_route_by_duration_to, to: :routes_finder
60
+ delegate :shortest_route_by_distance_to!, :shortest_route_by_duration_to!, to: :routes_finder
61
+ delegate :shortest_route_by_duration_in_traffic_to, to: :routes_finder
62
+ delegate :shortest_route_by_duration_in_traffic_to!, to: :routes_finder
61
63
 
62
64
 
63
65
  # Public: The data for this matrix.
@@ -42,6 +42,10 @@ module GoogleDistanceMatrix
42
42
  end
43
43
  end
44
44
 
45
+ def lat_lng?
46
+ lat.present? && lng.present?
47
+ end
48
+
45
49
  def lat_lng(scale = nil)
46
50
  [lat, lng].map do |v|
47
51
  if scale
@@ -0,0 +1,47 @@
1
+ require_relative 'polyline_encoder/delta'
2
+ require_relative 'polyline_encoder/value_encoder'
3
+
4
+ module GoogleDistanceMatrix
5
+ # Encodes a set of lat/lng pairs in to a polyline
6
+ # according to Google's Encoded Polyline Algorithm Format.
7
+ #
8
+ # See https://developers.google.com/maps/documentation/utilities/polylinealgorithm
9
+ class PolylineEncoder
10
+
11
+ # Encodes a set of lat/lng pairs
12
+ #
13
+ # Example
14
+ # encoded = PolylineEncoder.encode [[lat, lng], [lat, lng]]
15
+ def self.encode(array_of_lat_lng_pairs)
16
+ new(array_of_lat_lng_pairs).encode
17
+ end
18
+
19
+
20
+ # Initialize a new encoder
21
+ #
22
+ # Arguments
23
+ # array_of_lat_lng_pairs - The array of lat/lng pairs, like [[lat, lng], [lat, lng], ..etc]
24
+ # delta - An object responsible for rounding and calculate the deltas
25
+ # between the given lat/lng pairs.
26
+ # value_encoder - After deltas are calculated each value is passed to the encoder
27
+ # to be encoded in to ASCII characters
28
+ #
29
+ # @see ::encode
30
+ def initialize(array_of_lat_lng_pairs, delta: Delta.new, value_encoder: ValueEncoder.new)
31
+ @array_of_lat_lng_pairs = array_of_lat_lng_pairs
32
+ @delta = delta
33
+ @value_encoder = value_encoder
34
+ @encoded = nil
35
+ end
36
+
37
+ # Encode and returns the encoded string
38
+ def encode
39
+ return @encoded if @encoded
40
+
41
+ deltas = @delta.deltas_rounded @array_of_lat_lng_pairs
42
+ chars_array = deltas.map { |v| @value_encoder.encode v }
43
+
44
+ @encoded = chars_array.join
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ module GoogleDistanceMatrix
2
+ class PolylineEncoder
3
+ # Calculates deltas between lat_lng values, internal helper class for PolylineEncoder.
4
+ #
5
+ # According to the Google's polyline encoding spec:
6
+ # "Additionally, to conserve space, points only include the offset
7
+ # from the previous point (except of course for the first point)"
8
+ #
9
+ # @see GoogleDistanceMatrix::PolylineEncoder
10
+ class Delta
11
+ def initialize(precision = 1e5)
12
+ @precision = precision
13
+ end
14
+
15
+ # Takes a set of lat/lng pairs and calculates delta
16
+ #
17
+ # Returns a flatten array where each lat/lng delta pair is put in order.
18
+ def deltas_rounded(array_of_lat_lng_pairs)
19
+ rounded = round_to_precision array_of_lat_lng_pairs
20
+ calculate_deltas rounded
21
+ end
22
+
23
+
24
+ private
25
+
26
+ def round_to_precision(array_of_lat_lng_pairs)
27
+ array_of_lat_lng_pairs.map do |(lat, lng)|
28
+ [
29
+ (lat * @precision).round,
30
+ (lng * @precision).round
31
+ ]
32
+ end
33
+ end
34
+
35
+ def calculate_deltas(rounded)
36
+ deltas = []
37
+
38
+ delta_lat = 0
39
+ delta_lng = 0
40
+
41
+ rounded.each do |(lat, lng)|
42
+ deltas << lat - delta_lat
43
+ deltas << lng - delta_lng
44
+
45
+ delta_lat, delta_lng = lat, lng
46
+ end
47
+
48
+ deltas
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,61 @@
1
+ module GoogleDistanceMatrix
2
+ class PolylineEncoder
3
+ # Encodes a single value, like 17998321, in to encoded polyline value,
4
+ # as described in Google's documentation
5
+ # https://developers.google.com/maps/documentation/utilities/polylinealgorithm
6
+ #
7
+ # This is an internal helper class for PolylineEncoder.
8
+ # This encoder expects that the value is rounded.
9
+ #
10
+ # @see GoogleDistanceMatrix::PolylineEncoder
11
+ class ValueEncoder
12
+ def encode(value)
13
+ negative = value < 0
14
+ value = value.abs
15
+
16
+ # Step 3: Two's complement when negative
17
+ value = ~value + 1 if negative
18
+
19
+ # Step 4: Left shift one bit
20
+ value = value << 1
21
+
22
+ # Step 5: Invert if value was negative
23
+ value = ~value if negative
24
+
25
+ # Step 6 and 7: 5-bit chunks in reverse order
26
+ # We AND 5 first bits and push them on to chunks array.
27
+ # Right shift bits to get rid of the ones we just put on the array.
28
+ # Bits will end up in reverse order.
29
+ chunks_of_5_bits = []
30
+ while value > 0 do
31
+ chunks_of_5_bits.push(value & 0x1f)
32
+ value >>= 5
33
+ end
34
+
35
+ chunks_of_5_bits << 0 if chunks_of_5_bits.empty?
36
+
37
+ # Step 8, 9 and 10: OR each value with 0x20, unless last one. Add 63 to all values
38
+ last_index = chunks_of_5_bits.length - 1
39
+ chunks_of_5_bits.each_with_index do |chunk, index|
40
+ chunks_of_5_bits[index] = chunk | 0x20 unless index == last_index
41
+ chunks_of_5_bits[index] += 63
42
+ end
43
+
44
+ # step 11: Convert to ASCII
45
+ chunks_of_5_bits.map(&:chr)
46
+ end
47
+
48
+ private
49
+
50
+ # Debug method for pretty printing integers as bits.
51
+ #
52
+ # Example of usage
53
+ # p d 17998321 # => "00000001 00010010 10100001 11110001"
54
+ def d(v, bits = 32, chunk_size = 8)
55
+ (bits - 1).downto(0).
56
+ map { |n| v[n] }.
57
+ each_slice(chunk_size).map { |chunk| chunk.join }.join ' '
58
+ end
59
+ end
60
+ end
61
+ end
@@ -5,12 +5,15 @@ module GoogleDistanceMatrix
5
5
  # it's origin and destination.
6
6
  #
7
7
  class Route
8
- STATUSES = %w[ok zero_results not_found]
8
+ STATUSES = %w[ok zero_results not_found].freeze
9
9
 
10
10
  ATTRIBUTES = %w[
11
11
  origin destination
12
- status distance_text distance_in_meters duration_text duration_in_seconds
13
- ]
12
+ status
13
+ distance_text distance_in_meters
14
+ duration_text duration_in_seconds
15
+ duration_in_traffic_text duration_in_traffic_in_seconds
16
+ ].freeze
14
17
 
15
18
  attr_reader *ATTRIBUTES
16
19
 
@@ -30,6 +33,11 @@ module GoogleDistanceMatrix
30
33
  @distance_in_meters = attributes[:distance][:value]
31
34
  @duration_text = attributes[:duration][:text]
32
35
  @duration_in_seconds = attributes[:duration][:value]
36
+
37
+ if attributes.key? :duration_in_traffic
38
+ @duration_in_traffic_text = attributes[:duration_in_traffic][:text]
39
+ @duration_in_traffic_in_seconds = attributes[:duration_in_traffic][:value]
40
+ end
33
41
  end
34
42
  end
35
43
 
@@ -3,7 +3,7 @@ module GoogleDistanceMatrix
3
3
  class RoutesFinder
4
4
 
5
5
  attr_reader :matrix
6
- delegate :data, :origins, :destinations, to: :matrix
6
+ delegate :data, :origins, :destinations, :configuration, to: :matrix
7
7
 
8
8
 
9
9
  def initialize(matrix)
@@ -110,6 +110,34 @@ module GoogleDistanceMatrix
110
110
  routes_for!(place_or_object_place_was_built_from).min_by &:duration_in_seconds
111
111
  end
112
112
 
113
+ # Public: Finds shortes route by duration in traffic to a place.
114
+ #
115
+ # NOTE The matrix must be loaded with mode driving and a departure_time set to
116
+ # get the matrix loaded with duration in traffic.
117
+ #
118
+ # place - The place, or object place was built from, you want the shortest route to
119
+ #
120
+ # Returns shortest route, or nil if no routes had status ok
121
+ def shortest_route_by_duration_in_traffic_to(place_or_object_place_was_built_from)
122
+ ensure_driving_and_departure_time_or_fail!
123
+
124
+ routes = routes_for place_or_object_place_was_built_from
125
+ select_ok_routes(routes).min_by &:duration_in_traffic_in_seconds
126
+ end
127
+
128
+ # Public: Finds shortes route by duration in traffic to a place.
129
+ #
130
+ # NOTE The matrix must be loaded with mode driving and a departure_time set to
131
+ # get the matrix loaded with duration in traffic.
132
+ #
133
+ # place - The place, or object place was built from, you want the shortest route to
134
+ #
135
+ # Returns shortest route, fails if any of the routes are not ok
136
+ def shortest_route_by_duration_in_traffic_to!(place_or_object_place_was_built_from)
137
+ ensure_driving_and_departure_time_or_fail!
138
+
139
+ routes_for!(place_or_object_place_was_built_from).min_by &:duration_in_traffic_in_seconds
140
+ end
113
141
 
114
142
 
115
143
 
@@ -165,5 +193,11 @@ module GoogleDistanceMatrix
165
193
  def select_ok_routes(routes)
166
194
  routes.select &:ok?
167
195
  end
196
+
197
+ def ensure_driving_and_departure_time_or_fail!
198
+ if configuration.mode != 'driving' || configuration.departure_time.nil?
199
+ fail InvalidQuery, "Matrix must be in mode driving and a departure_time must be set"
200
+ end
201
+ end
168
202
  end
169
203
  end
@@ -1,3 +1,5 @@
1
+ require_relative 'url_builder/polyline_encoder_buffer'
2
+
1
3
  module GoogleDistanceMatrix
2
4
  class UrlBuilder
3
5
  BASE_URL = "maps.googleapis.com/maps/api/distancematrix/json"
@@ -48,14 +50,32 @@ module GoogleDistanceMatrix
48
50
  end
49
51
 
50
52
  def params
51
- places_to_param_config = {lat_lng_scale: configuration.lat_lng_scale}
52
-
53
53
  configuration.to_param.merge(
54
- origins: matrix.origins.map { |o| escape o.to_param(places_to_param_config) }.join(DELIMITER),
55
- destinations: matrix.destinations.map { |d| escape d.to_param(places_to_param_config) }.join(DELIMITER),
54
+ origins: places_to_param(matrix.origins),
55
+ destinations: places_to_param(matrix.destinations)
56
56
  )
57
57
  end
58
58
 
59
+ def places_to_param(places)
60
+ places_to_param_config = {lat_lng_scale: configuration.lat_lng_scale}
61
+
62
+ out = []
63
+ polyline_encode_buffer = PolylineEncoderBuffer.new
64
+
65
+ places.each do |place|
66
+ if place.lat_lng? && configuration.use_encoded_polylines
67
+ polyline_encode_buffer << place.lat_lng
68
+ else
69
+ polyline_encode_buffer.flush to: out
70
+ out << escape(place.to_param places_to_param_config)
71
+ end
72
+ end
73
+
74
+ polyline_encode_buffer.flush to: out
75
+
76
+ out.join(DELIMITER)
77
+ end
78
+
59
79
  def protocol
60
80
  configuration.protocol + "://"
61
81
  end
@@ -0,0 +1,26 @@
1
+ module GoogleDistanceMatrix
2
+ class UrlBuilder
3
+ class PolylineEncoderBuffer
4
+ def initialize
5
+ @buffer = []
6
+ end
7
+
8
+ def <<(lat_lng)
9
+ @buffer << lat_lng
10
+ end
11
+
12
+ def flush(to:)
13
+ return if @buffer.empty?
14
+
15
+ to << escape("enc:#{PolylineEncoder.encode @buffer}:")
16
+ @buffer.clear
17
+ end
18
+
19
+ private
20
+
21
+ def escape(string)
22
+ CGI.escape string
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module GoogleDistanceMatrix
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -13,6 +13,18 @@ describe GoogleDistanceMatrix::Configuration do
13
13
  expect(subject.errors[:departure_time].length).to eq 0
14
14
  end
15
15
 
16
+ it 'is valid with "now"' do
17
+ subject.departure_time = 'now'
18
+ subject.valid?
19
+ expect(subject.errors[:departure_time].length).to eq 0
20
+ end
21
+
22
+ it 'is invalid with "123now"' do
23
+ subject.departure_time = '123now'
24
+ subject.valid?
25
+ expect(subject.errors[:departure_time].length).to eq 1
26
+ end
27
+
16
28
  it 'is invalid with something else' do
17
29
  subject.departure_time = 'foo'
18
30
  subject.valid?
@@ -57,6 +69,7 @@ describe GoogleDistanceMatrix::Configuration do
57
69
  it { expect(subject.avoid).to be_nil }
58
70
  it { expect(subject.units).to eq "metric" }
59
71
  it { expect(subject.lat_lng_scale).to eq 5 }
72
+ it { expect(subject.use_encoded_polylines).to eq false }
60
73
  it { expect(subject.protocol).to eq 'https' }
61
74
  it { expect(subject.language).to be_nil }
62
75
 
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module GoogleDistanceMatrix
4
+ describe LogSubscriber do
5
+ class MockLogger
6
+ attr_reader :logged
7
+
8
+ def initialize
9
+ @logged = []
10
+ end
11
+
12
+ def info(msg, tag)
13
+ @logged << msg
14
+ end
15
+
16
+ def error(msg)
17
+ fail msg
18
+ end
19
+ end
20
+
21
+ # Little helper to clean up examples
22
+ def notify(instrumentation)
23
+ ActiveSupport::Notifications.instrument "client_request_matrix_data.google_distance_matrix", instrumentation do
24
+ end
25
+ end
26
+
27
+
28
+ let(:mock_logger) { MockLogger.new }
29
+ let(:config) { Configuration.new }
30
+
31
+ # Attach our own test logger, re-attach the original attached log subscriber after test.
32
+ before do
33
+ @old_subscribers = LogSubscriber.subscribers.dup
34
+ LogSubscriber.subscribers.clear
35
+ LogSubscriber.attach_to "google_distance_matrix", LogSubscriber.new(logger: mock_logger, config: config)
36
+ end
37
+
38
+ after do
39
+ @old_subscribers.each do |subscriber|
40
+ LogSubscriber.attach_to "google_distance_matrix", subscriber
41
+ end
42
+ end
43
+
44
+
45
+ it "logs the url and elements" do
46
+ url = 'https://example.com'
47
+ instrumentation = {url: url, elements: 0}
48
+
49
+ expect { notify instrumentation }.to change(mock_logger.logged, :length).from(0).to 1
50
+
51
+ expect(mock_logger.logged.first).to include "(elements: 0) GET https://example.com"
52
+ end
53
+
54
+ describe "filtering of logged url" do
55
+ it "filters nothing if config has no keys to be filtered" do
56
+ config.filter_parameters_in_logged_url.clear
57
+
58
+ instrumentation = {url: 'https://example.com/?foo=bar&sensitive=secret'}
59
+ notify instrumentation
60
+
61
+ expect(mock_logger.logged.first).to include "https://example.com/?foo=bar&sensitive=secret"
62
+ end
63
+
64
+ it "filters sensitive GET param if config has it in list of params to filter" do
65
+ config.filter_parameters_in_logged_url << 'sensitive'
66
+
67
+ instrumentation = {url: 'https://example.com/?foo=bar&sensitive=secret'}
68
+ notify instrumentation
69
+
70
+ expect(mock_logger.logged.first).to include "https://example.com/?foo=bar&sensitive=[FILTERED]"
71
+ end
72
+
73
+ it "filters key and signature as defaul from configuration" do
74
+ instrumentation = {url: 'https://example.com/?key=bar&signature=secret&other=foo'}
75
+ notify instrumentation
76
+
77
+ expect(mock_logger.logged.first).to include "https://example.com/?key=[FILTERED]&signature=[FILTERED]&other=foo"
78
+ end
79
+
80
+ it "filters all appearances of a param" do
81
+ instrumentation = {url: 'https://example.com/?key=bar&key=secret&other=foo'}
82
+ notify instrumentation
83
+
84
+ expect(mock_logger.logged.first).to include "https://example.com/?key=[FILTERED]&key=[FILTERED]&other=foo"
85
+ end
86
+ end
87
+ end
88
+ end
@@ -81,6 +81,8 @@ describe GoogleDistanceMatrix::Matrix do
81
81
  shortest_route_by_duration_to!
82
82
  shortest_route_by_distance_to
83
83
  shortest_route_by_distance_to!
84
+ shortest_route_by_duration_in_traffic_to
85
+ shortest_route_by_duration_in_traffic_to!
84
86
  ].each do |method|
85
87
  it "delegates #{method} to routes_finder" do
86
88
  finder = double
@@ -94,9 +96,22 @@ describe GoogleDistanceMatrix::Matrix do
94
96
  end
95
97
 
96
98
  describe "making API requests", :request_recordings do
97
- it "loads correctly" do
99
+ it "loads correctly API response data in to route objects" do
98
100
  stub_request(:get, url).to_return body: recorded_request_for(:success)
101
+ expect(subject.data[0][0].distance_text).to eq '2.0 km'
99
102
  expect(subject.data[0][0].distance_in_meters).to eq 2032
103
+ expect(subject.data[0][0].duration_text).to eq '6 mins'
104
+ expect(subject.data[0][0].duration_in_seconds).to eq 367
105
+ end
106
+
107
+ it "loads correctly API response data in to route objects when it includes in traffic data" do
108
+ stub_request(:get, url).to_return body: recorded_request_for(:success_with_in_traffic)
109
+ expect(subject.data[0][0].distance_text).to eq '1.8 km'
110
+ expect(subject.data[0][0].distance_in_meters).to eq 1752
111
+ expect(subject.data[0][0].duration_text).to eq '7 mins'
112
+ expect(subject.data[0][0].duration_in_seconds).to eq 435
113
+ expect(subject.data[0][0].duration_in_traffic_text).to eq '7 mins'
114
+ expect(subject.data[0][0].duration_in_traffic_in_seconds).to eq 405
100
115
  end
101
116
 
102
117
  context "no cache" do
@@ -185,7 +200,7 @@ describe GoogleDistanceMatrix::Matrix do
185
200
  expect(api_request_stub).to have_been_requested
186
201
  end
187
202
 
188
- it "as loaded route with errors correctly" do
203
+ it "adds loaded route with errors correctly" do
189
204
  route = subject.data[0][1]
190
205
 
191
206
  expect(route.status).to eq "zero_results"
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ module GoogleDistanceMatrix
4
+ describe PolylineEncoder::Delta do
5
+ it 'calculates deltas correctly' do
6
+ deltas = subject.deltas_rounded [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]
7
+
8
+ expect(deltas).to eq [
9
+ 3850000, -12020000,
10
+ 220000, -75000,
11
+ 255200, -550300
12
+ ]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ module GoogleDistanceMatrix
4
+ describe PolylineEncoder do
5
+ tests = {
6
+ [[-179.9832104, -179.9832104]] => '`~oia@`~oia@',
7
+ [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] => '_p~iF~ps|U_ulLnnqC_mqNvxq`@',
8
+ [[41.3522171071184, -86.0456299662023],[41.3522171071183, -86.0454368471533]] => 'krk{FdxdlO?e@'
9
+ }
10
+
11
+ tests.each_pair do |lat_lng_values, expected|
12
+ it "encodes #{lat_lng_values} to #{expected}" do
13
+ expect(described_class.encode lat_lng_values).to eq expected
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,6 +5,7 @@ describe GoogleDistanceMatrix::Route do
5
5
  {
6
6
  "distance" => {"text" => "2.0 km", "value" => 2032},
7
7
  "duration" => {"text" =>"6 mins", "value" => 367},
8
+ "duration_in_traffic" => {"text" =>"5 mins", "value" => 301},
8
9
  "status" =>"OK"
9
10
  }
10
11
  end
@@ -16,6 +17,8 @@ describe GoogleDistanceMatrix::Route do
16
17
  it { expect(subject.distance_text).to eq "2.0 km" }
17
18
  it { expect(subject.duration_in_seconds).to eq 367 }
18
19
  it { expect(subject.duration_text).to eq "6 mins" }
20
+ it { expect(subject.duration_in_traffic_in_seconds).to eq 301 }
21
+ it { expect(subject.duration_in_traffic_text).to eq "5 mins" }
19
22
 
20
23
  it { is_expected.to be_ok }
21
24
  end
@@ -21,7 +21,33 @@ describe GoogleDistanceMatrix::RoutesFinder, :request_recordings do
21
21
 
22
22
  subject { described_class.new matrix }
23
23
 
24
- context "success" do
24
+ context "success, with traffic data" do
25
+ before do
26
+ matrix.configure do |c|
27
+ c.departure_time = 'now'
28
+ end
29
+ end
30
+
31
+ let!(:api_request_stub) { stub_request(:get, url).to_return body: recorded_request_for(:success_with_in_traffic) }
32
+
33
+ describe "#shortest_route_by_duration_in_traffic_to" do
34
+ it "returns route representing shortest duration to given origin" do
35
+ expect(subject.shortest_route_by_duration_in_traffic_to(origin_1)).to eq matrix.data[0][0]
36
+ end
37
+
38
+ it "returns route representing shortest duration to given destination" do
39
+ expect(subject.shortest_route_by_duration_in_traffic_to(destination_2)).to eq matrix.data[1][1]
40
+ end
41
+ end
42
+
43
+ describe "#shortest_route_by_duration_in_traffic_to!" do
44
+ it "returns the same as shortest_route_by_duration_in_traffic_to" do
45
+ expect(subject.shortest_route_by_duration_in_traffic_to!(origin_1)).to eq subject.shortest_route_by_duration_in_traffic_to(origin_1)
46
+ end
47
+ end
48
+ end
49
+
50
+ context "success, without in traffic data" do
25
51
  let!(:api_request_stub) { stub_request(:get, url).to_return body: recorded_request_for(:success) }
26
52
 
27
53
  describe "#routes_for" do
@@ -123,6 +149,22 @@ describe GoogleDistanceMatrix::RoutesFinder, :request_recordings do
123
149
  expect(subject.shortest_route_by_duration_to!(origin_1)).to eq subject.shortest_route_by_duration_to(origin_1)
124
150
  end
125
151
  end
152
+
153
+ describe "#shortest_route_by_duration_in_traffic_to" do
154
+ it "returns route representing shortest duration to given origin" do
155
+ expect {
156
+ subject.shortest_route_by_duration_in_traffic_to(origin_1)
157
+ }.to raise_error GoogleDistanceMatrix::InvalidQuery
158
+ end
159
+ end
160
+
161
+ describe "#shortest_route_by_duration_in_traffic_to!" do
162
+ it "returns the same as shortest_route_by_duration_in_traffic_to" do
163
+ expect {
164
+ subject.shortest_route_by_duration_in_traffic_to!(origin_1)
165
+ }.to raise_error GoogleDistanceMatrix::InvalidQuery
166
+ end
167
+ end
126
168
  end
127
169
 
128
170
  context "routes mssing data" do
@@ -3,6 +3,7 @@ require "spec_helper"
3
3
  describe GoogleDistanceMatrix::UrlBuilder do
4
4
  let(:delimiter) { described_class::DELIMITER }
5
5
  let(:comma) { CGI.escape "," }
6
+ let(:colon) { CGI.escape ":" }
6
7
 
7
8
  let(:origin_1) { GoogleDistanceMatrix::Place.new address: "address_origin_1" }
8
9
  let(:origin_2) { GoogleDistanceMatrix::Place.new address: "address_origin_2" }
@@ -79,6 +80,31 @@ describe GoogleDistanceMatrix::UrlBuilder do
79
80
  end
80
81
  end
81
82
 
83
+ describe "use encoded polylines" do
84
+ let(:destination_3) { GoogleDistanceMatrix::Place.new address: "address_destination_3" }
85
+ let(:destination_4) { GoogleDistanceMatrix::Place.new lat: 4, lng: 44 }
86
+ let(:destinations) { [destination_1, destination_2, destination_3, destination_4] }
87
+
88
+ before do
89
+ matrix.configure { |c| c.use_encoded_polylines = true }
90
+ end
91
+
92
+ it "includes places with addresses as addresses" do
93
+ expect(subject.url).to include "origins=address_origin_1#{delimiter}address_origin_2"
94
+ end
95
+
96
+ it "encodes places with lat/lng values togheter, broken up by addresses to keep places order" do
97
+ expect(subject.url).to include(
98
+ # 2 first places encoded togheter as they have lat lng values
99
+ "destinations=enc#{colon}_ibE_mcbA_ibE_mcbA#{colon}#{delimiter}" +
100
+ # encoded polyline broken off by a destination with address
101
+ "address_destination_3#{delimiter}" +
102
+ # We continue to encode the last destination as it's own ony point polyline
103
+ "enc#{colon}_glW_wpkG#{colon}"
104
+ )
105
+ end
106
+ end
107
+
82
108
  describe "configuration" do
83
109
  context 'with google api key set' do
84
110
  before do
@@ -0,0 +1,78 @@
1
+ {
2
+ "destination_addresses" : [
3
+ "Drammensveien 1, 0271 Oslo, Norway",
4
+ "Skjellestadhagen, 1389 Heggedal, Norway"
5
+ ],
6
+ "origin_addresses" : [ "Karl Johans gate, Oslo, Norway", "Askerveien 1, 1384 Asker, Norway" ],
7
+ "rows" : [
8
+ {
9
+ "elements" : [
10
+ {
11
+ "distance" : {
12
+ "text" : "1.8 km",
13
+ "value" : 1752
14
+ },
15
+ "duration" : {
16
+ "text" : "7 mins",
17
+ "value" : 435
18
+ },
19
+ "duration_in_traffic" : {
20
+ "text" : "7 mins",
21
+ "value" : 405
22
+ },
23
+ "status" : "OK"
24
+ },
25
+ {
26
+ "distance" : {
27
+ "text" : "30.5 km",
28
+ "value" : 30501
29
+ },
30
+ "duration" : {
31
+ "text" : "38 mins",
32
+ "value" : 2267
33
+ },
34
+ "duration_in_traffic" : {
35
+ "text" : "36 mins",
36
+ "value" : 2179
37
+ },
38
+ "status" : "OK"
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "elements" : [
44
+ {
45
+ "distance" : {
46
+ "text" : "21.3 km",
47
+ "value" : 21322
48
+ },
49
+ "duration" : {
50
+ "text" : "24 mins",
51
+ "value" : 1438
52
+ },
53
+ "duration_in_traffic" : {
54
+ "text" : "23 mins",
55
+ "value" : 1397
56
+ },
57
+ "status" : "OK"
58
+ },
59
+ {
60
+ "distance" : {
61
+ "text" : "9.6 km",
62
+ "value" : 9550
63
+ },
64
+ "duration" : {
65
+ "text" : "18 mins",
66
+ "value" : 1109
67
+ },
68
+ "duration_in_traffic" : {
69
+ "text" : "18 mins",
70
+ "value" : 1083
71
+ },
72
+ "status" : "OK"
73
+ }
74
+ ]
75
+ }
76
+ ],
77
+ "status" : "OK"
78
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google_distance_matrix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thorbjørn Hermansen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-15 00:00:00.000000000 Z
11
+ date: 2016-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -148,22 +148,30 @@ files:
148
148
  - lib/google_distance_matrix/matrix.rb
149
149
  - lib/google_distance_matrix/place.rb
150
150
  - lib/google_distance_matrix/places.rb
151
+ - lib/google_distance_matrix/polyline_encoder.rb
152
+ - lib/google_distance_matrix/polyline_encoder/delta.rb
153
+ - lib/google_distance_matrix/polyline_encoder/value_encoder.rb
151
154
  - lib/google_distance_matrix/railtie.rb
152
155
  - lib/google_distance_matrix/route.rb
153
156
  - lib/google_distance_matrix/routes_finder.rb
154
157
  - lib/google_distance_matrix/url_builder.rb
158
+ - lib/google_distance_matrix/url_builder/polyline_encoder_buffer.rb
155
159
  - lib/google_distance_matrix/version.rb
156
160
  - spec/lib/google_distance_matrix/client_cache_spec.rb
157
161
  - spec/lib/google_distance_matrix/client_spec.rb
158
162
  - spec/lib/google_distance_matrix/configuration_spec.rb
163
+ - spec/lib/google_distance_matrix/log_subscriber_spec.rb
159
164
  - spec/lib/google_distance_matrix/logger_spec.rb
160
165
  - spec/lib/google_distance_matrix/matrix_spec.rb
161
166
  - spec/lib/google_distance_matrix/place_spec.rb
162
167
  - spec/lib/google_distance_matrix/places_spec.rb
168
+ - spec/lib/google_distance_matrix/polyline_encoder/delta_spec.rb
169
+ - spec/lib/google_distance_matrix/polyline_encoder_spec.rb
163
170
  - spec/lib/google_distance_matrix/route_spec.rb
164
171
  - spec/lib/google_distance_matrix/routes_finder_spec.rb
165
172
  - spec/lib/google_distance_matrix/url_builder_spec.rb
166
173
  - spec/request_recordings/success
174
+ - spec/request_recordings/success_with_in_traffic
167
175
  - spec/request_recordings/zero_results
168
176
  - spec/spec_helper.rb
169
177
  homepage: ''
@@ -194,13 +202,17 @@ test_files:
194
202
  - spec/lib/google_distance_matrix/client_cache_spec.rb
195
203
  - spec/lib/google_distance_matrix/client_spec.rb
196
204
  - spec/lib/google_distance_matrix/configuration_spec.rb
205
+ - spec/lib/google_distance_matrix/log_subscriber_spec.rb
197
206
  - spec/lib/google_distance_matrix/logger_spec.rb
198
207
  - spec/lib/google_distance_matrix/matrix_spec.rb
199
208
  - spec/lib/google_distance_matrix/place_spec.rb
200
209
  - spec/lib/google_distance_matrix/places_spec.rb
210
+ - spec/lib/google_distance_matrix/polyline_encoder/delta_spec.rb
211
+ - spec/lib/google_distance_matrix/polyline_encoder_spec.rb
201
212
  - spec/lib/google_distance_matrix/route_spec.rb
202
213
  - spec/lib/google_distance_matrix/routes_finder_spec.rb
203
214
  - spec/lib/google_distance_matrix/url_builder_spec.rb
204
215
  - spec/request_recordings/success
216
+ - spec/request_recordings/success_with_in_traffic
205
217
  - spec/request_recordings/zero_results
206
218
  - spec/spec_helper.rb