graticule 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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