google_distance_matrix 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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