GUI-graticule 0.2.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +46 -0
- data/LICENSE.txt +30 -0
- data/Manifest.txt +73 -0
- data/README.txt +29 -0
- data/Rakefile +86 -0
- data/bin/geocode +5 -0
- data/init.rb +2 -0
- data/lib/graticule.rb +26 -0
- data/lib/graticule/cli.rb +64 -0
- data/lib/graticule/distance.rb +29 -0
- data/lib/graticule/distance/haversine.rb +40 -0
- data/lib/graticule/distance/spherical.rb +52 -0
- data/lib/graticule/distance/vincenty.rb +76 -0
- data/lib/graticule/geocoder.rb +21 -0
- data/lib/graticule/geocoder/base.rb +138 -0
- data/lib/graticule/geocoder/bogus.rb +15 -0
- data/lib/graticule/geocoder/geocoder_ca.rb +54 -0
- data/lib/graticule/geocoder/geocoder_us.rb +49 -0
- data/lib/graticule/geocoder/google.rb +122 -0
- data/lib/graticule/geocoder/host_ip.rb +41 -0
- data/lib/graticule/geocoder/local_search_maps.rb +45 -0
- data/lib/graticule/geocoder/map_quest.rb +111 -0
- data/lib/graticule/geocoder/meta_carta.rb +33 -0
- data/lib/graticule/geocoder/multi.rb +80 -0
- data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
- data/lib/graticule/geocoder/rest.rb +18 -0
- data/lib/graticule/geocoder/yahoo.rb +102 -0
- data/lib/graticule/location.rb +488 -0
- data/lib/graticule/version.rb +9 -0
- data/site/index.html +114 -0
- data/site/plugin.html +82 -0
- data/site/stylesheets/style.css +73 -0
- data/test/config.yml.default +36 -0
- data/test/fixtures/responses/geocoder_us/success.xml +18 -0
- data/test/fixtures/responses/geocoder_us/unknown.xml +1 -0
- data/test/fixtures/responses/google/badkey.xml +1 -0
- data/test/fixtures/responses/google/limit.xml +10 -0
- data/test/fixtures/responses/google/missing_address.xml +1 -0
- data/test/fixtures/responses/google/only_coordinates.xml +1 -0
- data/test/fixtures/responses/google/partial.xml +1 -0
- data/test/fixtures/responses/google/server_error.xml +10 -0
- data/test/fixtures/responses/google/success.xml +1 -0
- data/test/fixtures/responses/google/unavailable.xml +1 -0
- data/test/fixtures/responses/google/unknown_address.xml +1 -0
- data/test/fixtures/responses/host_ip/private.txt +4 -0
- data/test/fixtures/responses/host_ip/success.txt +4 -0
- data/test/fixtures/responses/host_ip/unknown.txt +4 -0
- data/test/fixtures/responses/local_search_maps/empty.txt +1 -0
- data/test/fixtures/responses/local_search_maps/not_found.txt +1 -0
- data/test/fixtures/responses/local_search_maps/success.txt +1 -0
- data/test/fixtures/responses/meta_carta/bad_address.xml +17 -0
- data/test/fixtures/responses/meta_carta/multiple.xml +33 -0
- data/test/fixtures/responses/meta_carta/success.xml +31 -0
- data/test/fixtures/responses/postcode_anywhere/badkey.xml +9 -0
- data/test/fixtures/responses/postcode_anywhere/canada.xml +16 -0
- data/test/fixtures/responses/postcode_anywhere/empty.xml +16 -0
- data/test/fixtures/responses/postcode_anywhere/success.xml +16 -0
- data/test/fixtures/responses/postcode_anywhere/uk.xml +18 -0
- data/test/fixtures/responses/yahoo/success.xml +3 -0
- data/test/fixtures/responses/yahoo/unknown_address.xml +6 -0
- data/test/mocks/uri.rb +52 -0
- data/test/test_helper.rb +32 -0
- data/test/unit/graticule/distance_test.rb +58 -0
- data/test/unit/graticule/geocoder/geocoder_us_test.rb +43 -0
- data/test/unit/graticule/geocoder/google_test.rb +126 -0
- data/test/unit/graticule/geocoder/host_ip_test.rb +40 -0
- data/test/unit/graticule/geocoder/local_search_maps_test.rb +30 -0
- data/test/unit/graticule/geocoder/meta_carta_test.rb +44 -0
- data/test/unit/graticule/geocoder/multi_test.rb +43 -0
- data/test/unit/graticule/geocoder/postcode_anywhere_test.rb +50 -0
- data/test/unit/graticule/geocoder/yahoo_test.rb +58 -0
- data/test/unit/graticule/geocoder_test.rb +27 -0
- data/test/unit/graticule/location_test.rb +66 -0
- metadata +158 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module Graticule
|
2
|
+
module Distance
|
3
|
+
|
4
|
+
#
|
5
|
+
# The Spherical Law of Cosines is the simplist though least accurate distance
|
6
|
+
# formula (earth isn't a perfect sphere).
|
7
|
+
#
|
8
|
+
class Spherical < DistanceFormula
|
9
|
+
|
10
|
+
# Calculate the distance between two Locations using the Spherical formula
|
11
|
+
#
|
12
|
+
# Graticule::Distance::Spherical.distance(
|
13
|
+
# Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
|
14
|
+
# Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
|
15
|
+
# )
|
16
|
+
# #=> 101.061720831853
|
17
|
+
#
|
18
|
+
def self.distance(from, to, units = :miles)
|
19
|
+
from_longitude = deg2rad(from.longitude)
|
20
|
+
from_latitude = deg2rad(from.latitude)
|
21
|
+
to_longitude = deg2rad(to.longitude)
|
22
|
+
to_latitude = deg2rad(to.latitude)
|
23
|
+
|
24
|
+
Math.acos(
|
25
|
+
Math.sin(from_latitude) *
|
26
|
+
Math.sin(to_latitude) +
|
27
|
+
|
28
|
+
Math.cos(from_latitude) *
|
29
|
+
Math.cos(to_latitude) *
|
30
|
+
Math.cos(to_longitude - from_longitude)
|
31
|
+
) * EARTH_RADIUS[units.to_sym]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.to_sql(options)
|
35
|
+
options = {
|
36
|
+
:units => :miles,
|
37
|
+
:latitude_column => 'latitude',
|
38
|
+
:longitude_column => 'longitude'
|
39
|
+
}.merge(options)
|
40
|
+
%{(ACOS(
|
41
|
+
SIN(RADIANS(#{options[:latitude]})) *
|
42
|
+
SIN(RADIANS(#{options[:latitude_column]})) +
|
43
|
+
COS(RADIANS(#{options[:latitude]})) *
|
44
|
+
COS(RADIANS(#{options[:latitude_column]})) *
|
45
|
+
COS(RADIANS(#{options[:longitude_column]}) - RADIANS(#{options[:longitude]}))
|
46
|
+
) * #{Graticule::Distance::EARTH_RADIUS[options[:units].to_sym]})
|
47
|
+
}.gsub("\n", '').squeeze(" ")
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Graticule
|
2
|
+
module Distance
|
3
|
+
|
4
|
+
#
|
5
|
+
# The Vincenty Formula uses an ellipsoidal model of the earth, which is very accurate.
|
6
|
+
#
|
7
|
+
# Thanks to Chris Veness (http://www.movable-type.co.uk/scripts/LatLongVincenty.html)
|
8
|
+
# for distance formulas.
|
9
|
+
#
|
10
|
+
class Vincenty < DistanceFormula
|
11
|
+
|
12
|
+
# Calculate the distance between two Locations using the Vincenty formula
|
13
|
+
#
|
14
|
+
# Graticule::Distance::Vincenty.distance(
|
15
|
+
# Graticule::Location.new(:latitude => 42.7654, :longitude => -86.1085),
|
16
|
+
# Graticule::Location.new(:latitude => 41.849838, :longitude => -87.648193)
|
17
|
+
# )
|
18
|
+
# #=> 101.070118000159
|
19
|
+
#
|
20
|
+
def self.distance(from, to, units = :miles)
|
21
|
+
from_longitude = deg2rad(from.longitude)
|
22
|
+
from_latitude = deg2rad(from.latitude)
|
23
|
+
to_longitude = deg2rad(to.longitude)
|
24
|
+
to_latitude = deg2rad(to.latitude)
|
25
|
+
|
26
|
+
earth_major_axis_radius = EARTH_MAJOR_AXIS_RADIUS[units.to_sym]
|
27
|
+
earth_minor_axis_radius = EARTH_MINOR_AXIS_RADIUS[units.to_sym]
|
28
|
+
|
29
|
+
f = (earth_major_axis_radius - earth_minor_axis_radius) / earth_major_axis_radius
|
30
|
+
|
31
|
+
l = to_longitude - from_longitude
|
32
|
+
u1 = atan((1-f) * tan(from_latitude))
|
33
|
+
u2 = atan((1-f) * tan(to_latitude))
|
34
|
+
sin_u1 = sin(u1)
|
35
|
+
cos_u1 = cos(u1)
|
36
|
+
sin_u2 = sin(u2)
|
37
|
+
cos_u2 = cos(u2)
|
38
|
+
|
39
|
+
lambda = l
|
40
|
+
lambda_p = 2 * PI
|
41
|
+
iteration_limit = 20
|
42
|
+
while (lambda-lambda_p).abs > 1e-12 && (iteration_limit -= 1) > 0
|
43
|
+
sin_lambda = sin(lambda)
|
44
|
+
cos_lambda = cos(lambda)
|
45
|
+
sin_sigma = sqrt((cos_u2*sin_lambda) * (cos_u2*sin_lambda) +
|
46
|
+
(cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda) * (cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda))
|
47
|
+
return 0 if sin_sigma == 0 # co-incident points
|
48
|
+
cos_sigma = sin_u1*sin_u2 + cos_u1*cos_u2*cos_lambda
|
49
|
+
sigma = atan2(sin_sigma, cos_sigma)
|
50
|
+
sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin_sigma
|
51
|
+
cosSqAlpha = 1 - sin_alpha*sin_alpha
|
52
|
+
cos2SigmaM = cos_sigma - 2*sin_u1*sin_u2/cosSqAlpha
|
53
|
+
|
54
|
+
cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
|
55
|
+
|
56
|
+
c = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha))
|
57
|
+
lambda_p = lambda
|
58
|
+
lambda = l + (1-c) * f * sin_alpha *
|
59
|
+
(sigma + c*sin_sigma*(cos2SigmaM+c*cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)))
|
60
|
+
end
|
61
|
+
# formula failed to converge (happens on antipodal points)
|
62
|
+
# We'll call Haversine formula instead.
|
63
|
+
return Haversine.distance(from, to, units) if iteration_limit == 0
|
64
|
+
|
65
|
+
uSq = cosSqAlpha * (earth_major_axis_radius**2 - earth_minor_axis_radius**2) / (earth_minor_axis_radius**2)
|
66
|
+
a = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)))
|
67
|
+
b = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)))
|
68
|
+
delta_sigma = b*sin_sigma*(cos2SigmaM+b/4*(cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)-
|
69
|
+
b/6*cos2SigmaM*(-3+4*sin_sigma*sin_sigma)*(-3+4*cos2SigmaM*cos2SigmaM)))
|
70
|
+
|
71
|
+
earth_minor_axis_radius * a * (sigma-delta_sigma)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Graticule
|
3
|
+
|
4
|
+
# Get a geocoder for the given service
|
5
|
+
#
|
6
|
+
# geocoder = Graticule.service(:google).new "api_key"
|
7
|
+
#
|
8
|
+
# See the documentation for your specific geocoder for more information
|
9
|
+
#
|
10
|
+
def self.service(name)
|
11
|
+
Geocoder.const_get name.to_s.camelize
|
12
|
+
end
|
13
|
+
|
14
|
+
# Base error class
|
15
|
+
class Error < RuntimeError; end
|
16
|
+
class CredentialsError < Error; end
|
17
|
+
|
18
|
+
# Raised when you try to locate an invalid address.
|
19
|
+
class AddressError < Error; end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require "cgi"
|
3
|
+
|
4
|
+
module Graticule #:nodoc:
|
5
|
+
module Geocoder
|
6
|
+
|
7
|
+
# Abstract class for implementing geocoders.
|
8
|
+
#
|
9
|
+
# === Example
|
10
|
+
#
|
11
|
+
# The following methods must be implemented in sublcasses:
|
12
|
+
#
|
13
|
+
# * +initialize+:: Sets @url to the service enpoint.
|
14
|
+
# * +check_error+:: Checks for errors in the server response.
|
15
|
+
# * +parse_response+:: Extracts information from the server response.
|
16
|
+
#
|
17
|
+
# Optionally, you can also override
|
18
|
+
#
|
19
|
+
# * +prepare_response+:: Convert the string response into a different format
|
20
|
+
# that gets passed on to +check_error+ and +parse_response+.
|
21
|
+
#
|
22
|
+
# If you have extra URL paramaters (application id, output type) or need to
|
23
|
+
# perform URL customization, override +make_url+.
|
24
|
+
#
|
25
|
+
# class FakeGeocoder < Base
|
26
|
+
#
|
27
|
+
# def initialize(appid)
|
28
|
+
# @appid = appid
|
29
|
+
# @url = URI.parse 'http://example.com/test'
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# def locate(query)
|
33
|
+
# get :q => query
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# private
|
37
|
+
#
|
38
|
+
# def check_error(xml)
|
39
|
+
# raise Error, xml.elements['error'].text if xml.elements['error']
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def make_url(params)
|
43
|
+
# params[:appid] = @appid
|
44
|
+
# super params
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# def parse_first(response)
|
48
|
+
# # return Location
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
class Base
|
54
|
+
include Comparable
|
55
|
+
|
56
|
+
USER_AGENT = "Mozilla/5.0 (compatible; Graticule/#{Graticule::Version::STRING}; http://graticule.rubyforge.org)"
|
57
|
+
|
58
|
+
attr_accessor :preference
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
def <=>(other)
|
65
|
+
other.preference <=> preference
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def location_from_params(params)
|
71
|
+
case params
|
72
|
+
when Location then params
|
73
|
+
when Hash then Location.new params
|
74
|
+
else
|
75
|
+
raise ArgumentError, "Expected a Graticule::Location or a hash with :street, :locality, :region, :postal_code, and :country attributes"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Check for errors in +response+ and raise appropriate error, if any.
|
80
|
+
# Must return if no error could be found.
|
81
|
+
def check_error(response)
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
85
|
+
# Performs a GET request with +params+. Calls +check_error+ and returns
|
86
|
+
# the result of +parse_response+.
|
87
|
+
def get(*args)
|
88
|
+
params = args.last.is_a?(::Hash) ? args.pop : args
|
89
|
+
response = prepare_response(make_url(params).open('User-Agent' => USER_AGENT).read)
|
90
|
+
check_error(response)
|
91
|
+
|
92
|
+
result = parse_response(args.first, response)
|
93
|
+
|
94
|
+
# Make sure the parse_response method was overriden by a subclass to
|
95
|
+
# return a Location object. If it was, set its geocoder attribute to
|
96
|
+
# the current geocoder, so we know where the result came from.
|
97
|
+
if(result.kind_of? Array)
|
98
|
+
result.each do |location|
|
99
|
+
if(location.instance_of? Location)
|
100
|
+
location.geocoder = self
|
101
|
+
end
|
102
|
+
end
|
103
|
+
elsif(result.instance_of? Location)
|
104
|
+
result.geocoder = self
|
105
|
+
end
|
106
|
+
|
107
|
+
result
|
108
|
+
rescue OpenURI::HTTPError => e
|
109
|
+
check_error(prepare_response(e.io.read))
|
110
|
+
raise
|
111
|
+
end
|
112
|
+
|
113
|
+
# Creates a URI from the Hash +params+. Override this then call super if
|
114
|
+
# you need to add extra params like an application id or output type.
|
115
|
+
def make_url(params)
|
116
|
+
escaped_params = params.sort_by { |k,v| k.to_s }.map do |k,v|
|
117
|
+
"#{CGI.escape k.to_s}=#{CGI.escape v.to_s}"
|
118
|
+
end
|
119
|
+
|
120
|
+
url = @url.dup
|
121
|
+
url.query = escaped_params.join '&'
|
122
|
+
return url
|
123
|
+
end
|
124
|
+
|
125
|
+
# Override to convert the response to something besides a String, which
|
126
|
+
# will get passed to +check_error+ and +parse_response+.
|
127
|
+
def prepare_response(response)
|
128
|
+
response
|
129
|
+
end
|
130
|
+
|
131
|
+
# Must parse :first or :all results from +response+ into a Location.
|
132
|
+
def parse_response(parse_type, response)
|
133
|
+
raise NotImplementedError
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
# Bogus geocoder that can be used for test purposes
|
5
|
+
class Bogus
|
6
|
+
|
7
|
+
# returns a new location with the address set to the original query string
|
8
|
+
def locate(address)
|
9
|
+
Location.new :street => address
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
# TODO: Reverse Geocoding
|
5
|
+
class GeocoderCa < Rest
|
6
|
+
|
7
|
+
def initialize(auth = nil)
|
8
|
+
@url = URI.parse 'http://geocoder.ca/'
|
9
|
+
@auth = auth
|
10
|
+
end
|
11
|
+
|
12
|
+
def locate(address)
|
13
|
+
get :locate => address.is_a?(String) ? address : location_from_params(address).to_s(:country => false)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse_response(parse_type, xml) #:nodoc:
|
19
|
+
returning Location.new do |location|
|
20
|
+
location.latitude = xml.elements['geodata/latt'].text.to_f
|
21
|
+
location.longitude = xml.elements['geodata/longt'].text.to_f
|
22
|
+
location.street = xml.elements['geodata/standard/staddress'].text
|
23
|
+
location.locality = xml.elements['geodata/standard/city'].text
|
24
|
+
location.region = xml.elements['geodata/standard/prov'].text
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_error(xml) #:nodoc:
|
29
|
+
error = xml.elements['geodata/error']
|
30
|
+
if error
|
31
|
+
code = error.elements['code'].text.to_i
|
32
|
+
message = error.elements['description'].text
|
33
|
+
if (1..3).include?(code)
|
34
|
+
raise CredentialsError, message
|
35
|
+
elsif (4..8).include?(code)
|
36
|
+
raise AddressError, message
|
37
|
+
else
|
38
|
+
raise Error, message
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def make_url(params) #:nodoc:
|
44
|
+
params[:auth] = @auth if @auth
|
45
|
+
params[:standard] = 1
|
46
|
+
params[:showpostal] = 1
|
47
|
+
params[:geoit] = 'XML'
|
48
|
+
super params
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
# A library for lookup up coordinates with geocoder.us' API.
|
5
|
+
#
|
6
|
+
# http://geocoder.us/help/
|
7
|
+
class GeocoderUs < Rest
|
8
|
+
|
9
|
+
# Creates a new GeocoderUs object optionally using +username+ and
|
10
|
+
# +password+.
|
11
|
+
#
|
12
|
+
# You can sign up for a geocoder.us account here:
|
13
|
+
#
|
14
|
+
# http://geocoder.us/user/signup
|
15
|
+
def initialize(user = nil, password = nil)
|
16
|
+
if user and password then
|
17
|
+
@url = URI.parse 'http://geocoder.us/member/service/rest/geocode'
|
18
|
+
@url.user = user
|
19
|
+
@url.password = password
|
20
|
+
else
|
21
|
+
@url = URI.parse 'http://rpc.geocoder.us/service/rest/geocode'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Locates +address+ and returns the address' latitude and longitude or
|
26
|
+
# raises an AddressError.
|
27
|
+
def locate(address)
|
28
|
+
get :address => address.is_a?(String) ? address : location_from_params(address).to_s(:country => false)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_response(parse_type, xml) #:nodoc:
|
34
|
+
location = Location.new
|
35
|
+
location.street = xml.elements['rdf:RDF/geo:Point/dc:description'].text
|
36
|
+
|
37
|
+
location.latitude = xml.elements['rdf:RDF/geo:Point/geo:lat'].text.to_f
|
38
|
+
location.longitude = xml.elements['rdf:RDF/geo:Point/geo:long'].text.to_f
|
39
|
+
|
40
|
+
return location
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_error(xml) #:nodoc:
|
44
|
+
raise AddressError, xml.text if xml.text =~ /couldn't find this address! sorry/
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
|
2
|
+
module Graticule #:nodoc:
|
3
|
+
module Geocoder #:nodoc:
|
4
|
+
|
5
|
+
# First you need a Google Maps API key. You can register for one here:
|
6
|
+
# http://www.google.com/apis/maps/signup.html
|
7
|
+
#
|
8
|
+
# gg = Graticule.service(:google).new(MAPS_API_KEY)
|
9
|
+
# location = gg.locate '1600 Amphitheater Pkwy, Mountain View, CA'
|
10
|
+
# p location.coordinates
|
11
|
+
# #=> [37.423111, -122.081783
|
12
|
+
#
|
13
|
+
class Google < Rest
|
14
|
+
# http://www.google.com/apis/maps/documentation/#Geocoding_HTTP_Request
|
15
|
+
|
16
|
+
# http://www.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy
|
17
|
+
PRECISION = {
|
18
|
+
0 => Precision.unknown, # Unknown location. (Since 2.59)
|
19
|
+
1 => Precision.country, # Country level accuracy. (Since 2.59)
|
20
|
+
2 => Precision.state, # Region (state, province, prefecture, etc.) level accuracy. (Since 2.59)
|
21
|
+
3 => Precision.state, # Sub-region (county, municipality, etc.) level accuracy. (Since 2.59)
|
22
|
+
4 => Precision.city, # Town (city, village) level accuracy. (Since 2.59)
|
23
|
+
5 => Precision.zip, # Post code (zip code) level accuracy. (Since 2.59)
|
24
|
+
6 => Precision.street, # Street level accuracy. (Since 2.59)
|
25
|
+
7 => Precision.street, # Intersection level accuracy. (Since 2.59)
|
26
|
+
8 => Precision.address, # Address level accuracy. (Since 2.59)
|
27
|
+
9 => Precision.premise # Premise (building name, property name, shopping center, etc.) level accuracy. (Since 2.105)
|
28
|
+
}
|
29
|
+
|
30
|
+
# Creates a new GoogleGeocode that will use Google Maps API +key+.
|
31
|
+
def initialize(key)
|
32
|
+
@key = key
|
33
|
+
@url = URI.parse 'http://maps.google.com/maps/geo'
|
34
|
+
end
|
35
|
+
|
36
|
+
# Locates +address+ returning a Location
|
37
|
+
def locate(*args)
|
38
|
+
case args.first
|
39
|
+
when :first then get :first, :q => extract_location(args[1])
|
40
|
+
when :all then get :all, :q => extract_location(args[1])
|
41
|
+
else get :first, :q => extract_location(args.first)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def extract_location(address)
|
48
|
+
address.is_a?(String) ? address : location_from_params(address).to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
# Extracts a Location from +xml+.
|
52
|
+
def parse_response(parse_type, xml) #:nodoc:
|
53
|
+
# puts xml.elements['//Placemark[1]'].to_s
|
54
|
+
if parse_type == :all
|
55
|
+
locations = []
|
56
|
+
xml.elements.each("//Placemark") do |element|
|
57
|
+
locations << parse_location_xml(element)
|
58
|
+
end
|
59
|
+
return locations
|
60
|
+
else
|
61
|
+
return parse_location_xml(xml.elements['//Placemark[1]'])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_location_xml(xml)
|
66
|
+
longitude, latitude, = xml.elements['.//Point/coordinates'].text.split(',').map { |v| v.to_f }
|
67
|
+
returning Location.new(:latitude => latitude, :longitude => longitude) do |l|
|
68
|
+
address = REXML::XPath.first(xml, './/xal:AddressDetails',
|
69
|
+
'xal' => "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0")
|
70
|
+
if address
|
71
|
+
l.premise = value(address.elements['.//PremiseName/text()'])
|
72
|
+
l.street = value(address.elements['.//ThoroughfareName/text()'])
|
73
|
+
l.locality = value(address.elements['.//LocalityName/text()'])
|
74
|
+
l.region = value(address.elements['.//AdministrativeAreaName/text()'])
|
75
|
+
l.postal_code = value(address.elements['.//PostalCodeNumber/text()'])
|
76
|
+
l.country = value(address.elements['.//CountryNameCode/text()'])
|
77
|
+
l.precision = PRECISION[address.attribute('Accuracy').value.to_i] || Precision.unknown
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Extracts and raises an error from +xml+, if any.
|
83
|
+
def check_error(xml) #:nodoc:
|
84
|
+
status = xml.elements['/kml/Response/Status/code'].text.to_i
|
85
|
+
case status
|
86
|
+
when 200 then # ignore, ok
|
87
|
+
when 500 then
|
88
|
+
raise Error, 'server error'
|
89
|
+
when 601 then
|
90
|
+
raise AddressError, 'missing address'
|
91
|
+
when 602 then
|
92
|
+
raise AddressError, 'unknown address'
|
93
|
+
when 603 then
|
94
|
+
raise AddressError, 'unavailable address'
|
95
|
+
when 610 then
|
96
|
+
raise CredentialsError, 'invalid key'
|
97
|
+
when 620 then
|
98
|
+
raise CredentialsError, 'too many queries'
|
99
|
+
else
|
100
|
+
raise Error, "unknown error #{status}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Creates a URL from the Hash +params+. Automatically adds the key and
|
105
|
+
# sets the output type to 'xml'.
|
106
|
+
def make_url(params) #:nodoc:
|
107
|
+
params[:key] = @key
|
108
|
+
params[:output] = 'xml'
|
109
|
+
|
110
|
+
super params
|
111
|
+
end
|
112
|
+
|
113
|
+
def value(element)
|
114
|
+
element.value if element
|
115
|
+
end
|
116
|
+
|
117
|
+
def text(element)
|
118
|
+
element.text if element
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|