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
data/CHANGELOG.txt CHANGED
@@ -1,3 +1,14 @@
1
+ 0.2.0
2
+ * changed city to locality, state to region, and zip to postal_code
3
+ * added support for PostcodeAnywhere
4
+ * added support for Local Search Maps (James Stewart)
5
+ * added IP-based geocoder
6
+ * moved geocoders to Graticule::Geocoder namespace
7
+ * fixed Google geocoder (again)
8
+ * made Yahoo geocoder consistent with others by returning 1 result
9
+ * geocoders can how take a Hash (:street, :locality, :region, :postal_code, :country)
10
+ or a Graticule::Location for the #locate call
11
+
1
12
  0.1.3 (2007-02-14)
2
13
  * fixed Google geocoder
3
14
  * fixed CLI
data/Manifest.txt CHANGED
@@ -12,12 +12,16 @@ lib/graticule/distance/haversine.rb
12
12
  lib/graticule/distance/spherical.rb
13
13
  lib/graticule/distance/vincenty.rb
14
14
  lib/graticule/geocoder.rb
15
- lib/graticule/geocoders/bogus.rb
16
- lib/graticule/geocoders/geocoder_us.rb
17
- lib/graticule/geocoders/google.rb
18
- lib/graticule/geocoders/meta_carta.rb
19
- lib/graticule/geocoders/rest.rb
20
- lib/graticule/geocoders/yahoo.rb
15
+ lib/graticule/geocoder/bogus.rb
16
+ lib/graticule/geocoder/geocoder_ca.rb
17
+ lib/graticule/geocoder/geocoder_us.rb
18
+ lib/graticule/geocoder/google.rb
19
+ lib/graticule/geocoder/host_ip.rb
20
+ lib/graticule/geocoder/local_search_maps.rb
21
+ lib/graticule/geocoder/meta_carta.rb
22
+ lib/graticule/geocoder/postcode_anywhere.rb
23
+ lib/graticule/geocoder/rest.rb
24
+ lib/graticule/geocoder/yahoo.rb
21
25
  lib/graticule/location.rb
22
26
  lib/graticule/version.rb
23
27
  test/fixtures/responses/geocoder_us/success.xml
@@ -29,18 +33,29 @@ test/fixtures/responses/google/server_error.xml
29
33
  test/fixtures/responses/google/success.xml
30
34
  test/fixtures/responses/google/unavailable.xml
31
35
  test/fixtures/responses/google/unknown_address.xml
36
+ test/fixtures/responses/host_ip/private.txt
37
+ test/fixtures/responses/host_ip/success.txt
38
+ test/fixtures/responses/host_ip/unknown.txt
39
+ test/fixtures/responses/local_search_maps/success.txt
32
40
  test/fixtures/responses/meta_carta/bad_address.xml
33
41
  test/fixtures/responses/meta_carta/multiple.xml
34
42
  test/fixtures/responses/meta_carta/success.xml
43
+ test/fixtures/responses/postcode_anywhere/badkey.xml
44
+ test/fixtures/responses/postcode_anywhere/canada.xml
45
+ test/fixtures/responses/postcode_anywhere/empty.xml
46
+ test/fixtures/responses/postcode_anywhere/success.xml
47
+ test/fixtures/responses/postcode_anywhere/uk.xml
35
48
  test/fixtures/responses/yahoo/success.xml
36
49
  test/fixtures/responses/yahoo/unknown_address.xml
37
50
  test/mocks/uri.rb
38
51
  test/test_helper.rb
39
52
  test/unit/graticule/distance_test.rb
53
+ test/unit/graticule/geocoder/geocoder_us_test.rb
54
+ test/unit/graticule/geocoder/google_test.rb
55
+ test/unit/graticule/geocoder/host_ip_test.rb
56
+ test/unit/graticule/geocoder/local_search_maps_test.rb
57
+ test/unit/graticule/geocoder/meta_carta_test.rb
58
+ test/unit/graticule/geocoder/postcode_anywhere_test.rb
59
+ test/unit/graticule/geocoder/yahoo_test.rb
40
60
  test/unit/graticule/geocoder_test.rb
41
- test/unit/graticule/geocoders/geocoder_us_test.rb
42
- test/unit/graticule/geocoders/geocoders.rb
43
- test/unit/graticule/geocoders/google_test.rb
44
- test/unit/graticule/geocoders/meta_carta_test.rb
45
- test/unit/graticule/geocoders/yahoo_test.rb
46
61
  test/unit/graticule/location_test.rb
data/README.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  = Graticule
2
2
 
3
- Graticule is a geocoding API for looking up address coordinates. It supports supports the Yahoo, Google, Geocoder.us, and MetaCarta APIs.
3
+ Graticule is a geocoding API for looking up address coordinates. It supports many popular APIs, including Yahoo, Google, Geocoder.ca, Geocoder.us, PostcodeAnywhere and MetaCarta.
4
4
 
5
5
  = Usage
6
6
 
