collectiveidea-graticule 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG.txt +61 -0
- data/LICENSE.txt +30 -0
- data/Manifest.txt +84 -0
- data/README.txt +41 -0
- data/Rakefile +143 -0
- data/VERSION +1 -0
- data/bin/geocode +5 -0
- data/graticule.gemspec +147 -0
- data/init.rb +2 -0
- data/lib/graticule.rb +26 -0
- data/lib/graticule/cli.rb +64 -0
- data/lib/graticule/core_ext.rb +15 -0
- data/lib/graticule/distance.rb +18 -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 +112 -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 +52 -0
- data/lib/graticule/geocoder/google.rb +100 -0
- data/lib/graticule/geocoder/host_ip.rb +41 -0
- data/lib/graticule/geocoder/local_search_maps.rb +45 -0
- data/lib/graticule/geocoder/mapquest.rb +87 -0
- data/lib/graticule/geocoder/meta_carta.rb +33 -0
- data/lib/graticule/geocoder/multi.rb +46 -0
- data/lib/graticule/geocoder/multimap.rb +73 -0
- data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
- data/lib/graticule/geocoder/rest.rb +18 -0
- data/lib/graticule/geocoder/yahoo.rb +84 -0
- data/lib/graticule/location.rb +61 -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 +69 -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/success_multiple_results.xml +88 -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/mapquest/multi_result.xml +1 -0
- data/test/fixtures/responses/mapquest/success.xml +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/multimap/missing_params.xml +4 -0
- data/test/fixtures/responses/multimap/no_matches.xml +4 -0
- data/test/fixtures/responses/multimap/success.xml +19 -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 +31 -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/geocoders.rb +56 -0
- data/test/unit/graticule/geocoder/google_test.rb +112 -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/mapquest_test.rb +47 -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/multimap_test.rb +52 -0
- data/test/unit/graticule/geocoder/postcode_anywhere_test.rb +50 -0
- data/test/unit/graticule/geocoder/yahoo_test.rb +48 -0
- data/test/unit/graticule/geocoder_test.rb +27 -0
- data/test/unit/graticule/location_test.rb +73 -0
- metadata +166 -0
@@ -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(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,52 @@
|
|
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(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
|
+
text = xml.to_s
|
45
|
+
raise AddressError, text if text =~ /couldn't find this address! sorry/
|
46
|
+
raise Error, text if text =~ /Your browser sent a request that this server could not understand./
|
47
|
+
raise Error, text if !(text =~ /geo:Point/)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,100 @@
|
|
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 => :unknown, # Unknown location. (Since 2.59)
|
19
|
+
1 => :country, # Country level accuracy. (Since 2.59)
|
20
|
+
2 => :state, # Region (state, province, prefecture, etc.) level accuracy. (Since 2.59)
|
21
|
+
3 => :state, # Sub-region (county, municipality, etc.) level accuracy. (Since 2.59)
|
22
|
+
4 => :city, # Town (city, village) level accuracy. (Since 2.59)
|
23
|
+
5 => :zip, # Post code (zip code) level accuracy. (Since 2.59)
|
24
|
+
6 => :street, # Street level accuracy. (Since 2.59)
|
25
|
+
7 => :street, # Intersection level accuracy. (Since 2.59)
|
26
|
+
8 => :address # Address level accuracy. (Since 2.59)
|
27
|
+
}
|
28
|
+
|
29
|
+
# Creates a new GoogleGeocode that will use Google Maps API +key+.
|
30
|
+
def initialize(key)
|
31
|
+
@key = key
|
32
|
+
@url = URI.parse 'http://maps.google.com/maps/geo'
|
33
|
+
end
|
34
|
+
|
35
|
+
# Locates +address+ returning a Location
|
36
|
+
def locate(address)
|
37
|
+
get :q => address.is_a?(String) ? address : location_from_params(address).to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Extracts a Location from +xml+.
|
43
|
+
def parse_response(xml) #:nodoc:
|
44
|
+
longitude, latitude, = xml.elements['/kml/Response/Placemark/Point/coordinates'].text.split(',').map { |v| v.to_f }
|
45
|
+
returning Location.new(:latitude => latitude, :longitude => longitude) do |l|
|
46
|
+
address = REXML::XPath.first(xml, '//xal:AddressDetails',
|
47
|
+
'xal' => "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0")
|
48
|
+
|
49
|
+
if address
|
50
|
+
l.street = value(address.elements['.//ThoroughfareName/text()'])
|
51
|
+
l.locality = value(address.elements['.//LocalityName/text()'])
|
52
|
+
l.region = value(address.elements['.//AdministrativeAreaName/text()'])
|
53
|
+
l.postal_code = value(address.elements['.//PostalCodeNumber/text()'])
|
54
|
+
l.country = value(address.elements['.//CountryNameCode/text()'])
|
55
|
+
l.precision = PRECISION[address.attribute('Accuracy').value.to_i] || :unknown
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Extracts and raises an error from +xml+, if any.
|
61
|
+
def check_error(xml) #:nodoc:
|
62
|
+
status = xml.elements['/kml/Response/Status/code'].text.to_i
|
63
|
+
case status
|
64
|
+
when 200 then # ignore, ok
|
65
|
+
when 500 then
|
66
|
+
raise Error, 'server error'
|
67
|
+
when 601 then
|
68
|
+
raise AddressError, 'missing address'
|
69
|
+
when 602 then
|
70
|
+
raise AddressError, 'unknown address'
|
71
|
+
when 603 then
|
72
|
+
raise AddressError, 'unavailable address'
|
73
|
+
when 610 then
|
74
|
+
raise CredentialsError, 'invalid key'
|
75
|
+
when 620 then
|
76
|
+
raise CredentialsError, 'too many queries'
|
77
|
+
else
|
78
|
+
raise Error, "unknown error #{status}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Creates a URL from the Hash +params+. Automatically adds the key and
|
83
|
+
# sets the output type to 'xml'.
|
84
|
+
def make_url(params) #:nodoc:
|
85
|
+
params[:key] = @key
|
86
|
+
params[:output] = 'xml'
|
87
|
+
|
88
|
+
super params
|
89
|
+
end
|
90
|
+
|
91
|
+
def value(element)
|
92
|
+
element.value if element
|
93
|
+
end
|
94
|
+
|
95
|
+
def text(element)
|
96
|
+
element.text if element
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Graticule #:nodoc:
|
4
|
+
module Geocoder #:nodoc:
|
5
|
+
|
6
|
+
class HostIp < Base
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@url = URI.parse 'http://api.hostip.info/get_html.php'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Geocode an IP address using http://hostip.info
|
13
|
+
def locate(address)
|
14
|
+
get :ip => address, :position => true
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def prepare_response(response)
|
20
|
+
# add new line so YAML.load doesn't puke
|
21
|
+
YAML.load(response + "\n")
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_response(response) #:nodoc:
|
25
|
+
returning Location.new do |location|
|
26
|
+
location.latitude = response['Latitude']
|
27
|
+
location.longitude = response['Longitude']
|
28
|
+
location.locality, location.region = response['City'].split(', ')
|
29
|
+
country = response['Country'].match(/\((\w+)\)$/)
|
30
|
+
location.country = country[1] if country
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_error(response) #:nodoc:
|
35
|
+
raise AddressError, 'Unknown' if response['City'] =~ /Unknown City/
|
36
|
+
raise AddressError, 'Private Address' if response['City'] =~ /Private Address/
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
# A library for lookup of coordinates with http://geo.localsearchmaps.com/
|
5
|
+
#
|
6
|
+
# See http://emad.fano.us/blog/?p=277
|
7
|
+
class LocalSearchMaps < Base
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@url = URI.parse 'http://geo.localsearchmaps.com/'
|
11
|
+
end
|
12
|
+
|
13
|
+
# This web service will handle some addresses outside the US
|
14
|
+
# if given more structured arguments than just a string address
|
15
|
+
# So allow input as a hash for the different arguments (:city, :country, :zip)
|
16
|
+
def locate(params)
|
17
|
+
get params.is_a?(String) ? {:loc => params} : map_attributes(location_from_params(params))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def map_attributes(location)
|
23
|
+
mapping = {:street => :street, :locality => :city, :region => :state, :postal_code => :zip, :country => :country}
|
24
|
+
mapping.keys.inject({}) do |result,attribute|
|
25
|
+
result[mapping[attribute]] = location.attributes[attribute] unless location.attributes[attribute].blank?
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_error(js)
|
31
|
+
raise AddressError, "Empty Response" if js.nil?
|
32
|
+
raise AddressError, 'Location not found' if js =~ /location not found/
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_response(js)
|
36
|
+
returning Location.new do |location|
|
37
|
+
coordinates = js.match(/map.centerAndZoom\(new GPoint\((.+?), (.+?)\)/)
|
38
|
+
location.longitude = coordinates[1].to_f
|
39
|
+
location.latitude = coordinates[2].to_f
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
# Mapquest requires both a client id and a password, which you can
|
5
|
+
# get by registering at:
|
6
|
+
# http://developer.mapquest.com/Home/Register?_devAPISignup_WAR_devAPISignup_action=signup&_devAPISignup_WAR_devAPISignup_clientType=Developer
|
7
|
+
#
|
8
|
+
# mq = Graticule.service(:mapquest).new(CLIENT_ID, PASSWORD)
|
9
|
+
# location = gg.locate('44 Allen Rd., Lovell, ME 04051')
|
10
|
+
# [42.78942, -86.104424]
|
11
|
+
#
|
12
|
+
class Mapquest < Rest
|
13
|
+
# I would link to the documentation here, but there is none that will do anything but confuse you.
|
14
|
+
|
15
|
+
PRECISION = {
|
16
|
+
'L1' => :address,
|
17
|
+
'I1' => :street,
|
18
|
+
'B1' => :street,
|
19
|
+
'B2' => :street,
|
20
|
+
'B3' => :street,
|
21
|
+
'Z3' => :zip,
|
22
|
+
'Z4' => :zip,
|
23
|
+
'Z2' => :zip,
|
24
|
+
'Z1' => :zip,
|
25
|
+
'A5' => :city,
|
26
|
+
'A4' => :county,
|
27
|
+
'A3' => :state,
|
28
|
+
'A1' => :country
|
29
|
+
}
|
30
|
+
|
31
|
+
def initialize(client_id, password)
|
32
|
+
@password = password
|
33
|
+
@client_id = client_id
|
34
|
+
@url = URI.parse('http://geocode.dev.mapquest.com/mq/mqserver.dll')
|
35
|
+
end
|
36
|
+
|
37
|
+
# Locates +address+ returning a Location
|
38
|
+
def locate(address)
|
39
|
+
get :q => address.is_a?(String) ? address : location_from_params(address).to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def make_url(params) #:nodoc
|
45
|
+
query = "e=5&<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><Geocode Version=\"1\"> \
|
46
|
+
#{address_string(params[:q])}#{authentication_string}</Geocode>"
|
47
|
+
url = @url.dup
|
48
|
+
url.query = URI.escape(query)
|
49
|
+
url
|
50
|
+
end
|
51
|
+
|
52
|
+
# Extracts a location from +xml+.
|
53
|
+
def parse_response(xml) #:nodoc:
|
54
|
+
longitude = xml.elements['/GeocodeResponse/LocationCollection/GeoAddress/LatLng/Lng'].text.to_f
|
55
|
+
latitude = xml.elements['/GeocodeResponse/LocationCollection/GeoAddress/LatLng/Lat'].text.to_f
|
56
|
+
returning Location.new(:latitude => latitude, :longitude => longitude) do |l|
|
57
|
+
address = REXML::XPath.first(xml, '/GeocodeResponse/LocationCollection/GeoAddress')
|
58
|
+
|
59
|
+
if address
|
60
|
+
l.street = value(address.elements['./Street/text()'])
|
61
|
+
l.locality = value(address.elements['./AdminArea5/text()'])
|
62
|
+
l.region = value(address.elements['./AdminArea3/text()'])
|
63
|
+
l.postal_code = value(address.elements['./PostalCode/text()'])
|
64
|
+
l.country = value(address.elements['./AdminArea1/text()'])
|
65
|
+
l.precision = PRECISION[value(address.elements['./ResultCode/text()'])[0,2]]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Extracts and raises any errors in +xml+
|
71
|
+
def check_error(xml) #:nodoc
|
72
|
+
end
|
73
|
+
|
74
|
+
def value(element)
|
75
|
+
element.value if element
|
76
|
+
end
|
77
|
+
|
78
|
+
def authentication_string
|
79
|
+
"<Authentication Version=\"2\"><Password>#{@password}</Password><ClientId>#{@client_id}</ClientId></Authentication>"
|
80
|
+
end
|
81
|
+
|
82
|
+
def address_string(query)
|
83
|
+
"<Address><Street>#{query}</Street></Address><GeocodeOptionsCollection Count=\"0\"/>"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
module Graticule
|
3
|
+
module Geocoder
|
4
|
+
|
5
|
+
# Library for looking up coordinates with MetaCarta's GeoParser API.
|
6
|
+
#
|
7
|
+
# http://labs.metacarta.com/GeoParser/documentation.html
|
8
|
+
class MetaCarta < Rest
|
9
|
+
|
10
|
+
def initialize # :nodoc:
|
11
|
+
@url = URI.parse 'http://labs.metacarta.com/GeoParser/'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Finds +location+ and returns a Location object.
|
15
|
+
def locate(location)
|
16
|
+
get :q => location.is_a?(String) ? location : location_from_params(location).to_s, :output => 'locations'
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def check_error(xml) # :nodoc:
|
22
|
+
raise AddressError, 'bad location' unless xml.elements['Locations/Location']
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_response(xml) # :nodoc:
|
26
|
+
result = xml.elements['/Locations/Location[1]']
|
27
|
+
coords = result.elements['Centroid/gml:Point/gml:coordinates'].text.split ','
|
28
|
+
Location.new :latitude => coords.first.to_f, :longitude => coords.last.to_f
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
class Multi
|
4
|
+
|
5
|
+
# The Multi geocoder allows you to use multiple geocoders in succession.
|
6
|
+
#
|
7
|
+
# geocoder = Graticule.service(:multi).new(
|
8
|
+
# Graticule.service(:google).new("api_key"),
|
9
|
+
# Graticule.service(:yahoo).new("api_key"),
|
10
|
+
# )
|
11
|
+
# geocoder.locate '49423' # <= tries geocoders in succession
|
12
|
+
#
|
13
|
+
# The Multi geocoder will try the geocoders in order if a Graticule::AddressError
|
14
|
+
# is raised. You can customize this behavior by passing in a block to the Multi
|
15
|
+
# geocoder. For example, to try the geocoders until one returns a result with a
|
16
|
+
# high enough precision:
|
17
|
+
#
|
18
|
+
# geocoder = Graticule.service(:multi).new(geocoders) do |result|
|
19
|
+
# [:address, :street].include?(result.precision)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Geocoders will be tried in order until the block returned true for one of the results
|
23
|
+
#
|
24
|
+
def initialize(*geocoders, &acceptable)
|
25
|
+
@acceptable = acceptable || lambda { true }
|
26
|
+
@geocoders = geocoders.flatten
|
27
|
+
end
|
28
|
+
|
29
|
+
def locate(address)
|
30
|
+
last_error = nil
|
31
|
+
@geocoders.each do |geocoder|
|
32
|
+
begin
|
33
|
+
result = geocoder.locate address
|
34
|
+
return result if @acceptable.call(result)
|
35
|
+
rescue Error => e
|
36
|
+
last_error = e
|
37
|
+
rescue Errno::ECONNREFUSED
|
38
|
+
logger.error("Connection refused to #{service}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
raise last_error || AddressError.new("Couldn't find '#{address}' with any of the services")
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|