pepe-graticule 0.2.11

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 (88) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG.txt +61 -0
  3. data/LICENSE.txt +30 -0
  4. data/Manifest.txt +84 -0
  5. data/README.txt +41 -0
  6. data/Rakefile +143 -0
  7. data/VERSION +1 -0
  8. data/bin/geocode +5 -0
  9. data/graticule.gemspec +146 -0
  10. data/init.rb +2 -0
  11. data/lib/graticule.rb +26 -0
  12. data/lib/graticule/cli.rb +64 -0
  13. data/lib/graticule/core_ext.rb +15 -0
  14. data/lib/graticule/distance.rb +18 -0
  15. data/lib/graticule/distance/haversine.rb +40 -0
  16. data/lib/graticule/distance/spherical.rb +52 -0
  17. data/lib/graticule/distance/vincenty.rb +76 -0
  18. data/lib/graticule/geocoder.rb +21 -0
  19. data/lib/graticule/geocoder/base.rb +112 -0
  20. data/lib/graticule/geocoder/bogus.rb +15 -0
  21. data/lib/graticule/geocoder/geocoder_ca.rb +54 -0
  22. data/lib/graticule/geocoder/geocoder_us.rb +51 -0
  23. data/lib/graticule/geocoder/google.rb +100 -0
  24. data/lib/graticule/geocoder/host_ip.rb +41 -0
  25. data/lib/graticule/geocoder/local_search_maps.rb +45 -0
  26. data/lib/graticule/geocoder/mapquest.rb +87 -0
  27. data/lib/graticule/geocoder/meta_carta.rb +33 -0
  28. data/lib/graticule/geocoder/multi.rb +46 -0
  29. data/lib/graticule/geocoder/multimap.rb +73 -0
  30. data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
  31. data/lib/graticule/geocoder/rest.rb +18 -0
  32. data/lib/graticule/geocoder/yahoo.rb +84 -0
  33. data/lib/graticule/location.rb +82 -0
  34. data/lib/graticule/version.rb +9 -0
  35. data/site/index.html +114 -0
  36. data/site/plugin.html +82 -0
  37. data/site/stylesheets/style.css +69 -0
  38. data/test/config.yml.default +36 -0
  39. data/test/fixtures/responses/geocoder_us/success.xml +18 -0
  40. data/test/fixtures/responses/geocoder_us/unknown.xml +1 -0
  41. data/test/fixtures/responses/google/badkey.xml +1 -0
  42. data/test/fixtures/responses/google/limit.xml +10 -0
  43. data/test/fixtures/responses/google/missing_address.xml +1 -0
  44. data/test/fixtures/responses/google/only_coordinates.xml +1 -0
  45. data/test/fixtures/responses/google/partial.xml +1 -0
  46. data/test/fixtures/responses/google/server_error.xml +10 -0
  47. data/test/fixtures/responses/google/success.xml +1 -0
  48. data/test/fixtures/responses/google/success_multiple_results.xml +88 -0
  49. data/test/fixtures/responses/google/unavailable.xml +1 -0
  50. data/test/fixtures/responses/google/unknown_address.xml +1 -0
  51. data/test/fixtures/responses/host_ip/private.txt +4 -0
  52. data/test/fixtures/responses/host_ip/success.txt +4 -0
  53. data/test/fixtures/responses/host_ip/unknown.txt +4 -0
  54. data/test/fixtures/responses/local_search_maps/empty.txt +1 -0
  55. data/test/fixtures/responses/local_search_maps/not_found.txt +1 -0
  56. data/test/fixtures/responses/local_search_maps/success.txt +1 -0
  57. data/test/fixtures/responses/mapquest/multi_result.xml +1 -0
  58. data/test/fixtures/responses/mapquest/success.xml +1 -0
  59. data/test/fixtures/responses/meta_carta/bad_address.xml +17 -0
  60. data/test/fixtures/responses/meta_carta/multiple.xml +33 -0
  61. data/test/fixtures/responses/meta_carta/success.xml +31 -0
  62. data/test/fixtures/responses/multimap/missing_params.xml +4 -0
  63. data/test/fixtures/responses/multimap/no_matches.xml +4 -0
  64. data/test/fixtures/responses/multimap/success.xml +19 -0
  65. data/test/fixtures/responses/postcode_anywhere/badkey.xml +9 -0
  66. data/test/fixtures/responses/postcode_anywhere/canada.xml +16 -0
  67. data/test/fixtures/responses/postcode_anywhere/empty.xml +16 -0
  68. data/test/fixtures/responses/postcode_anywhere/success.xml +16 -0
  69. data/test/fixtures/responses/postcode_anywhere/uk.xml +18 -0
  70. data/test/fixtures/responses/yahoo/success.xml +3 -0
  71. data/test/fixtures/responses/yahoo/unknown_address.xml +6 -0
  72. data/test/mocks/uri.rb +52 -0
  73. data/test/test_helper.rb +31 -0
  74. data/test/unit/graticule/distance_test.rb +58 -0
  75. data/test/unit/graticule/geocoder/geocoder_us_test.rb +43 -0
  76. data/test/unit/graticule/geocoder/geocoders.rb +56 -0
  77. data/test/unit/graticule/geocoder/google_test.rb +112 -0
  78. data/test/unit/graticule/geocoder/host_ip_test.rb +40 -0
  79. data/test/unit/graticule/geocoder/local_search_maps_test.rb +30 -0
  80. data/test/unit/graticule/geocoder/mapquest_test.rb +47 -0
  81. data/test/unit/graticule/geocoder/meta_carta_test.rb +44 -0
  82. data/test/unit/graticule/geocoder/multi_test.rb +43 -0
  83. data/test/unit/graticule/geocoder/multimap_test.rb +52 -0
  84. data/test/unit/graticule/geocoder/postcode_anywhere_test.rb +50 -0
  85. data/test/unit/graticule/geocoder/yahoo_test.rb +48 -0
  86. data/test/unit/graticule/geocoder_test.rb +27 -0
  87. data/test/unit/graticule/location_test.rb +79 -0
  88. metadata +167 -0