data/bin/geocode CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'graticule'
3
+ require 'graticule/cli'
4
4
 
5
5
  Graticule::Cli.start ARGV
data/lib/graticule.rb CHANGED
@@ -4,15 +4,17 @@ require 'active_support'
4
4
 
5
5
  require 'graticule/location'
6
6
  require 'graticule/geocoder'
7
- require 'graticule/geocoders/bogus'
8
- require 'graticule/geocoders/rest'
9
- require 'graticule/geocoders/google'
10
- require 'graticule/geocoders/yahoo'
11
- require 'graticule/geocoders/geocoder_us'
12
- require 'graticule/geocoders/meta_carta'
7
+ require 'graticule/geocoder/bogus'
8
+ require 'graticule/geocoder/rest'
9
+ require 'graticule/geocoder/google'
10
+ require 'graticule/geocoder/host_ip'
11
+ require 'graticule/geocoder/yahoo'
12
+ require 'graticule/geocoder/geocoder_ca'
13
+ require 'graticule/geocoder/geocoder_us'
14
+ require 'graticule/geocoder/local_search_maps'
15
+ require 'graticule/geocoder/meta_carta'
16
+ require 'graticule/geocoder/postcode_anywhere'
13
17
  require 'graticule/distance'
14
18
  require 'graticule/distance/haversine'
15
19
  require 'graticule/distance/spherical'
16
20
  require 'graticule/distance/vincenty'
17
-
18
- require 'graticule/cli'
data/lib/graticule/cli.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'graticule'
1
2
  require 'optparse'
2
3
 
3
4
  module Graticule
@@ -8,18 +8,7 @@ module Graticule
8
8
  # See the documentation for your specific geocoder for more information
9
9
  #
10
10
  def self.service(name)
11
- self.const_get "#{name}_geocoder".camelize
12
- end
13
-
14
- # The Geocode class is the base class for all geocoder implementations. The
15
- # geocoders must implement:
16
- #
17
- # * locate(address)
18
- #
19
- class Geocoder
20
- def initialize
21
- raise NotImplementedError
22
- end
11
+ Geocoder.const_get name.to_s.camelize
23
12
  end
24
13
 
25
14
  # Base error class
