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.
Files changed (50) hide show
  1. data/CHANGELOG.txt +11 -0
  2. data/Manifest.txt +26 -11
  3. data/README.txt +1 -1
  4. data/bin/geocode +1 -1
  5. data/lib/graticule.rb +10 -8
  6. data/lib/graticule/cli.rb +1 -0
  7. data/lib/graticule/geocoder.rb +1 -12
  8. data/lib/graticule/geocoder/bogus.rb +15 -0
  9. data/lib/graticule/geocoder/geocoder_ca.rb +52 -0
  10. data/lib/graticule/geocoder/geocoder_us.rb +47 -0
  11. data/lib/graticule/geocoder/google.rb +106 -0
  12. data/lib/graticule/geocoder/host_ip.rb +50 -0
  13. data/lib/graticule/geocoder/local_search_maps.rb +45 -0
  14. data/lib/graticule/geocoder/meta_carta.rb +38 -0
  15. data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
  16. data/lib/graticule/geocoder/rest.rb +110 -0
  17. data/lib/graticule/geocoder/yahoo.rb +96 -0
  18. data/lib/graticule/location.rb +19 -7
  19. data/lib/graticule/version.rb +2 -2
  20. data/test/fixtures/responses/host_ip/private.txt +4 -0
  21. data/test/fixtures/responses/host_ip/success.txt +4 -0
  22. data/test/fixtures/responses/host_ip/unknown.txt +4 -0
  23. data/test/fixtures/responses/local_search_maps/success.txt +1 -0
  24. data/test/fixtures/responses/postcode_anywhere/badkey.xml +9 -0
  25. data/test/fixtures/responses/postcode_anywhere/canada.xml +16 -0
  26. data/test/fixtures/responses/postcode_anywhere/empty.xml +16 -0
  27. data/test/fixtures/responses/postcode_anywhere/success.xml +16 -0
  28. data/test/fixtures/responses/postcode_anywhere/uk.xml +18 -0
  29. data/test/test_helper.rb +4 -2
  30. data/test/unit/graticule/geocoder/geocoder_us_test.rb +43 -0
  31. data/test/unit/graticule/geocoder/google_test.rb +66 -0
  32. data/test/unit/graticule/geocoder/host_ip_test.rb +39 -0
  33. data/test/unit/graticule/geocoder/local_search_maps_test.rb +30 -0
  34. data/test/unit/graticule/geocoder/meta_carta_test.rb +44 -0
  35. data/test/unit/graticule/geocoder/postcode_anywhere_test.rb +50 -0
  36. data/test/unit/graticule/geocoder/yahoo_test.rb +48 -0
  37. data/test/unit/graticule/geocoder_test.rb +5 -9
  38. data/test/unit/graticule/location_test.rb +22 -7
  39. metadata +37 -18
  40. data/lib/graticule/geocoders/bogus.rb +0 -11
  41. data/lib/graticule/geocoders/geocoder_us.rb +0 -45
  42. data/lib/graticule/geocoders/google.rb +0 -99
  43. data/lib/graticule/geocoders/meta_carta.rb +0 -102
  44. data/lib/graticule/geocoders/rest.rb +0 -98
  45. data/lib/graticule/geocoders/yahoo.rb +0 -101
  46. data/test/unit/graticule/geocoders/geocoder_us_test.rb +0 -42
  47. data/test/unit/graticule/geocoders/geocoders.rb +0 -56
  48. data/test/unit/graticule/geocoders/google_test.rb +0 -22
  49. data/test/unit/graticule/geocoders/meta_carta_test.rb +0 -70
  50. 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
@@ -2,12 +2,23 @@ module Graticule
2
2
 
3
3
  # A geographic location
4
4
  class Location
5
- attr_accessor :latitude, :longitude, :street, :city, :state, :zip, :country, :precision, :warning
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, :city, :state, :zip, :country, :precision].all? do |m|
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
- new_longitude = longitude + (longitude >= 0 ? -180 : 180)
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(coordinates = false)
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 << [city, [state, zip, country].compact.join(" ")].compact.join(", ")
41
- result << "\nlatitude: #{latitude}, longitude: #{longitude}" if coordinates && [latitude, longitude].any?
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
 
@@ -1,8 +1,8 @@
1
1
  module Graticule #:nodoc:
2
2
  module Version #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 1
5
- TINY = 3
4
+ MINOR = 2
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -0,0 +1,4 @@
1
+ Country: (Private Address) (XX)
2
+ City: (Private Address)
3
+ Latitude:
4
+ Longitude:
@@ -0,0 +1,4 @@
1
+ Country: UNITED STATES (US)
2
+ City: Mountain View, CA
3
+ Latitude: 37.402
4
+ Longitude: -122.078
@@ -0,0 +1,4 @@
1
+ Country: (Unknown Country?) (XX)
2
+ City: (Unknown City?)
3
+ Latitude:
4
+ Longitude:
@@ -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>