graticule 0.1.3 → 0.2.0
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.
- 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>
|