@@ -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,52 @@
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
+ def parse_response(xml) #:nodoc:
17
+ returning Location.new do |location|
18
+ location.latitude = xml.elements['geodata/latt'].text.to_f
19
+ location.longitude = xml.elements['geodata/longt'].text.to_f
20
+ location.street = xml.elements['geodata/standard/staddress'].text
21
+ location.locality = xml.elements['geodata/standard/city'].text
22
+ location.region = xml.elements['geodata/standard/prov'].text
23
+ end
24
+ end
25
+
26
+ def check_error(xml) #:nodoc:
27
+ error = xml.elements['geodata/error']
28
+ if error
29
+ code = error.elements['code'].text.to_i
30
+ message = error.elements['description'].text
31
+ if (1..3).include?(code)
32
+ raise CredentialsError, message
33
+ elsif (4..8).include?(code)
34
+ raise AddressError, message
35
+ else
36
+ raise Error, message
37
+ end
38
+ end
39
+ end
40
+
41
+ def make_url(params) #:nodoc:
42
+ params[:auth] = @auth if @auth
43
+ params[:standard] = 1
44
+ params[:showpostal] = 1
45
+ params[:geoit] = 'XML'
46
+ super params
47
+ end
48
+
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
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
+ def parse_response(xml) #:nodoc:
32
+ location = Location.new
33
+ location.street = xml.elements['rdf:RDF/geo:Point/dc:description'].text
34
+
35
+ location.latitude = xml.elements['rdf:RDF/geo:Point/geo:lat'].text.to_f
36
+ location.longitude = xml.elements['rdf:RDF/geo:Point/geo:long'].text.to_f
37
+
38
+ return location
39
+ end
40
+
41
+ def check_error(xml) #:nodoc:
42
+ raise AddressError, xml.text if xml.text == 'couldn\'t find this address! sorry'
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,106 @@
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
+ # Then you create a GoogleGeocode object and start locating addresses:
9
+ #
10
+ # require 'rubygems'
11
+ # require 'graticule'
12
+ #
13
+ # gg = Graticule.service(:google).new(:key => MAPS_API_KEY)
14
+ # location = gg.locate '1600 Amphitheater Pkwy, Mountain View, CA'
15
+ # p location.coordinates
16
+ #
17
+ class Google < Rest
18
+ # http://www.google.com/apis/maps/documentation/#Geocoding_HTTP_Request
19
+
20
+ # http://www.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy
21
+ PRECISION = {
22
+ 0 => :unknown, # Unknown location. (Since 2.59)
23
+ 1 => :country, # Country level accuracy. (Since 2.59)
24
+ 2 => :state, # Region (state, province, prefecture, etc.) level accuracy. (Since 2.59)
25
+ 3 => :state, # Sub-region (county, municipality, etc.) level accuracy. (Since 2.59)
26
+ 4 => :city, # Town (city, village) level accuracy. (Since 2.59)
27
+ 5 => :zip, # Post code (zip code) level accuracy. (Since 2.59)
28
+ 6 => :street, # Street level accuracy. (Since 2.59)
29
+ 7 => :street, # Intersection level accuracy. (Since 2.59)
30
+ 8 => :address # Address level accuracy. (Since 2.59)
31
+ }
32
+
33
+ # Creates a new GoogleGeocode that will use Google Maps API key +key+. You
34
+ # can sign up for an API key here:
35
+ #
36
+ # http://www.google.com/apis/maps/signup.html
37
+ def initialize(key)
38
+ @key = key
39
+ @url = URI.parse 'http://maps.google.com/maps/geo'
40
+ end
41
+
42
+ # Locates +address+ returning a Location
43
+ def locate(address)
44
+ get :q => address.is_a?(String) ? address : location_from_params(address).to_s
45
+ end
46
+
47
+ # Extracts a Location from +xml+.
48
+ def parse_response(xml) #:nodoc:
49
+ address = REXML::XPath.first(xml, '//xal:AddressDetails', 'xal' => "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0")
50
+
51
+ longitude, latitude, = xml.elements['/kml/Response/Placemark/Point/coordinates'].text.split(',').map { |v| v.to_f }
52
+
53
+ Location.new \
54
+ :street => value(address.elements['Country/AdministrativeArea/SubAdministrativeArea/Locality/Thoroughfare/ThoroughfareName/text()']),
55
+ :locality => value(address.elements['Country/AdministrativeArea/SubAdministrativeArea/Locality/LocalityName/text()']),
56
+ :region => value(address.elements['Country/AdministrativeArea/AdministrativeAreaName/text()']),
57
+ :postal_code => value(address.elements['Country/AdministrativeArea/SubAdministrativeArea/Locality/PostalCode/PostalCodeNumber/text()']),
58
+ :country => value(address.elements['Country/CountryNameCode/text()']),
59
+ :latitude => latitude,
60
+ :longitude => longitude,
61
+ :precision => PRECISION[address.attribute('Accuracy').value.to_i] || :unknown
62
+ end
63
+
64
+ # Extracts and raises an error from +xml+, if any.
65
+ def check_error(xml) #:nodoc:
66
+ status = xml.elements['/kml/Response/Status/code'].text.to_i
67
+ case status
68
+ when 200 then # ignore, ok
69
+ when 500 then
70
+ raise Error, 'server error'
71
+ when 601 then
72
+ raise AddressError, 'missing address'
73
+ when 602 then
74
+ raise AddressError, 'unknown address'
75
+ when 603 then
76
+ raise AddressError, 'unavailable address'
77
+ when 610 then
78
+ raise CredentialsError, 'invalid key'
79
+ when 620 then
80
+ raise CredentialsError, 'too many queries'
81
+ else
82
+ raise Error, "unknown error #{status}"
83
+ end
84
+ end
85
+
86
+ # Creates a URL from the Hash +params+. Automatically adds the key and
87
+ # sets the output type to 'xml'.
88
+ def make_url(params) #:nodoc:
89
+ params[:key] = @key
90
+ params[:output] = 'xml'
91
+
92
+ super params
93
+ end
94
+
95
+ private
96
+
97
+ def value(element)
98
+ element.value if element
99
+ end
100
+
101
+ def text(element)
102
+ element.text if element
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,50 @@
1
+ require 'yaml'
2
+
3
+ module Graticule #:nodoc:
4
+ module Geocoder #:nodoc:
5
+
6
+ class HostIp
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
+ make_url(:ip => address, :position => true).open do |response|
15
+ # add new line so YAML.load doesn't puke
16
+ result = response.read + "\n"
17
+ check_error(result)
18
+ parse_response(YAML.load(result))
19
+ end
20
+ end
21
+
22
+ private
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 =~ /Unknown City/
36
+ raise AddressError, 'Private Address' if response =~ /Private Address/
37
+ end
38
+
39
+ def make_url(params) #:nodoc:
40
+ returning @url.dup do |url|
41
+ url.query = params.map do |k,v|
42
+ "#{URI.escape k.to_s}=#{URI.escape v.to_s}"
43
+ end.join('&')
44
+ end
45
+ end
46
+
47
+
48
+ end
49
+ end
50
+ 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 < Rest
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 => :address, :locality => :city, :region => :state, :postal_code => :zip}
24
+ mapping = {}
25
+ mapping.keys.inject({}) do |result,attribute|
26
+ result[mapping[attribute]] = location.attributes[attribute]
27
+ end
28
+ end
29
+
30
+ def check_error(js)
31
+ raise AddressError, "Empty Response" if js.nil? or js.text.nil?
32
+ raise AddressError, 'Location not found' if js.text =~ /location not found/
33
+ end
34
+
35
+ def parse_response(js)
36
+ returning Location.new do |location|
37
+ coordinates = js.text.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