server-side-google-maps 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/AUTHORS +1 -0
- data/Gemfile +8 -0
- data/LICENSE +3 -0
- data/README.md +48 -0
- data/Rakefile +2 -0
- data/bin/autospec +16 -0
- data/bin/htmldiff +16 -0
- data/bin/httparty +16 -0
- data/bin/ldiff +16 -0
- data/bin/rspec +16 -0
- data/lib/server-side-google-maps.rb +11 -0
- data/lib/server-side-google-maps/directions.rb +137 -0
- data/lib/server-side-google-maps/geo_math.rb +24 -0
- data/lib/server-side-google-maps/route.rb +53 -0
- data/lib/server-side-google-maps/server.rb +13 -0
- data/lib/server-side-google-maps/version.rb +3 -0
- data/server-side-google-maps.gemspec +24 -0
- data/spec/directions_spec.rb +122 -0
- data/spec/files/directions-45.5086700,-73.5536800-to-45.4119000,-75.6984600.txt +344 -0
- data/spec/files/directions-Montreal,QC-to-Ottawa,ON.txt +344 -0
- data/spec/files/directions-Ottawa,ON-to-Toronto,ON.txt +344 -0
- data/spec/route_spec.rb +98 -0
- data/spec/spec.rb +3 -0
- metadata +124 -0
data/.gitignore
ADDED
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Adam Hooper <adam@adamhooper.com>
|
data/Gemfile
ADDED
data/LICENSE
ADDED
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Server-Side Google Maps: map data from Google, for your server
|
2
|
+
|
3
|
+
Make requests for direction from Google's servers, and receive them in a
|
4
|
+
Ruby-usable object.
|
5
|
+
|
6
|
+
## Installation and usage
|
7
|
+
|
8
|
+
To install:
|
9
|
+
|
10
|
+
sudo gem install server-side-google-maps
|
11
|
+
|
12
|
+
Then, to use within Ruby:
|
13
|
+
|
14
|
+
directions = Directions.new('Montreal, QC', 'Ottawa, ON', :mode => :driving)
|
15
|
+
# Origin and destination accept [lat,lon] coordinates as well as strings
|
16
|
+
# :mode => :driving is the default. Others are :bicycling and :walking
|
17
|
+
|
18
|
+
directions.status # 'OK'
|
19
|
+
directions.origin_address # 'Montreal, QC, Canada'
|
20
|
+
directions.origin_point # [ 45.5086700, -73.5536800 ]
|
21
|
+
directions.destination_address # 'Ottawa, ON, Canada'
|
22
|
+
directions.destination_point # [ 45.4119000, -75.6984600 ]
|
23
|
+
directions.points # List of [lat,lon] coordinates of route
|
24
|
+
directions.distance # 199901 (metres)
|
25
|
+
|
26
|
+
route = Route.new(['Montreal, QC', 'Ottawa, ON', 'Toronto, ON'], :mode => :bicycling)
|
27
|
+
# All the same methods apply to route as to directions
|
28
|
+
|
29
|
+
One final `:mode` is `:direct`, which calculates `points` and estimates
|
30
|
+
`distance` without querying Google. To ensure Google isn't queried, input
|
31
|
+
the origin and destination as latitude/longitude coordinates.
|
32
|
+
|
33
|
+
## Limitations
|
34
|
+
|
35
|
+
As per the Google Maps API license agreement, the data returned from the
|
36
|
+
Google Maps API must be used only for display on a Google Map.
|
37
|
+
|
38
|
+
I'll write that again: Google Maps data is for Google Maps only! You may not
|
39
|
+
do any extra calculating and processing, unless the results of those extra
|
40
|
+
calculations are displayed on a Google Map.
|
41
|
+
|
42
|
+
There are also query limits on each Google API, on the order of a few thousand
|
43
|
+
queries each day. Design your software accordingly. (Cache heavily and don't
|
44
|
+
query excessively.)
|
45
|
+
|
46
|
+
## Development
|
47
|
+
|
48
|
+
Each feature and issue must have a spec. Please write specs if you can.
|
data/Rakefile
ADDED
data/bin/autospec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby1.8
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'autospec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('server-side-google-maps', 'autospec')
|
data/bin/htmldiff
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby1.8
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'htmldiff' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('server-side-google-maps', 'htmldiff')
|
data/bin/httparty
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby1.8
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'httparty' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('server-side-google-maps', 'httparty')
|
data/bin/ldiff
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby1.8
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'ldiff' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('server-side-google-maps', 'ldiff')
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby1.8
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('server-side-google-maps', 'rspec')
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Bundler.setup
|
2
|
+
Bundler.require
|
3
|
+
|
4
|
+
require 'server-side-google-maps/version'
|
5
|
+
require 'server-side-google-maps/directions'
|
6
|
+
require 'server-side-google-maps/geo_math'
|
7
|
+
require 'server-side-google-maps/route'
|
8
|
+
require 'server-side-google-maps/server'
|
9
|
+
|
10
|
+
module ServerSideGoogleMaps
|
11
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module ServerSideGoogleMaps
|
2
|
+
class Directions
|
3
|
+
LATLNG_STRING_REGEXP = /(-?\d+(\.?\d+)?),(-?\d+(\.?\d+)?)/
|
4
|
+
|
5
|
+
def self.get(params)
|
6
|
+
server = Server.new
|
7
|
+
server.get('/maps/api/directions', {:sensor => false}.merge(params))
|
8
|
+
end
|
9
|
+
|
10
|
+
# Initializes directions
|
11
|
+
#
|
12
|
+
# Parameters:
|
13
|
+
# origin: string or [lat,lng] of the first point
|
14
|
+
# destination: string or [lat,lng] of the last point
|
15
|
+
# params:
|
16
|
+
# :mode: :driving, :bicycling and :walking will be passed to Google Maps.
|
17
|
+
# Another option, :direct, will avoid in-between points and calculate
|
18
|
+
# the distance using the Haversine formula. Defaults to :driving.
|
19
|
+
# :find_shortcuts: [ {:factor => Float, :mode => :a_mode}, ... ]
|
20
|
+
# For each list item (in the order given), determines if
|
21
|
+
# using given :mode will cut the distance to less than
|
22
|
+
# :factor and if so, chooses it. For example, if :mode is
|
23
|
+
# :bicycling and there's a huge detour because of a missing
|
24
|
+
# bike lane, pass { :factor => 0.5, :mode => :driving }
|
25
|
+
# and if a shortcut cuts the distance in half that route
|
26
|
+
# will be chosen instead.
|
27
|
+
def initialize(origin, destination, params = {})
|
28
|
+
@origin = origin
|
29
|
+
@destination = destination
|
30
|
+
params = params.dup
|
31
|
+
find_shortcuts = params.delete(:find_shortcuts) || []
|
32
|
+
raise ArgumentError.new(':find_shortcuts must be an Array') unless Array === find_shortcuts
|
33
|
+
@direct = params[:mode] == :direct
|
34
|
+
params[:mode] = :driving if params[:mode] == :direct || params[:mode].nil?
|
35
|
+
|
36
|
+
origin = origin.join(',') if Array === origin
|
37
|
+
destination = destination.join(',') if Array === destination
|
38
|
+
|
39
|
+
unless @direct && origin_point_without_server && destination_point_without_server
|
40
|
+
@data = self.class.get(params.merge(:origin => origin, :destination => destination))
|
41
|
+
end
|
42
|
+
|
43
|
+
if !@direct
|
44
|
+
find_shortcuts.each do |try_shortcut|
|
45
|
+
factor = try_shortcut[:factor]
|
46
|
+
mode = try_shortcut[:mode]
|
47
|
+
|
48
|
+
other = Directions.new(origin, destination, params.merge(:mode => mode))
|
49
|
+
if other.distance.to_f / distance < factor
|
50
|
+
@points = other.points
|
51
|
+
@distance = other.distance
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def origin_input
|
58
|
+
@origin
|
59
|
+
end
|
60
|
+
|
61
|
+
def destination_input
|
62
|
+
@destination
|
63
|
+
end
|
64
|
+
|
65
|
+
def origin_address
|
66
|
+
leg['start_address']
|
67
|
+
end
|
68
|
+
|
69
|
+
def destination_address
|
70
|
+
leg['end_address']
|
71
|
+
end
|
72
|
+
|
73
|
+
def origin_point
|
74
|
+
@origin_point ||= origin_point_without_server || [ leg['start_location']['lat'], leg['start_location']['lng'] ]
|
75
|
+
end
|
76
|
+
|
77
|
+
def destination_point
|
78
|
+
@destination_point ||= destination_point_without_server || [ leg['end_location']['lat'], leg['end_location']['lng'] ]
|
79
|
+
end
|
80
|
+
|
81
|
+
def status
|
82
|
+
@data['status']
|
83
|
+
end
|
84
|
+
|
85
|
+
def points
|
86
|
+
@points ||= calculate_points
|
87
|
+
end
|
88
|
+
|
89
|
+
def distance
|
90
|
+
@distance ||= calculate_distance
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def origin_point_without_server
|
96
|
+
calculate_point_without_server_or_nil(origin_input)
|
97
|
+
end
|
98
|
+
|
99
|
+
def destination_point_without_server
|
100
|
+
calculate_point_without_server_or_nil(destination_input)
|
101
|
+
end
|
102
|
+
|
103
|
+
def calculate_point_without_server_or_nil(input)
|
104
|
+
return input if Array === input
|
105
|
+
m = LATLNG_STRING_REGEXP.match(input)
|
106
|
+
return [ m[1].to_f, m[3].to_f ] if m
|
107
|
+
end
|
108
|
+
|
109
|
+
def route
|
110
|
+
@data['routes'].first
|
111
|
+
end
|
112
|
+
|
113
|
+
def leg
|
114
|
+
route['legs'].first
|
115
|
+
end
|
116
|
+
|
117
|
+
def points_and_levels
|
118
|
+
@points_and_levels ||= calculate_points_and_levels
|
119
|
+
end
|
120
|
+
|
121
|
+
def calculate_points_and_levels
|
122
|
+
polyline = route['overview_polyline']
|
123
|
+
::GoogleMapsPolyline.decode_polyline(polyline['points'], polyline['levels'])
|
124
|
+
end
|
125
|
+
|
126
|
+
def calculate_points
|
127
|
+
return [ origin_point, destination_point ] if @direct
|
128
|
+
points = points_and_levels.map { |lat,lng,level| [lat,lng] }
|
129
|
+
points
|
130
|
+
end
|
131
|
+
|
132
|
+
def calculate_distance
|
133
|
+
return GeoMath.latlng_distance(*points) if @direct
|
134
|
+
leg['distance']['value']
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ServerSideGoogleMaps
|
2
|
+
module GeoMath
|
3
|
+
RADIUS_OF_EARTH = 6367000 # metres
|
4
|
+
|
5
|
+
# Returns the distance (in m) between two [lat,lng] points with the Haversine formula
|
6
|
+
def self.latlng_distance(pt1, pt2)
|
7
|
+
lat1 = pt1[0] * Math::PI / 180
|
8
|
+
lon1 = pt1[1] * Math::PI / 180
|
9
|
+
lat2 = pt2[0] * Math::PI / 180
|
10
|
+
lon2 = pt2[1] * Math::PI / 180
|
11
|
+
dlon = lon2 - lon1
|
12
|
+
dlat = lat2 - lat1
|
13
|
+
a = Math.sin(dlat/2)**2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dlon/2)**2
|
14
|
+
c = 2 * Math.asin(min(1.0, Math.sqrt(a)))
|
15
|
+
(RADIUS_OF_EARTH * c).to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.min(f1, f2)
|
21
|
+
f1 < f2 ? f1 : f2
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ServerSideGoogleMaps
|
2
|
+
class Route
|
3
|
+
def initialize(points, params = {})
|
4
|
+
raise ArgumentError if points.length < 2
|
5
|
+
|
6
|
+
@directionses = points[0..-2].zip(points[1..-1]).map do |origin, destination|
|
7
|
+
Directions.new(origin, destination, params)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def origin_input
|
12
|
+
@directionses.first.origin_input
|
13
|
+
end
|
14
|
+
|
15
|
+
def destination_input
|
16
|
+
@directionses.last.destination_input
|
17
|
+
end
|
18
|
+
|
19
|
+
def origin_address
|
20
|
+
@directionses.first.origin_address
|
21
|
+
end
|
22
|
+
|
23
|
+
def destination_address
|
24
|
+
@directionses.last.destination_address
|
25
|
+
end
|
26
|
+
|
27
|
+
def origin_point
|
28
|
+
@directionses.first.origin_point
|
29
|
+
end
|
30
|
+
|
31
|
+
def destination_point
|
32
|
+
@directionses.last.destination_point
|
33
|
+
end
|
34
|
+
|
35
|
+
def points
|
36
|
+
@points ||= calculate_points
|
37
|
+
end
|
38
|
+
|
39
|
+
def distance
|
40
|
+
@distance ||= @directionses.map{|d| d.distance}.inject(:+)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def calculate_points
|
46
|
+
pointses = @directionses.map { |d| d.points }
|
47
|
+
|
48
|
+
first = pointses.shift
|
49
|
+
|
50
|
+
first + pointses.map{ |p| p[1..-1] }.flatten(1)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "server-side-google-maps/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "server-side-google-maps"
|
7
|
+
s.version = ServerSideGoogleMaps::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Adam Hooper"]
|
10
|
+
s.email = ["adam@adamhooper.com"]
|
11
|
+
s.homepage = "http://github.com/adamh/server-side-google-maps"
|
12
|
+
s.summary = %q{Performs calculations with Google Maps}
|
13
|
+
s.description = %q{Servers can use Google Maps, too. This library helps fetch and parse data through the Google Maps v3 API. Stick to the terms of usage, though, and don't use the data Google gives you on anything other than a Google map.}
|
14
|
+
|
15
|
+
s.add_dependency('httparty')
|
16
|
+
s.add_dependency('nayutaya-googlemaps-polyline', '0.0.1')
|
17
|
+
|
18
|
+
s.rubyforge_project = "server-side-google-maps"
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|