google_distance_matrix 0.0.1

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.
Files changed (34) hide show
  1. data/.gitignore +17 -0
  2. data/.rbenv-version +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +106 -0
  6. data/Rakefile +1 -0
  7. data/google_distance_matrix.gemspec +30 -0
  8. data/lib/google_distance_matrix.rb +38 -0
  9. data/lib/google_distance_matrix/client.rb +47 -0
  10. data/lib/google_distance_matrix/configuration.rb +68 -0
  11. data/lib/google_distance_matrix/errors.rb +88 -0
  12. data/lib/google_distance_matrix/log_subscriber.rb +14 -0
  13. data/lib/google_distance_matrix/logger.rb +32 -0
  14. data/lib/google_distance_matrix/matrix.rb +122 -0
  15. data/lib/google_distance_matrix/place.rb +101 -0
  16. data/lib/google_distance_matrix/places.rb +43 -0
  17. data/lib/google_distance_matrix/railtie.rb +9 -0
  18. data/lib/google_distance_matrix/route.rb +49 -0
  19. data/lib/google_distance_matrix/routes_finder.rb +149 -0
  20. data/lib/google_distance_matrix/url_builder.rb +63 -0
  21. data/lib/google_distance_matrix/version.rb +3 -0
  22. data/spec/lib/google_distance_matrix/client_spec.rb +67 -0
  23. data/spec/lib/google_distance_matrix/configuration_spec.rb +63 -0
  24. data/spec/lib/google_distance_matrix/logger_spec.rb +38 -0
  25. data/spec/lib/google_distance_matrix/matrix_spec.rb +169 -0
  26. data/spec/lib/google_distance_matrix/place_spec.rb +93 -0
  27. data/spec/lib/google_distance_matrix/places_spec.rb +77 -0
  28. data/spec/lib/google_distance_matrix/route_spec.rb +28 -0
  29. data/spec/lib/google_distance_matrix/routes_finder_spec.rb +190 -0
  30. data/spec/lib/google_distance_matrix/url_builder_spec.rb +105 -0
  31. data/spec/request_recordings/success +62 -0
  32. data/spec/request_recordings/zero_results +57 -0
  33. data/spec/spec_helper.rb +24 -0
  34. metadata +225 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p194
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in google_distance_matrix.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Thorbjørn Hermansen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # GoogleDistanceMatrix
2
+
3
+ Ruby client for The Google Distance Matrix API.
4
+
5
+ This lib makes Google's distance matrix API easy to work with,
6
+ allowing you to set up some origins and destinations and
7
+ pull the distance matrix from Google.
8
+
9
+ Once you have the matrix you can fetch all routes from a given
10
+ origin or to a given destination.
11
+
12
+ The matrix may also be used as a data set for traveling salesman problem,
13
+ but to solve it you may look at http://ai4r.org/.
14
+
15
+
16
+
17
+
18
+ ## Examples
19
+
20
+ ### Set up a matrix to work with
21
+
22
+ matrix = GoogleDistanceMatrix::Matrix.new
23
+
24
+ ### Create some places to be used as origins or destinations for the matrix
25
+
26
+ lat_lng = GoogleDistanceMatrix::Place.new lng: 12, lat: 12
27
+ address = GoogleDistanceMatrix::Place.new address: "My address, Oslo"
28
+ dest_address = GoogleDistanceMatrix::Place.new address: "Home, Oppegaard"
29
+
30
+ point_dest = Point.new lat: 1, lng: 2
31
+ dest_object = GoogleDistanceMatrix::Place.new point_dest
32
+
33
+ ### Add places to matrix's origins and destinations
34
+
35
+ matrix.origins << lat_lng << address
36
+ matrix.destinations << dest_address << dest_object
37
+
38
+ # Added objects will be wrapped in a place automatically, so you may skip manyally creating Places.
39
+ another_point = Point.new lat: 1, lng: 3
40
+ matrix.origins << another_point
41
+
42
+ ### Do some configuration - see GoogleDistanceMatrix.default_configuration as well.
43
+
44
+ matrix.configure do |config|
45
+ config.mode = 'driving'
46
+ config.avoid = ['tolls']
47
+
48
+ # To build signed URLs to use with a Google Business account
49
+ config.google_business_api_client_id = "123"
50
+ config.google_business_api_private_key = "your-secret-key"
51
+ end
52
+
53
+ ### Get the data for the matrix
54
+
55
+ # Returns the data, loaded from Google, for this matrix.
56
+ # It is a multi dimensional array. Rows are ordered according to the values in the origins.
57
+ # Each row corresponds to an origin, and each element within that row corresponds to
58
+ # a pairing of the origin with a destination.
59
+ matrix.data
60
+
61
+ ### Query the matrix
62
+
63
+ # Returns an array of Google::DistanceMatrix::Route, all having given origin or destination.
64
+ matrix.routes_for dest_address
65
+
66
+ # Returns Google::DistanceMatrix::Route with given origin and destination
67
+ matrix.route_for origin: lat_lng, destination: dest_address
68
+
69
+ matrix.shortest_route_by_distance_to(dest_address) # returns Google::DistanceMatrix::Route with one origin and a destination, together with route data
70
+ matrix.shortest_route_by_duration_to(dest_address) # returns Google::DistanceMatrix::Route with one origin and a destination, together with route data
71
+
72
+ # In cases you built the place with an object (not hash with attributes) you may provide that object
73
+ # as well asking for routes. This is true for route_for and shortest_route_by_* as well.
74
+ matrix.routes_for point_dest # Returns routes for dest_object
75
+
76
+ You may call query methods with a bang, in which case it will fail with an error if noe all routes in your result set for the called method is ok.
77
+
78
+
79
+
80
+
81
+
82
+ ## Installation
83
+
84
+ Add this line to your application's Gemfile:
85
+
86
+ gem 'google_distance_matrix'
87
+
88
+ And then execute:
89
+
90
+ $ bundle
91
+
92
+ Or install it yourself as:
93
+
94
+ $ gem install google_distance_matrix
95
+
96
+
97
+
98
+
99
+
100
+ ## Contributing
101
+
102
+ 1. Fork it
103
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
104
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
105
+ 4. Push to the branch (`git push origin my-new-feature`)
106
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'google_distance_matrix/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "google_distance_matrix"
8
+ spec.version = GoogleDistanceMatrix::VERSION
9
+ spec.authors = ["Thorbjørn Hermansen"]
10
+ spec.email = ["thhermansen@gmail.com"]
11
+ spec.description = %q{Ruby client for The Google Distance Matrix API}
12
+ spec.summary = %q{Ruby client for The Google Distance Matrix API}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport", "~> 3.2.13"
22
+ spec.add_dependency "activemodel", "~> 3.2.13"
23
+ spec.add_dependency "google_business_api_url_signer", "~> 0.0.1"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rspec", "~> 2.13"
27
+ spec.add_development_dependency "shoulda-matchers", "~> 2.0.0"
28
+ spec.add_development_dependency "webmock", "~> 1.11"
29
+ spec.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,38 @@
1
+ require "google_distance_matrix/version"
2
+
3
+ require "cgi"
4
+ require "json"
5
+ require "active_model"
6
+ require "active_support/core_ext/hash"
7
+ require "google_business_api_url_signer"
8
+
9
+ require "google_distance_matrix/logger"
10
+ require "google_distance_matrix/errors"
11
+ require "google_distance_matrix/configuration"
12
+ require "google_distance_matrix/url_builder"
13
+ require "google_distance_matrix/client"
14
+ require "google_distance_matrix/routes_finder"
15
+ require "google_distance_matrix/matrix"
16
+ require "google_distance_matrix/places"
17
+ require "google_distance_matrix/place"
18
+ require "google_distance_matrix/route"
19
+
20
+ require "google_distance_matrix/log_subscriber"
21
+ require 'google_distance_matrix/railtie' if defined? Rails
22
+
23
+
24
+ module GoogleDistanceMatrix
25
+ extend self
26
+
27
+ def default_configuration
28
+ @default_configuration ||= Configuration.new
29
+ end
30
+
31
+ def configure_defaults
32
+ yield default_configuration
33
+ end
34
+
35
+ def logger
36
+ @logger ||= Logger.new default_configuration.logger
37
+ end
38
+ end
@@ -0,0 +1,47 @@
1
+ module GoogleDistanceMatrix
2
+ class Client
3
+ CLIENT_ERRORS = %w[
4
+ INVALID_REQUEST
5
+ MAX_ELEMENTS_EXCEEDED
6
+ OVER_QUERY_LIMIT
7
+ REQUEST_DENIED
8
+ UNKNOWN_ERROR
9
+ ]
10
+
11
+ def get(url)
12
+ uri = URI.parse url
13
+
14
+ response = ActiveSupport::Notifications.instrument "client_request_matrix_data.google_distance_matrix", url: url do
15
+ Net::HTTP.get_response uri
16
+ end
17
+
18
+ case response
19
+ when Net::HTTPSuccess
20
+ inspect_for_client_errors! response
21
+ when Net::HTTPRequestURITooLong
22
+ fail MatrixUrlTooLong.new url, UrlBuilder::MAX_URL_SIZE, response
23
+ when Net::HTTPClientError
24
+ fail ClientError.new response
25
+ when Net::HTTPServerError
26
+ fail RequestError.new response
27
+ else # Handle this as a request error for now. Maybe fine tune this more later.
28
+ fail RequestError.new response
29
+ end
30
+ rescue Timeout::Error => error
31
+ fail RequestError.new error
32
+ end
33
+
34
+
35
+ private
36
+
37
+ def inspect_for_client_errors!(response)
38
+ status = JSON.parse(response.body).fetch "status"
39
+
40
+ if CLIENT_ERRORS.include? status
41
+ fail ClientError.new response, status
42
+ end
43
+
44
+ response
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,68 @@
1
+ module GoogleDistanceMatrix
2
+ # Public: Configuration of matrix and it's request.
3
+ #
4
+ # Holds configuration used when building API URL.
5
+ #
6
+ # See https://developers.google.com/maps/documentation/distancematrix/#RequestParameters
7
+ # for documentation on each configuration.
8
+ #
9
+ class Configuration
10
+ include ActiveModel::Validations
11
+
12
+ ATTRIBUTES = %w[sensor mode avoid units]
13
+
14
+ API_DEFAULTS = {
15
+ mode: "driving",
16
+ units: "metric"
17
+ }.with_indifferent_access
18
+
19
+ attr_accessor *ATTRIBUTES, :protocol, :logger, :lat_lng_scale
20
+ attr_accessor :google_business_api_client_id, :google_business_api_private_key
21
+
22
+
23
+ validates :sensor, inclusion: {in: [true, false]}
24
+ validates :mode, inclusion: {in: ["driving", "walking", "bicycling"]}, allow_blank: true
25
+ validates :avoid, inclusion: {in: ["tolls", "highways"]}, allow_blank: true
26
+ validates :units, inclusion: {in: ["metric", "imperial"]}, allow_blank: true
27
+
28
+ validates :protocol, inclusion: {in: ["http", "https"]}, allow_blank: true
29
+
30
+
31
+ def initialize
32
+ self.sensor = false
33
+ self.protocol = "http"
34
+ self.lat_lng_scale = 5
35
+
36
+ API_DEFAULTS.each_pair do |attr_name, value|
37
+ self[attr_name] = value
38
+ end
39
+ end
40
+
41
+ def to_param
42
+ Hash[array_param]
43
+ end
44
+
45
+ def []=(attr_name, value)
46
+ public_send "#{attr_name}=", value
47
+ end
48
+
49
+
50
+ private
51
+
52
+ def array_param
53
+ out = ATTRIBUTES.map { |attr| [attr, public_send(attr)] }.reject do |attr_and_value|
54
+ attr_and_value[1].nil? || param_same_as_api_default?(attr_and_value)
55
+ end
56
+
57
+ if google_business_api_client_id.present?
58
+ out << ['client', google_business_api_client_id]
59
+ end
60
+
61
+ out
62
+ end
63
+
64
+ def param_same_as_api_default?(param)
65
+ API_DEFAULTS[param[0]] == param[1]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,88 @@
1
+ module GoogleDistanceMatrix
2
+ # Public: Error class for lib.
3
+ class Error < StandardError
4
+ end
5
+
6
+ # Public: Matrix has errors.
7
+ class InvalidMatrix < Error
8
+ def initialize(matrix)
9
+ @matrix = matrix
10
+ end
11
+
12
+ def to_s
13
+ @matrix.errors.full_messages.to_sentence
14
+ end
15
+ end
16
+
17
+ # Public: Route seems invalid
18
+ #
19
+ # Fails if a route is built, but it's status from
20
+ # Google isn't OK.
21
+ #
22
+ class InvalidRoute < Error
23
+ def initialize(route)
24
+ @route = route
25
+ end
26
+
27
+ def to_s
28
+ "API did not provide a complete answer for #{@route}."
29
+ end
30
+ end
31
+
32
+ # Public: Got a request error back trying to do a request
33
+ #
34
+ # This includes wire errors like timeouts etc, and server errors
35
+ # like 5xx. Inspect error_or_response for more information.
36
+ #
37
+ class RequestError < Error
38
+ attr_reader :error_or_response
39
+
40
+ def initialize(error_or_response)
41
+ @error_or_response = error_or_response
42
+ end
43
+
44
+ def to_s
45
+ "GoogleDistanceMatrix::RequestError - #{error_or_response.inspect}."
46
+ end
47
+ end
48
+
49
+ # Public: Got an error where the client seems to be doing something wrong
50
+ #
51
+ # These errors comes from http 4xx errors, or API errors like MAX_ELEMENTS_EXCEEDED etc.
52
+ # See https://developers.google.com/maps/documentation/distancematrix/#StatusCodes for info.
53
+ #
54
+ class ClientError < Error
55
+ attr_reader :response, :status_read_from_api_response
56
+
57
+ def initialize(response, status_read_from_api_response = nil)
58
+ @response = response
59
+ @status_read_from_api_response = status_read_from_api_response
60
+ end
61
+
62
+ def to_s
63
+ "GoogleDistanceMatrix::ClientError - #{[response, status_read_from_api_response].compact.join('. ')}."
64
+ end
65
+ end
66
+
67
+ # Public: API URL was too long
68
+ #
69
+ # See https://developers.google.com/maps/documentation/distancematrix/#Limits, which states:
70
+ # "Distance Matrix API URLs are restricted to 2048 characters, before URL encoding."
71
+ # "As some Distance Matrix API service URLs may involve many locations, be aware of this limit when constructing your URLs."
72
+ #
73
+ class MatrixUrlTooLong < ClientError
74
+ attr_reader :url, :max_url_size
75
+
76
+ def initialize(url, max_url_size, response = nil)
77
+ super response
78
+
79
+ @url = url
80
+ @max_url_size = max_url_size
81
+ end
82
+
83
+ def to_s
84
+ "Matrix API URL max size is: #{max_url_size}. Built URL was: #{url.length}. URL: '#{url}'."
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,14 @@
1
+ module GoogleDistanceMatrix
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def self.logger
4
+ GoogleDistanceMatrix.logger
5
+ end
6
+
7
+ def client_request_matrix_data(event)
8
+ logger.info "(#{event.duration}ms) GET #{event.payload[:url]}", tag: :client
9
+ end
10
+ end
11
+ end
12
+
13
+ GoogleDistanceMatrix::LogSubscriber.attach_to "google_distance_matrix"
14
+