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,54 @@
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
+ private
17
+
18
+ def parse_response(xml) #:nodoc:
19
+ returning Location.new do |location|
20
+ location.latitude = xml.elements['geodata/latt'].text.to_f
21
+ location.longitude = xml.elements['geodata/longt'].text.to_f
22
+ location.street = xml.elements['geodata/standard/staddress'].text
23
+ location.locality = xml.elements['geodata/standard/city'].text
24
+ location.region = xml.elements['geodata/standard/prov'].text
25
+ end
26
+ end
27
+
28
+ def check_error(xml) #:nodoc:
29
+ error = xml.elements['geodata/error']
30
+ if error
31
+ code = error.elements['code'].text.to_i
32
+ message = error.elements['description'].text
33
+ if (1..3).include?(code)
34
+ raise CredentialsError, message
35
+ elsif (4..8).include?(code)
36
+ raise AddressError, message
37
+ else
38
+ raise Error, message
39
+ end
40
+ end
41
+ end
42
+
43
+ def make_url(params) #:nodoc:
44
+ params[:auth] = @auth if @auth
45
+ params[:standard] = 1
46
+ params[:showpostal] = 1
47
+ params[:geoit] = 'XML'
48
+ super params
49
+ end
50
+
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,51 @@
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
+ private
32
+
33
+ def parse_response(xml) #:nodoc:
34
+ location = Location.new
35
+ location.street = xml.elements['rdf:RDF/geo:Point/dc:description'].text
36
+
37
+ location.latitude = xml.elements['rdf:RDF/geo:Point/geo:lat'].text.to_f
38
+ location.longitude = xml.elements['rdf:RDF/geo:Point/geo:long'].text.to_f
39
+
40
+ return location
41
+ end
42
+
43
+ def check_error(xml) #:nodoc:
44
+ raise AddressError, xml.text if xml.text =~ /couldn't find this address! sorry/
45
+ raise Error, xml.text if xml.text =~ /Your browser sent a request that this server could not understand./
46
+ raise Error, xml.text if !(xml.text =~ /geo:Point/)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,100 @@
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
+ # gg = Graticule.service(:google).new(MAPS_API_KEY)
9
+ # location = gg.locate '1600 Amphitheater Pkwy, Mountain View, CA'
10
+ # p location.coordinates
11
+ # #=> [37.423111, -122.081783
12
+ #
13
+ class Google < Rest
14
+ # http://www.google.com/apis/maps/documentation/#Geocoding_HTTP_Request
15
+
16
+ # http://www.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy
17
+ PRECISION = {
18
+ 0 => :unknown, # Unknown location. (Since 2.59)
19
+ 1 => :country, # Country level accuracy. (Since 2.59)
20
+ 2 => :state, # Region (state, province, prefecture, etc.) level accuracy. (Since 2.59)
21
+ 3 => :state, # Sub-region (county, municipality, etc.) level accuracy. (Since 2.59)
22
+ 4 => :city, # Town (city, village) level accuracy. (Since 2.59)
23
+ 5 => :zip, # Post code (zip code) level accuracy. (Since 2.59)
24
+ 6 => :street, # Street level accuracy. (Since 2.59)
25
+ 7 => :street, # Intersection level accuracy. (Since 2.59)
26
+ 8 => :address # Address level accuracy. (Since 2.59)
27
+ }
28
+
29
+ # Creates a new GoogleGeocode that will use Google Maps API +key+.
30
+ def initialize(key)
31
+ @key = key
32
+ @url = URI.parse 'http://maps.google.com/maps/geo'
33
+ end
34
+
35
+ # Locates +address+ returning a Location
36
+ def locate(address)
37
+ get :q => address.is_a?(String) ? address : location_from_params(address).to_s
38
+ end
39
+
40
+ private
41
+
42
+ # Extracts a Location from +xml+.
43
+ def parse_response(xml) #:nodoc:
44
+ longitude, latitude, = xml.elements['/kml/Response/Placemark/Point/coordinates'].text.split(',').map { |v| v.to_f }
45
+ returning Location.new(:latitude => latitude, :longitude => longitude) do |l|
46
+ address = REXML::XPath.first(xml, '//xal:AddressDetails',
47
+ 'xal' => "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0")
48
+
49
+ if address
50
+ l.street = value(address.elements['.//ThoroughfareName/text()'])
51
+ l.locality = value(address.elements['.//LocalityName/text()'])
52
+ l.region = value(address.elements['.//AdministrativeAreaName/text()'])
53
+ l.postal_code = value(address.elements['.//PostalCodeNumber/text()'])
54
+ l.country = value(address.elements['.//CountryNameCode/text()'])
55
+ l.precision = PRECISION[address.attribute('Accuracy').value.to_i] || :unknown
56
+ end
57
+ end
58
+ end
59
+
60
+ # Extracts and raises an error from +xml+, if any.
61
+ def check_error(xml) #:nodoc:
62
+ status = xml.elements['/kml/Response/Status/code'].text.to_i
63
+ case status
64
+ when 200 then # ignore, ok
65
+ when 500 then
66
+ raise Error, 'server error'
67
+ when 601 then
68
+ raise AddressError, 'missing address'
69
+ when 602 then
70
+ raise AddressError, 'unknown address'
71
+ when 603 then
72
+ raise AddressError, 'unavailable address'
73
+ when 610 then
74
+ raise CredentialsError, 'invalid key'
75
+ when 620 then
76
+ raise CredentialsError, 'too many queries'
77
+ else
78
+ raise Error, "unknown error #{status}"
79
+ end
80
+ end
81
+
82
+ # Creates a URL from the Hash +params+. Automatically adds the key and
83
+ # sets the output type to 'xml'.
84
+ def make_url(params) #:nodoc:
85
+ params[:key] = @key
86
+ params[:output] = 'xml'
87
+
88
+ super params
89
+ end
90
+
91
+ def value(element)
92
+ element.value if element
93
+ end
94
+
95
+ def text(element)
96
+ element.text if element
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ require 'yaml'
2
+
3
+ module Graticule #:nodoc:
4
+ module Geocoder #:nodoc:
5
+
6
+ class HostIp < Base
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
+ get :ip => address, :position => true
15
+ end
16
+
17
+ private
18
+
19
+ def prepare_response(response)
20
+ # add new line so YAML.load doesn't puke
21
+ YAML.load(response + "\n")
22
+ end
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['City'] =~ /Unknown City/
36
+ raise AddressError, 'Private Address' if response['City'] =~ /Private Address/
37
+ end
38
+
39
+ end
40
+ end
41
+ 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 < Base
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 => :street, :locality => :city, :region => :state, :postal_code => :zip, :country => :country}
24
+ mapping.keys.inject({}) do |result,attribute|
25
+ result[mapping[attribute]] = location.attributes[attribute] unless location.attributes[attribute].blank?
26
+ result
27
+ end
28
+ end
29
+
30
+ def check_error(js)
31
+ raise AddressError, "Empty Response" if js.nil?
32
+ raise AddressError, 'Location not found' if js =~ /location not found/
33
+ end
34
+
35
+ def parse_response(js)
36
+ returning Location.new do |location|
37
+ coordinates = js.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
@@ -0,0 +1,87 @@
1
+ module Graticule #:nodoc:
2
+ module Geocoder #:nodoc:
3
+
4
+ # Mapquest requires both a client id and a password, which you can
5
+ # get by registering at:
6
+ # http://developer.mapquest.com/Home/Register?_devAPISignup_WAR_devAPISignup_action=signup&_devAPISignup_WAR_devAPISignup_clientType=Developer
7
+ #
8
+ # mq = Graticule.service(:mapquest).new(CLIENT_ID, PASSWORD)
9
+ # location = gg.locate('44 Allen Rd., Lovell, ME 04051')
10
+ # [42.78942, -86.104424]
11
+ #
12
+ class Mapquest < Rest
13
+ # I would link to the documentation here, but there is none that will do anything but confuse you.
14
+
15
+ PRECISION = {
16
+ 'L1' => :address,
17
+ 'I1' => :street,
18
+ 'B1' => :street,
19
+ 'B2' => :street,
20
+ 'B3' => :street,
21
+ 'Z3' => :zip,
22
+ 'Z4' => :zip,
23
+ 'Z2' => :zip,
24
+ 'Z1' => :zip,
25
+ 'A5' => :city,
26
+ 'A4' => :county,
27
+ 'A3' => :state,
28
+ 'A1' => :country
29
+ }
30
+
31
+ def initialize(client_id, password)
32
+ @password = password
33
+ @client_id = client_id
34
+ @url = URI.parse('http://geocode.dev.mapquest.com/mq/mqserver.dll')
35
+ end
36
+
37
+ # Locates +address+ returning a Location
38
+ def locate(address)
39
+ get :q => address.is_a?(String) ? address : location_from_params(address).to_s
40
+ end
41
+
42
+ protected
43
+
44
+ def make_url(params) #:nodoc
45
+ query = "e=5&<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><Geocode Version=\"1\"> \
46
+ #{address_string(params[:q])}#{authentication_string}</Geocode>"
47
+ url = @url.dup
48
+ url.query = URI.escape(query)
49
+ url
50
+ end
51
+
52
+ # Extracts a location from +xml+.
53
+ def parse_response(xml) #:nodoc:
54
+ longitude = xml.elements['/GeocodeResponse/LocationCollection/GeoAddress/LatLng/Lng'].text.to_f
55
+ latitude = xml.elements['/GeocodeResponse/LocationCollection/GeoAddress/LatLng/Lat'].text.to_f
56
+ returning Location.new(:latitude => latitude, :longitude => longitude) do |l|
57
+ address = REXML::XPath.first(xml, '/GeocodeResponse/LocationCollection/GeoAddress')
58
+
59
+ if address
60
+ l.street = value(address.elements['./Street/text()'])
61
+ l.locality = value(address.elements['./AdminArea5/text()'])
62
+ l.region = value(address.elements['./AdminArea3/text()'])
63
+ l.postal_code = value(address.elements['./PostalCode/text()'])
64
+ l.country = value(address.elements['./AdminArea1/text()'])
65
+ l.precision = PRECISION[value(address.elements['./ResultCode/text()'])[0,2]]
66
+ end
67
+ end
68
+ end
69
+
70
+ # Extracts and raises any errors in +xml+
71
+ def check_error(xml) #:nodoc
72
+ end
73
+
74
+ def value(element)
75
+ element.value if element
76
+ end
77
+
78
+ def authentication_string
79
+ "<Authentication Version=\"2\"><Password>#{@password}</Password><ClientId>#{@client_id}</ClientId></Authentication>"
80
+ end
81
+
82
+ def address_string(query)
83
+ "<Address><Street>#{query}</Street></Address><GeocodeOptionsCollection Count=\"0\"/>"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,33 @@
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, :output => 'locations'
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 parse_response(xml) # :nodoc:
26
+ result = xml.elements['/Locations/Location[1]']
27
+ coords = result.elements['Centroid/gml:Point/gml:coordinates'].text.split ','
28
+ Location.new :latitude => coords.first.to_f, :longitude => coords.last.to_f
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ module Graticule #:nodoc:
2
+ module Geocoder #:nodoc:
3
+ class Multi
4
+
5
+ # The Multi geocoder allows you to use multiple geocoders in succession.
6
+ #
7
+ # geocoder = Graticule.service(:multi).new(
8
+ # Graticule.service(:google).new("api_key"),
9
+ # Graticule.service(:yahoo).new("api_key"),
10
+ # )
11
+ # geocoder.locate '49423' # <= tries geocoders in succession
12
+ #
13
+ # The Multi geocoder will try the geocoders in order if a Graticule::AddressError
14
+ # is raised. You can customize this behavior by passing in a block to the Multi
15
+ # geocoder. For example, to try the geocoders until one returns a result with a
16
+ # high enough precision:
17
+ #
18
+ # geocoder = Graticule.service(:multi).new(geocoders) do |result|
19
+ # [:address, :street].include?(result.precision)
20
+ # end
21
+ #
22
+ # Geocoders will be tried in order until the block returned true for one of the results
23
+ #
24
+ def initialize(*geocoders, &acceptable)
25
+ @acceptable = acceptable || lambda { true }
26
+ @geocoders = geocoders.flatten
27
+ end
28
+
29
+ def locate(address)
30
+ last_error = nil
31
+ @geocoders.each do |geocoder|
32
+ begin
33
+ result = geocoder.locate address
34
+ return result if @acceptable.call(result)
35
+ rescue Error => e
36
+ last_error = e
37
+ rescue Errno::ECONNREFUSED
38
+ logger.error("Connection refused to #{service}")
39
+ end
40
+ end
41
+ raise last_error || AddressError.new("Couldn't find '#{address}' with any of the services")
42
+ end
43
+
44
+ end
45
+ end
46
+ end