@@ -0,0 +1,73 @@
1
+ module Graticule #:nodoc:
2
+ module Geocoder #:nodoc:
3
+
4
+ # Multimap geocoding API
5
+
6
+ class Multimap < Rest
7
+
8
+ # This precision information is not complete.
9
+ # More details should be implemented from:
10
+ # http://www.multimap.com/share/documentation/clientzone/gqcodes.htm
11
+
12
+ PRECISION = {
13
+ "6"=> :country,
14
+ "5" => :state,
15
+ "4" => :postal_code,
16
+ "3" => :city,
17
+ "2" => :street,
18
+ "1" => :address
19
+ }
20
+
21
+ # Web services initializer.
22
+ #
23
+ # The +api_key+ is the Open API key that uniquely identifies your
24
+ # application.
25
+ #
26
+ # See http://www.multimap.com/openapi/
27
+
28
+ def initialize(api_key)
29
+ @api_key = api_key
30
+ @url = URI.parse "http://clients.multimap.com/API/geocode/1.2/#{@api_key}"
31
+ end
32
+
33
+ # Returns a location for an address in the form of a String, Hash or Location.
34
+
35
+ def locate(address)
36
+ location = address.is_a?(String) ? address : location_from_params(address)
37
+ case location
38
+ when String
39
+ get :qs => location
40
+ when Location
41
+ get "street" => location.street,
42
+ "region" => location.region,
43
+ "city" => location.city,
44
+ "postalCode" => location.postal_code,
45
+ "countryCode" => location.country
46
+ end
47
+ end
48
+
49
+ def parse_response(xml)
50
+ r = xml.elements['Results/Location[1]']
51
+ returning Location.new do |location|
52
+
53
+ location.precision = PRECISION[r.attributes['geocodeQuality']] || :unknown
54
+
55
+ location.street = r.elements['Address/Street'].text.titleize unless r.elements['Address/Street'].nil?
56
+ location.locality = r.elements['Address/Areas/Area'].text.titleize unless r.elements['Address/Areas/Area'].nil?
57
+ location.region = r.elements['Address/State'].text.titleize unless r.elements['Address/State'].nil?
58
+ location.postal_code = r.elements['Address/PostalCode'].text unless r.elements['Address/PostalCode'].nil?
59
+ location.country = r.elements['Address/CountryCode'].text
60
+
61
+ location.latitude = r.elements['Point/Lat'].text.to_f
62
+ location.longitude = r.elements['Point/Lon'].text.to_f
63
+ end
64
+ end
65
+
66
+ def check_error(xml)
67
+ error = xml.elements['Results'].attributes['errorCode']
68
+ raise Error, error unless error.nil?
69
+ end
70
+
71
+ end
72
+ end
73
+ 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,18 @@
1
+ require 'rexml/document'
2
+
3
+ module Graticule #:nodoc:
4
+ module Geocoder #:nodoc:
5
+
6
+ # Abstract class for implementing REST geocoders. Passes on a REXML::Document
7
+ # to +check_errors+ and +parse_response+.
8
+ class Rest < Base
9
+
10
+ private
11
+
12
+ def prepare_response(response)
13
+ REXML::Document.new(response)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,84 @@
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
+ # See http://developer.yahoo.com/search/rest.html
26
+ def initialize(appid)
27
+ @appid = appid
28
+ @url = URI.parse "http://api.local.yahoo.com/MapsService/V1/geocode"
29
+ end
30
+
31
+ # Returns a Location for +address+.
32
+ #
33
+ # The +address+ can be any of:
34
+ # * city, state
35
+ # * city, state, zip
36
+ # * zip
37
+ # * street, city, state
38
+ # * street, city, state, zip
39
+ # * street, zip
40
+ def locate(address)
41
+ location = (address.is_a?(String) ? address : location_from_params(address).to_s(:country => false))
42
+ # yahoo pukes on line breaks
43
+ get :location => location.gsub("\n", ', ')
44
+ end
45
+
46
+ def parse_response(xml) # :nodoc:
47
+ r = xml.elements['ResultSet/Result[1]']
48
+ returning Location.new do |location|
49
+ location.precision = PRECISION[r.attributes['precision']] || :unknown
50
+
51
+ if r.attributes.include? 'warning' then
52
+ location.warning = r.attributes['warning']
53
+ end
54
+
55
+ location.latitude = r.elements['Latitude'].text.to_f
56
+ location.longitude = r.elements['Longitude'].text.to_f
57
+
58
+ location.street = r.elements['Address'].text.titleize unless r.elements['Address'].text.blank?
59
+ location.locality = r.elements['City'].text.titleize unless r.elements['City'].text.blank?
60
+ location.region = r.elements['State'].text
61
+ location.postal_code = r.elements['Zip'].text
62
+ location.country = r.elements['Country'].text
63
+ end
64
+ end
65
+
66
+ # Extracts and raises an error from +xml+, if any.
67
+ def check_error(xml) #:nodoc:
68
+ err = xml.elements['Error']
69
+ raise Error, err.elements['Message'].text if err
70
+ end
71
+
72
+ # Creates a URL from the Hash +params+. Automatically adds the appid and
73
+ # sets the output type to 'xml'.
74
+ def make_url(params) #:nodoc:
75
+ params[:appid] = @appid
76
+ params[:output] = 'xml'
77
+
78
+ super params
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,82 @@
1
+ module Graticule
2
+
3
+ # A geographic location
4
+ class Location
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
9
+
10
+ # if latitude is string both latitude and longitude are converted to float
11
+ def initialize(attrs = {})
12
+ attrs = parse_strings(attrs) if attrs[:latitude].is_a? String
13
+ attrs.each do |key,value|
14
+ instance_variable_set "@#{key}", value
15
+ end
16
+ self.precision ||= :unknown
17
+ end
18
+
19
+ def attributes
20
+ [:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision].inject({}) do |result,attr|
21
+ result[attr] = self.send(attr) unless self.send(attr).blank?
22
+ result
23
+ end
24
+ end
25
+
26
+ def blank?
27
+ attributes.except(:precision).empty?
28
+ end
29
+
30
+ # Returns an Array with latitude and longitude.
31
+ def coordinates
32
+ [latitude, longitude]
33
+ end
34
+
35
+ def ==(other)
36
+ other.respond_to?(:attributes) ? attributes == other.attributes : false
37
+ end
38
+
39
+ # Calculate the distance to another location. See the various Distance formulas
40
+ # for more information
41
+ def distance_to(destination, options = {})
42
+ options = {:formula => :haversine, :units => :miles}.merge(options)
43
+ "Graticule::Distance::#{options[:formula].to_s.titleize}".constantize.distance(self, destination, options[:units])
44
+ end
45
+
46
+ # Where would I be if I dug through the center of the earth?
47
+ def antipode
48
+ Location.new :latitude => -latitude, :longitude => longitude + (longitude >= 0 ? -180 : 180)
49
+ end
50
+ alias_method :antipodal_location, :antipode
51
+
52
+ def to_s(options = {})
53
+ options = {:coordinates => false, :country => true}.merge(options)
54
+ result = ""
55
+ result << "#{street}\n" if street
56
+ result << [locality, [region, postal_code].compact.join(" ")].compact.join(", ")
57
+ result << " #{country}" if options[:country] && country
58
+ result << "\nlatitude: #{latitude}, longitude: #{longitude}" if options[:coordinates] && [latitude, longitude].any?
59
+ result
60
+ end
61
+
62
+ private
63
+ # parses string latitude to float
64
+ def parse_strings(attrs)
65
+ [:latitude, :longitude].each do |sym|
66
+ attrs[sym] = degree_to_float(attrs[sym])
67
+ end
68
+ return attrs
69
+ end
70
+
71
+ # parses string in degrees, minutes and seconds to int
72
+ def degree_to_float(str)
73
+ data = str.match(/(\d{1,2})°(\d{1,2})'(\d{1,2}.\d{1,3})"([N|S|E|W])/)
74
+ raise 'Bad format' unless data
75
+ res = data[1].to_i + (data[2].to_f * 1/60.0) + (data[3].to_f * 1/3600)
76
+ res = -res if data[4].match(/S|W/)
77
+ res = ((res * 1e5).round)/1e5 # round it to 5 places
78
+ return res
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,9 @@
1
+ module Graticule #:nodoc:
2
+ module Version #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 11
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/site/index.html ADDED
@@ -0,0 +1,114 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
7
+ <title>Graticule</title>
8
+ <link rel="stylesheet" type="text/css" href="http://opensoul.org/stylesheets/code.css" />
9
+ <link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
10
+ <link href="http://opensoul.org/stylesheets/ci.css" rel="stylesheet" type="text/css" />
11
+ <script src="http://opensoul.org/javascripts/code_highlighter.js" type="text/javascript"></script>
12
+ <script src="http://opensoul.org/javascripts/ruby.js" type="text/javascript"></script>
13
+ </head>
14
+
15
+ <body>
16
+ <div id="collectiveidea">
17
+ <a href="http://collectiveidea.com"><img src="http://opensoul.org/images/header_logo.gif" alt="Collective Idea" class="logo" width="17" height="22" /></a>
18
+ <ul class="links">
19
+ <li><a href="http://daniel.collectiveidea.com/blog">Daniel</a></li>
20
+ <li><a href="http://opensoul.org">Brandon</a></li>
21
+ <li class="name"><a href="http://collectiveidea.com"><img src="http://opensoul.org/images/header_collectiveidea.gif" alt="Collective Idea" width="123" height="21" /></a></li>
22
+ </ul>
23
+ </div>
24
+ <div id="main">
25
+ <div id="header">
26
+ <h1><a href="/">Graticule</a></h1>
27
+ <ul id="nav">
28
+ <li><a href="graticule">API Docs</a></li>
29
+ <li><a href="plugin.html">Rails Plugin</a></li>
30
+ <li><a href="http://rubyforge.org/projects/graticule">RubyForge</a></li>
31
+ <li><a href="http://opensoul.org/tags/geocoding">Blog</a></li>
32
+ </ul>
33
+ </div>
34
+ <div id="content">
35
+ <dl lang="en" xml:lang="en">
36
+ <dt><strong>grat·i·cule</strong>, <span class="pronunciation">|ˈgratəˌkyoōl|</span>,
37
+ <em><abbr title="noun">n.</abbr></em>
38
+ <span class="description">technical</span>
39
+ </dt>
40
+ <dd><em>Navigation.</em> a network of parallels and meridians on a map or chart.</dd>
41
+ </dl>
42
+
43
+ <p>Graticule is a geocoding API for looking up address coordinates and performing distance calculations. It supports many popular APIs, including:</p>
44
+
45
+ <ul>
46
+ <li>Yahoo</li>
47
+ <li>Google</li>
48
+ <li>Geocoder.ca</li>
49
+ <li>Geocoder.us</li>
50
+ <li>PostcodeAnywhere</li>
51
+ <li>MetaCarta</li>
52
+ </ul>
53
+
54
+ <p>There is a <a href="plugin.html">companion Rails plugin</a> that makes geocoding seem like magic.</p>
55
+
56
+ <h2>Example</h2>
57
+
58
+ <pre><code class="ruby">require 'rubygems'
59
+ require 'graticule'
60
+ geocoder = Graticule.service(:google).new "api_key"
61
+ location = geocoder.locate "1600 Amphitheatre Parkway, Mountain View, CA"
62
+ location.coordinates #=> [37.423021, -122.083739]
63
+ location.country #=> "US"</code></pre>
64
+
65
+ <p>See the <a href="graticule">API documentation</a> for more details.</p>
66
+
67
+ <h2>International Support</h2>
68
+
69
+ <p>Graticule supports several services with international support. The international geocoders require slightly more structured data than the US ones:</p>
70
+
71
+ <pre><code class="ruby">g = Graticule.service(:local_search_maps).new
72
+ location = g.locate :street => '48 Leicester Square', :locality => 'London', :country => 'UK'
73
+ location.coordinates #=> [51.510036, -0.130427]</code></pre>
74
+
75
+ <h2>Distance Calculation</h2>
76
+
77
+ <p>Graticule includes 3 different distance formulas, Spherical (simplest but least accurate), Vincenty (most accurate and most complicated), and Haversine (somewhere inbetween).</p>
78
+
79
+ <pre><code class="ruby">location = geocoder.locate("Holland, MI")
80
+ location.distance_to(geocoder.locate("Chicago, IL"))
81
+ #=> 101.997458788177</code></pre>
82
+
83
+ <h2>Command Line</h2>
84
+ <p>Graticule also includes a command line interface to the various geocoders:</p>
85
+
86
+ <pre>
87
+ $ geocode -s yahoo -a yahookey Washington, DC
88
+ Washington, DC US
89
+ latitude: 38.895222, longitude: -77.036758</pre>
90
+
91
+ <h2>Installation</h2>
92
+
93
+ <p>Install the gem by executing:</p>
94
+
95
+ <pre>gem install graticule</pre>
96
+
97
+ <p>Or, download it from <a href="http://rubyforge.org/frs/?group_id=2643">RubyForge</a>.</p>
98
+
99
+ <h2>Contributing</h2>
100
+
101
+ <p>Contributions are welcome and appreciated! Join the <a href="http://rubyforge.org/mailman/listinfo/graticule-users">mailing list</a> and grab the source from:</p>
102
+
103
+ <pre>http://source.collectiveidea.com/public/geocode/trunk/</pre>
104
+ </div>
105
+ </div>
106
+
107
+ <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
108
+ </script>
109
+ <script type="text/javascript">
110
+ _uacct = "UA-194397-7";
111
+ urchinTracker();
112
+ </script>
113
+ </body>
114
+ </html>