graticule 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +11 -0
- data/Manifest.txt +26 -11
- data/README.txt +1 -1
- data/bin/geocode +1 -1
- data/lib/graticule.rb +10 -8
- data/lib/graticule/cli.rb +1 -0
- data/lib/graticule/geocoder.rb +1 -12
- data/lib/graticule/geocoder/bogus.rb +15 -0
- data/lib/graticule/geocoder/geocoder_ca.rb +52 -0
- data/lib/graticule/geocoder/geocoder_us.rb +47 -0
- data/lib/graticule/geocoder/google.rb +106 -0
- data/lib/graticule/geocoder/host_ip.rb +50 -0
- data/lib/graticule/geocoder/local_search_maps.rb +45 -0
- data/lib/graticule/geocoder/meta_carta.rb +38 -0
- data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
- data/lib/graticule/geocoder/rest.rb +110 -0
- data/lib/graticule/geocoder/yahoo.rb +96 -0
- data/lib/graticule/location.rb +19 -7
- data/lib/graticule/version.rb +2 -2
- 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/success.txt +1 -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/test_helper.rb +4 -2
- data/test/unit/graticule/geocoder/geocoder_us_test.rb +43 -0
- data/test/unit/graticule/geocoder/google_test.rb +66 -0
- data/test/unit/graticule/geocoder/host_ip_test.rb +39 -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/postcode_anywhere_test.rb +50 -0
- data/test/unit/graticule/geocoder/yahoo_test.rb +48 -0
- data/test/unit/graticule/geocoder_test.rb +5 -9
- data/test/unit/graticule/location_test.rb +22 -7
- metadata +37 -18
- data/lib/graticule/geocoders/bogus.rb +0 -11
- data/lib/graticule/geocoders/geocoder_us.rb +0 -45
- data/lib/graticule/geocoders/google.rb +0 -99
- data/lib/graticule/geocoders/meta_carta.rb +0 -102
- data/lib/graticule/geocoders/rest.rb +0 -98
- data/lib/graticule/geocoders/yahoo.rb +0 -101
- data/test/unit/graticule/geocoders/geocoder_us_test.rb +0 -42
- data/test/unit/graticule/geocoders/geocoders.rb +0 -56
- data/test/unit/graticule/geocoders/google_test.rb +0 -22
- data/test/unit/graticule/geocoders/meta_carta_test.rb +0 -70
- data/test/unit/graticule/geocoders/yahoo_test.rb +0 -49
@@ -0,0 +1,38 @@
|
|
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
|
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 make_url(params) # :nodoc:
|
26
|
+
params[:output] = 'locations'
|
27
|
+
super params
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_response(xml) # :nodoc:
|
31
|
+
result = xml.elements['/Locations/Location[1]']
|
32
|
+
coords = result.elements['Centroid/gml:Point/gml:coordinates'].text.split ','
|
33
|
+
Location.new :latitude => coords.first.to_f, :longitude => coords.last.to_f
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
class PostcodeAnywhere < Rest
|
5
|
+
|
6
|
+
# http://www.postcodeanywhere.com/register/
|
7
|
+
def initialize(account_code, license_code)
|
8
|
+
@url = URI.parse 'http://services.postcodeanywhere.co.uk/xml.aspx'
|
9
|
+
@account_code = account_code
|
10
|
+
@license_code = license_code
|
11
|
+
end
|
12
|
+
|
13
|
+
def locate(params)
|
14
|
+
location = location_from_params(params)
|
15
|
+
get :address => location.to_s(:country => false), :country => location.country
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def make_url(params) #:nodoc:
|
21
|
+
params[:account_code] = @account_code
|
22
|
+
params[:license_code] = @license_code
|
23
|
+
params[:action] = 'geocode'
|
24
|
+
super params
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_response(xml) #:nodoc:
|
28
|
+
result = xml.elements['/PostcodeAnywhere/Data/Item[1]']
|
29
|
+
returning Location.new do |location|
|
30
|
+
location.latitude = result.attribute('latitude').value.to_f
|
31
|
+
location.longitude = result.attribute('longitude').value.to_f
|
32
|
+
location.street = value(result.attribute('line1'))
|
33
|
+
location.locality = value(result.attribute('city'))
|
34
|
+
location.region = value(result.attribute('state'))
|
35
|
+
location.postal_code = value(result.attribute('postal_code'))
|
36
|
+
location.country = value(result.attribute('country'))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def value(attribute)
|
41
|
+
attribute.value if attribute
|
42
|
+
end
|
43
|
+
|
44
|
+
# http://www.postcodeanywhere.co.uk/developers/documentation/errors.aspx
|
45
|
+
def check_error(xml) #:nodoc:
|
46
|
+
#raise AddressError, xml.text if xml.text == 'couldn\'t find this address! sorry'
|
47
|
+
if error = xml.elements['/PostcodeAnywhere/Data/Item[@error_number][1]']
|
48
|
+
error_number = error.attribute('error_number').value.to_i
|
49
|
+
message = error.attribute('message').value
|
50
|
+
if (1..11).include?(error_number) || (34..38).include?(error_number)
|
51
|
+
raise CredentialsError, message
|
52
|
+
else
|
53
|
+
raise Error, message
|
54
|
+
end
|
55
|
+
elsif xml.elements['/PostcodeAnywhere/Data'].elements.empty?
|
56
|
+
raise AddressError, 'No results returned'
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
module Graticule #:nodoc:
|
5
|
+
module Geocoder #:nodoc:
|
6
|
+
|
7
|
+
# Abstract class for implementing REST APIs.
|
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
|
+
# If you have extra URL paramaters (application id, output type) or need to
|
18
|
+
# perform URL customization, override +make_url+.
|
19
|
+
#
|
20
|
+
# class FakeService < RCRest
|
21
|
+
#
|
22
|
+
# class Error < RCRest::Error; end
|
23
|
+
#
|
24
|
+
# def initialize(appid)
|
25
|
+
# @appid = appid
|
26
|
+
# @url = URI.parse 'http://example.com/test'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def check_error(xml)
|
30
|
+
# raise Error, xml.elements['error'].text if xml.elements['error']
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def make_url(params)
|
34
|
+
# params[:appid] = @appid
|
35
|
+
# super params
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def parse_response(xml)
|
39
|
+
# # return Location
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def test(query)
|
43
|
+
# get :q => query
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# end
|
47
|
+
class Rest
|
48
|
+
|
49
|
+
# Web services initializer.
|
50
|
+
#
|
51
|
+
# Concrete web services implementations must set the +url+ instance
|
52
|
+
# variable which must be a URI.
|
53
|
+
def initialize
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def location_from_params(params)
|
60
|
+
case params
|
61
|
+
when Location then params
|
62
|
+
when Hash then Location.new params
|
63
|
+
else
|
64
|
+
raise ArgumentError, "Expected a Graticule::Location or a hash with :street, :locality, :region, :postal_code, and :country attributes"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Must extract and raise an error from +xml+, an REXML::Document, if any.
|
69
|
+
# Must returns if no error could be found.
|
70
|
+
def check_error(xml)
|
71
|
+
raise NotImplementedError
|
72
|
+
end
|
73
|
+
|
74
|
+
# Performs a GET request with +params+. Calls the parse_response method on
|
75
|
+
# the concrete class with an REXML::Document instance and returns its
|
76
|
+
# result.
|
77
|
+
def get(params = {})
|
78
|
+
url = make_url params
|
79
|
+
|
80
|
+
url.open do |response|
|
81
|
+
res = REXML::Document.new response.read
|
82
|
+
check_error(res)
|
83
|
+
return parse_response(res)
|
84
|
+
end
|
85
|
+
rescue OpenURI::HTTPError => e
|
86
|
+
response = REXML::Document.new e.io.read
|
87
|
+
check_error response
|
88
|
+
raise
|
89
|
+
end
|
90
|
+
|
91
|
+
# Creates a URI from the Hash +params+. Override this then call super if
|
92
|
+
# you need to add extra params like an application id or output type.
|
93
|
+
def make_url(params)
|
94
|
+
escaped_params = params.sort_by { |k,v| k.to_s }.map do |k,v|
|
95
|
+
"#{URI.escape k.to_s}=#{URI.escape v.to_s}"
|
96
|
+
end
|
97
|
+
|
98
|
+
url = @url.dup
|
99
|
+
url.query = escaped_params.join '&'
|
100
|
+
return url
|
101
|
+
end
|
102
|
+
|
103
|
+
# Must parse results from +xml+, an REXML::Document, into a Location.
|
104
|
+
def parse_response(xml)
|
105
|
+
raise NotImplementedError
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Graticule #:nodoc:
|
2
|
+
module Geocoder #:nodoc:
|
3
|
+
|
4
|
+
# Yahoo geocoding API.
|
5
|
+
#
|
6
|
+
# http://developer.yahoo.com/maps/rest/V1/geocode.html
|
7
|
+
class Yahoo < Rest
|
8
|
+
|
9
|
+
PRECISION = {
|
10
|
+
"country"=> :country,
|
11
|
+
"state" => :state,
|
12
|
+
"city" => :city,
|
13
|
+
"zip+4" => :zip,
|
14
|
+
"zip+2" => :zip,
|
15
|
+
"zip" => :zip,
|
16
|
+
"street" => :street,
|
17
|
+
"address" => :address
|
18
|
+
}
|
19
|
+
|
20
|
+
# Web services initializer.
|
21
|
+
#
|
22
|
+
# The +appid+ is the Application ID that uniquely identifies your
|
23
|
+
# application. See: http://developer.yahoo.com/faq/index.html#appid
|
24
|
+
#
|
25
|
+
# Concrete web services implementations need to set the following instance
|
26
|
+
# variables then call super:
|
27
|
+
#
|
28
|
+
# +host+:: API endpoint hostname
|
29
|
+
# +service_name+:: service name
|
30
|
+
# +version+:: service name version number
|
31
|
+
# +method+:: service method call
|
32
|
+
#
|
33
|
+
# See http://developer.yahoo.com/search/rest.html
|
34
|
+
def initialize(appid)
|
35
|
+
@host = 'api.local.yahoo.com'
|
36
|
+
@service_name = 'MapsService'
|
37
|
+
@version = 'V1'
|
38
|
+
@method = 'geocode'
|
39
|
+
@appid = appid
|
40
|
+
@url = URI.parse "http://#{@host}/#{@service_name}/#{@version}/#{@method}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a Location for +address+.
|
44
|
+
#
|
45
|
+
# The +address+ can be any of:
|
46
|
+
# * city, state
|
47
|
+
# * city, state, zip
|
48
|
+
# * zip
|
49
|
+
# * street, city, state
|
50
|
+
# * street, city, state, zip
|
51
|
+
# * street, zip
|
52
|
+
def locate(address)
|
53
|
+
location = (address.is_a?(String) ? address : location_from_params(address).to_s(:country => false))
|
54
|
+
# yahoo pukes on line breaks
|
55
|
+
get :location => location.gsub("\n", ', ')
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_response(xml) # :nodoc:
|
59
|
+
r = xml.elements['ResultSet/Result[1]']
|
60
|
+
returning Location.new do |location|
|
61
|
+
location.precision = PRECISION[r.attributes['precision']] || :unknown
|
62
|
+
|
63
|
+
if r.attributes.include? 'warning' then
|
64
|
+
location.warning = r.attributes['warning']
|
65
|
+
end
|
66
|
+
|
67
|
+
location.latitude = r.elements['Latitude'].text.to_f
|
68
|
+
location.longitude = r.elements['Longitude'].text.to_f
|
69
|
+
|
70
|
+
location.street = r.elements['Address'].text.titleize unless r.elements['Address'].text.blank?
|
71
|
+
location.locality = r.elements['City'].text.titleize unless r.elements['City'].text.blank?
|
72
|
+
location.region = r.elements['State'].text
|
73
|
+
location.postal_code = r.elements['Zip'].text
|
74
|
+
location.country = r.elements['Country'].text
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Extracts and raises an error from +xml+, if any.
|
79
|
+
def check_error(xml) #:nodoc:
|
80
|
+
err = xml.elements['Error']
|
81
|
+
raise Error, err.elements['Message'].text if err
|
82
|
+
end
|
83
|
+
|
84
|
+
# Creates a URL from the Hash +params+. Automatically adds the appid and
|
85
|
+
# sets the output type to 'xml'.
|
86
|
+
def make_url(params) #:nodoc:
|
87
|
+
params[:appid] = @appid
|
88
|
+
params[:output] = 'xml'
|
89
|
+
|
90
|
+
super params
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/lib/graticule/location.rb
CHANGED
@@ -2,12 +2,23 @@ module Graticule
|
|
2
2
|
|
3
3
|
# A geographic location
|
4
4
|
class Location
|
5
|
-
attr_accessor :latitude, :longitude, :street, :
|
5
|
+
attr_accessor :latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision, :warning
|
6
|
+
alias_method :city, :locality
|
7
|
+
alias_method :state, :region
|
8
|
+
alias_method :zip, :postal_code
|
6
9
|
|
7
10
|
def initialize(attrs = {})
|
8
11
|
attrs.each do |key,value|
|
9
12
|
instance_variable_set "@#{key}", value
|
10
13
|
end
|
14
|
+
self.precision ||= :unknown
|
15
|
+
end
|
16
|
+
|
17
|
+
def attributes
|
18
|
+
[:latitude, :longitude, :street, :locality, :region, :postal_code, :country].inject({}) do |result,attr|
|
19
|
+
result[attr] = self.send(attr) if self.send(attr)
|
20
|
+
result
|
21
|
+
end
|
11
22
|
end
|
12
23
|
|
13
24
|
# Returns an Array with latitude and longitude.
|
@@ -16,7 +27,7 @@ module Graticule
|
|
16
27
|
end
|
17
28
|
|
18
29
|
def ==(object)
|
19
|
-
super(object) || [:latitude, :longitude, :street, :
|
30
|
+
super(object) || [:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision].all? do |m|
|
20
31
|
object.respond_to?(m) && self.send(m) == object.send(m)
|
21
32
|
end
|
22
33
|
end
|
@@ -29,16 +40,17 @@ module Graticule
|
|
29
40
|
|
30
41
|
# Where would I be if I dug through the center of the earth?
|
31
42
|
def antipode
|
32
|
-
|
33
|
-
Location.new(:latitude => -latitude, :longitude => new_longitude)
|
43
|
+
Location.new :latitude => -latitude, :longitude => longitude + (longitude >= 0 ? -180 : 180)
|
34
44
|
end
|
35
45
|
alias_method :antipodal_location, :antipode
|
36
46
|
|
37
|
-
def to_s(
|
47
|
+
def to_s(options = {})
|
48
|
+
options = {:coordinates => false, :country => true}.merge(options)
|
38
49
|
result = ""
|
39
50
|
result << "#{street}\n" if street
|
40
|
-
result << [
|
41
|
-
result << "
|
51
|
+
result << [locality, [region, postal_code].compact.join(" ")].compact.join(", ")
|
52
|
+
result << " #{country}" if options[:country] && country
|
53
|
+
result << "\nlatitude: #{latitude}, longitude: #{longitude}" if options[:coordinates] && [latitude, longitude].any?
|
42
54
|
result
|
43
55
|
end
|
44
56
|
|
data/lib/graticule/version.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
map.centerAndZoom(new GPoint(-0.130427, 51.510036), 4);
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<PostcodeAnywhere Server="WS12" Version="3.0" Date="12/03/2007 15:43:33" Duration="0.016s">
|
2
|
+
<Schema Items="2">
|
3
|
+
<Field Name="error_number"/>
|
4
|
+
<Field Name="message"/>
|
5
|
+
</Schema>
|
6
|
+
<Data Items="1">
|
7
|
+
<Item error_number="6" message="License key was not recognised"/>
|
8
|
+
</Data>
|
9
|
+
</PostcodeAnywhere>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<PostcodeAnywhere Server="WS12" Version="3.0" Date="17/03/2007 19:01:05" Duration="1.109s">
|
3
|
+
<Schema Items="8">
|
4
|
+
<Field Name="line1"/>
|
5
|
+
<Field Name="line2"/>
|
6
|
+
<Field Name="city"/>
|
7
|
+
<Field Name="state"/>
|
8
|
+
<Field Name="postal_code"/>
|
9
|
+
<Field Name="country"/>
|
10
|
+
<Field Name="longitude"/>
|
11
|
+
<Field Name="latitude"/>
|
12
|
+
</Schema>
|
13
|
+
<Data Items="1">
|
14
|
+
<Item line1="204 Campbell Ave" city="Revelstoke" state="BC" postal_code="V0E" country="Canada" longitude="-118.196970002204" latitude="50.9997350418267"/>
|
15
|
+
</Data>
|
16
|
+
</PostcodeAnywhere>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<PostcodeAnywhere Version="3.0" Server="WS03" Date="17/03/2007 19:26:33" Duration="0.016s">
|
3
|
+
<Schema Items="10">
|
4
|
+
<Field Name="id"/>
|
5
|
+
<Field Name="seq"/>
|
6
|
+
<Field Name="location"/>
|
7
|
+
<Field Name="grid_east_m"/>
|
8
|
+
<Field Name="grid_north_m"/>
|
9
|
+
<Field Name="longitude"/>
|
10
|
+
<Field Name="latitude"/>
|
11
|
+
<Field Name="os_reference"/>
|
12
|
+
<Field Name="wgs84_longitude"/>
|
13
|
+
<Field Name="wgs84_latitude"/>
|
14
|
+
</Schema>
|
15
|
+
<Data Items="0"/>
|
16
|
+
</PostcodeAnywhere>
|