graticule 0.2.2 → 0.2.3

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 (43) hide show
  1. data/CHANGELOG.txt +4 -0
  2. data/Manifest.txt +9 -0
  3. data/Rakefile +69 -0
  4. data/lib/graticule.rb +2 -0
  5. data/lib/graticule/cli.rb +6 -4
  6. data/lib/graticule/geocoder/base.rb +112 -0
  7. data/lib/graticule/geocoder/geocoder_us.rb +1 -1
  8. data/lib/graticule/geocoder/google.rb +5 -5
  9. data/lib/graticule/geocoder/host_ip.rb +9 -18
  10. data/lib/graticule/geocoder/local_search_maps.rb +4 -4
  11. data/lib/graticule/geocoder/meta_carta.rb +1 -6
  12. data/lib/graticule/geocoder/rest.rb +6 -98
  13. data/lib/graticule/geocoder/yahoo.rb +1 -13
  14. data/lib/graticule/version.rb +1 -1
  15. data/site/index.html +114 -0
  16. data/site/plugin.html +82 -0
  17. data/site/stylesheets/style.css +73 -0
  18. data/test/config.yml.default +36 -0
  19. data/test/fixtures/responses/geocoder_us/success.xml +13 -5
  20. data/test/fixtures/responses/geocoder_us/unknown.xml +1 -1
  21. data/test/fixtures/responses/google/badkey.xml +1 -10
  22. data/test/fixtures/responses/google/missing_address.xml +1 -10
  23. data/test/fixtures/responses/google/partial.xml +1 -0
  24. data/test/fixtures/responses/google/success.xml +1 -37
  25. data/test/fixtures/responses/google/unavailable.xml +1 -10
  26. data/test/fixtures/responses/google/unknown_address.xml +1 -10
  27. data/test/fixtures/responses/host_ip/private.txt +1 -1
  28. data/test/fixtures/responses/host_ip/success.txt +1 -1
  29. data/test/fixtures/responses/host_ip/unknown.txt +1 -1
  30. data/test/fixtures/responses/local_search_maps/empty.txt +1 -0
  31. data/test/fixtures/responses/local_search_maps/not_found.txt +1 -0
  32. data/test/fixtures/responses/local_search_maps/success.txt +1 -1
  33. data/test/fixtures/responses/meta_carta/bad_address.xml +15 -7
  34. data/test/fixtures/responses/meta_carta/success.xml +29 -21
  35. data/test/fixtures/responses/yahoo/success.xml +2 -2
  36. data/test/fixtures/responses/yahoo/unknown_address.xml +6 -6
  37. data/test/mocks/uri.rb +3 -2
  38. data/test/unit/graticule/geocoder/google_test.rb +14 -0
  39. data/test/unit/graticule/geocoder/host_ip_test.rb +1 -0
  40. data/test/unit/graticule/geocoder/meta_carta_test.rb +1 -1
  41. data/test/unit/graticule/location_test.rb +1 -1
  42. metadata +10 -3
  43. data/test/unit/graticule/cli_test.rb +0 -26
data/CHANGELOG.txt CHANGED
@@ -1,3 +1,7 @@
1
+ 0.2.3 (2007-04-27)
2
+ * fixed Google for less precise queries
3
+ * added User-Agent to coerce Google into returning UTF-8 (Jonathan Tron)
4
+
1
5
  0.2.2 (2007-03-27)
2
6
  * fixed LocalSearchMaps
3
7
 
data/Manifest.txt CHANGED
@@ -12,6 +12,7 @@ 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/geocoder/base.rb
15
16
  lib/graticule/geocoder/bogus.rb
16
17
  lib/graticule/geocoder/geocoder_ca.rb
17
18
  lib/graticule/geocoder/geocoder_us.rb
@@ -24,11 +25,16 @@ lib/graticule/geocoder/rest.rb
24
25
  lib/graticule/geocoder/yahoo.rb
25
26
  lib/graticule/location.rb
26
27
  lib/graticule/version.rb
28
+ site/index.html
29
+ site/plugin.html
30
+ site/stylesheets/style.css
31
+ test/config.yml.default
27
32
  test/fixtures/responses/geocoder_us/success.xml
28
33
  test/fixtures/responses/geocoder_us/unknown.xml
29
34
  test/fixtures/responses/google/badkey.xml
30
35
  test/fixtures/responses/google/limit.xml
31
36
  test/fixtures/responses/google/missing_address.xml
37
+ test/fixtures/responses/google/partial.xml
32
38
  test/fixtures/responses/google/server_error.xml
33
39
  test/fixtures/responses/google/success.xml
34
40
  test/fixtures/responses/google/unavailable.xml
@@ -36,6 +42,8 @@ test/fixtures/responses/google/unknown_address.xml
36
42
  test/fixtures/responses/host_ip/private.txt
37
43
  test/fixtures/responses/host_ip/success.txt
38
44
  test/fixtures/responses/host_ip/unknown.txt
45
+ test/fixtures/responses/local_search_maps/empty.txt
46
+ test/fixtures/responses/local_search_maps/not_found.txt
39
47
  test/fixtures/responses/local_search_maps/success.txt
40
48
  test/fixtures/responses/meta_carta/bad_address.xml
41
49
  test/fixtures/responses/meta_carta/multiple.xml
@@ -59,3 +67,4 @@ test/unit/graticule/geocoder/postcode_anywhere_test.rb
59
67
  test/unit/graticule/geocoder/yahoo_test.rb
60
68
  test/unit/graticule/geocoder_test.rb
61
69
  test/unit/graticule/location_test.rb
70
+
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'active_support'
2
3
  require 'hoe'
3
4
  require File.join(File.dirname(__FILE__), 'lib', 'graticule', 'version.rb')
4
5
 
@@ -15,3 +16,71 @@ Hoe.new("graticule", Graticule::Version::STRING) do |p|
15
16
  p.changes = p.paragraphs_of('CHANGELOG.txt', 0..1).join("\n\n")
16
17
  p.extra_deps << ['activesupport']
17
18
  end
19
+
20
+
21
+
22
+ namespace :test do
23
+ namespace :cache do
24
+ desc 'Cache test responses from all the free geocoders'
25
+ task :free => [:google, :geocoder_us, :host_ip, :local_search_maps, :meta_carta, :yahoo]
26
+
27
+ desc 'Cache test responses from Google'
28
+ task :google do
29
+ cache_responses('google')
30
+ end
31
+
32
+ desc 'Cache test responses from Geocoder.us'
33
+ task :geocoder_us do
34
+ cache_responses('geocoder_us')
35
+ end
36
+
37
+ desc 'Cache test responses from HostIP'
38
+ task :host_ip do
39
+ cache_responses('host_ip')
40
+ end
41
+
42
+ desc 'Cache test responses from Local Search Maps'
43
+ task :local_search_maps do
44
+ cache_responses('local_search_maps')
45
+ end
46
+
47
+ desc 'Cache test responses from Meta Carta'
48
+ task :meta_carta do
49
+ cache_responses('meta_carta')
50
+ end
51
+
52
+ desc 'Cache test responses from Yahoo'
53
+ task :yahoo do
54
+ cache_responses('yahoo')
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ require 'net/http'
61
+ require 'uri'
62
+ RESPONSES_PATH = File.dirname(__FILE__) + '/test/fixtures/responses'
63
+
64
+ def cache_responses(service)
65
+ test_config[service.to_s]['responses'].each do |file,url|
66
+ File.open("#{RESPONSES_PATH}/#{service}/#{file}", 'w') do |f|
67
+ f.puts Net::HTTP.get(URI.parse(url))
68
+ end
69
+ end
70
+ end
71
+
72
+ def test_config
73
+ file = File.dirname(__FILE__) + '/test/config.yml'
74
+ raise "Copy config.yml.default to config.yml and set the API keys" unless File.exists?(file)
75
+ @test_config ||= returning(YAML.load(File.read(file))) do |config|
76
+ config.each do |service,values|
77
+ values['responses'].each {|file,url| update_placeholders!(values, url) }
78
+ end
79
+ end
80
+ end
81
+
82
+ def update_placeholders!(config, thing)
83
+ config.each do |option, value|
84
+ thing.gsub!(":#{option}", value) if value.is_a?(String)
85
+ end
86
+ end
data/lib/graticule.rb CHANGED
@@ -2,8 +2,10 @@ $:.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require 'active_support'
4
4
 
5
+ require 'graticule/version'
5
6
  require 'graticule/location'
6
7
  require 'graticule/geocoder'
8
+ require 'graticule/geocoder/base'
7
9
  require 'graticule/geocoder/bogus'
8
10
  require 'graticule/geocoder/rest'
9
11
  require 'graticule/geocoder/google'
data/lib/graticule/cli.rb CHANGED
@@ -20,7 +20,7 @@ module Graticule
20
20
  # -h, --help Help
21
21
  class Cli
22
22
 
23
- def self.start(args)
23
+ def self.start(args, out = STDOUT)
24
24
  options = { :service => :yahoo, :api_key => 'YahooDemo' }
25
25
 
26
26
  OptionParser.new do |opts|
@@ -42,12 +42,14 @@ module Graticule
42
42
 
43
43
  options[:location] = args.join(" ")
44
44
 
45
- result = Graticule.service(options[:service]).new(options[:api_key]).locate(options[:location])
45
+ result = Graticule.service(options[:service]).new(*options[:api_key].split(',')).locate(options[:location])
46
46
  location = (result.is_a?(Array) ? result.first : result)
47
47
  if location
48
- puts location.to_s(:coordinates => true)
48
+ out << location.to_s(:coordinates => true)
49
+ exit 0
49
50
  else
50
- puts "Location not found"
51
+ out << "Location not found"
52
+ exit 1
51
53
  end
52
54
  rescue Graticule::CredentialsError
53
55
  $stderr.puts "Invalid API key. Pass your #{options[:service]} API key using the -a option. "
@@ -0,0 +1,112 @@
1
+ require 'open-uri'
2
+
3
+ module Graticule #:nodoc:
4
+ module Geocoder
5
+
6
+ # Abstract class for implementing geocoders.
7
+ #
8
+ # === Example
9
+ #
10
+ # The following methods must be implemented in sublcasses:
11
+ #
12
+ # * +initialize+:: Sets @url to the service enpoint.
13
+ # * +check_error+:: Checks for errors in the server response.
14
+ # * +parse_response+:: Extracts information from the server response.
15
+ #
16
+ # Optionally, you can also override
17
+ #
18
+ # * +prepare_response+:: Convert the string response into a different format
19
+ # that gets passed on to +check_error+ and +parse_response+.
20
+ #
21
+ # If you have extra URL paramaters (application id, output type) or need to
22
+ # perform URL customization, override +make_url+.
23
+ #
24
+ # class FakeGeocoder < Base
25
+ #
26
+ # def initialize(appid)
27
+ # @appid = appid
28
+ # @url = URI.parse 'http://example.com/test'
29
+ # end
30
+ #
31
+ # def locate(query)
32
+ # get :q => query
33
+ # end
34
+ #
35
+ # private
36
+ #
37
+ # def check_error(xml)
38
+ # raise Error, xml.elements['error'].text if xml.elements['error']
39
+ # end
40
+ #
41
+ # def make_url(params)
42
+ # params[:appid] = @appid
43
+ # super params
44
+ # end
45
+ #
46
+ # def parse_response(response)
47
+ # # return Location
48
+ # end
49
+ #
50
+ # end
51
+ #
52
+ class Base
53
+ USER_AGENT = "Mozilla/5.0 (compatible; Graticule/#{Graticule::Version::STRING}; http://graticule.rubyforge.org)"
54
+
55
+ def initialize
56
+ raise NotImplementedError
57
+ end
58
+
59
+ private
60
+
61
+ def location_from_params(params)
62
+ case params
63
+ when Location then params
64
+ when Hash then Location.new params
65
+ else
66
+ raise ArgumentError, "Expected a Graticule::Location or a hash with :street, :locality, :region, :postal_code, and :country attributes"
67
+ end
68
+ end
69
+
70
+ # Check for errors in +response+ and raise appropriate error, if any.
71
+ # Must return if no error could be found.
72
+ def check_error(response)
73
+ raise NotImplementedError
74
+ end
75
+
76
+ # Performs a GET request with +params+. Calls +check_error+ and returns
77
+ # the result of +parse_response+.
78
+ def get(params = {})
79
+ response = prepare_response(make_url(params).open('User-Agent' => USER_AGENT).read)
80
+ check_error(response)
81
+ return parse_response(response)
82
+ rescue OpenURI::HTTPError => e
83
+ check_error(prepare_response(e.io.read))
84
+ raise
85
+ end
86
+
87
+ # Creates a URI from the Hash +params+. Override this then call super if
88
+ # you need to add extra params like an application id or output type.
89
+ def make_url(params)
90
+ escaped_params = params.sort_by { |k,v| k.to_s }.map do |k,v|
91
+ "#{URI.escape k.to_s}=#{URI.escape v.to_s}"
92
+ end
93
+
94
+ url = @url.dup
95
+ url.query = escaped_params.join '&'
96
+ return url
97
+ end
98
+
99
+ # Override to convert the response to something besides a String, which
100
+ # will get passed to +check_error+ and +parse_response+.
101
+ def prepare_response(response)
102
+ response
103
+ end
104
+
105
+ # Must parse results from +response+ into a Location.
106
+ def parse_response(response)
107
+ raise NotImplementedError
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -39,7 +39,7 @@ module Graticule #:nodoc:
39
39
  end
40
40
 
41
41
  def check_error(xml) #:nodoc:
42
- raise AddressError, xml.text if xml.text == 'couldn\'t find this address! sorry'
42
+ raise AddressError, xml.text if xml.text =~ /couldn't find this address! sorry/
43
43
  end
44
44
 
45
45
  end
@@ -51,11 +51,11 @@ module Graticule #:nodoc:
51
51
  longitude, latitude, = xml.elements['/kml/Response/Placemark/Point/coordinates'].text.split(',').map { |v| v.to_f }
52
52
 
53
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()']),
54
+ :street => value(address.elements['//ThoroughfareName/text()']),
55
+ :locality => value(address.elements['//LocalityName/text()']),
56
+ :region => value(address.elements['//AdministrativeAreaName/text()']),
57
+ :postal_code => value(address.elements['//PostalCodeNumber/text()']),
58
+ :country => value(address.elements['//CountryNameCode/text()']),
59
59
  :latitude => latitude,
60
60
  :longitude => longitude,
61
61
  :precision => PRECISION[address.attribute('Accuracy').value.to_i] || :unknown
@@ -3,7 +3,7 @@ require 'yaml'
3
3
  module Graticule #:nodoc:
4
4
  module Geocoder #:nodoc:
5
5
 
6
- class HostIp
6
+ class HostIp < Base
7
7
 
8
8
  def initialize
9
9
  @url = URI.parse 'http://api.hostip.info/get_html.php'
@@ -11,15 +11,15 @@ module Graticule #:nodoc:
11
11
 
12
12
  # Geocode an IP address using http://hostip.info
13
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
14
+ get :ip => address, :position => true
20
15
  end
21
16
 
22
17
  private
18
+
19
+ def read_response(response)
20
+ # add new line so YAML.load doesn't puke
21
+ YAML.load(response + "\n")
22
+ end
23
23
 
24
24
  def parse_response(response) #:nodoc:
25
25
  returning Location.new do |location|
@@ -32,19 +32,10 @@ module Graticule #:nodoc:
32
32
  end
33
33
 
34
34
  def check_error(response) #:nodoc:
35
- raise AddressError, 'Unknown' if response =~ /Unknown City/
36
- raise AddressError, 'Private Address' if response =~ /Private Address/
35
+ raise AddressError, 'Unknown' if response['City'] =~ /Unknown City/
36
+ raise AddressError, 'Private Address' if response['City'] =~ /Private Address/
37
37
  end
38
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
39
  end
49
40
  end
50
41
  end
@@ -4,7 +4,7 @@ module Graticule #:nodoc:
4
4
  # A library for lookup of coordinates with http://geo.localsearchmaps.com/
5
5
  #
6
6
  # See http://emad.fano.us/blog/?p=277
7
- class LocalSearchMaps < Rest
7
+ class LocalSearchMaps < Base
8
8
 
9
9
  def initialize
10
10
  @url = URI.parse 'http://geo.localsearchmaps.com/'
@@ -28,13 +28,13 @@ module Graticule #:nodoc:
28
28
  end
29
29
 
30
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/
31
+ raise AddressError, "Empty Response" if js.nil?
32
+ raise AddressError, 'Location not found' if js =~ /location not found/
33
33
  end
34
34
 
35
35
  def parse_response(js)
36
36
  returning Location.new do |location|
37
- coordinates = js.text.match(/map.centerAndZoom\(new GPoint\((.+?), (.+?)\)/)
37
+ coordinates = js.match(/map.centerAndZoom\(new GPoint\((.+?), (.+?)\)/)
38
38
  location.longitude = coordinates[1].to_f
39
39
  location.latitude = coordinates[2].to_f
40
40
  end
@@ -13,7 +13,7 @@ module Graticule
13
13
 
14
14
  # Finds +location+ and returns a Location object.
15
15
  def locate(location)
16
- get :q => location.is_a?(String) ? location : location_from_params(location).to_s
16
+ get :q => location.is_a?(String) ? location : location_from_params(location).to_s, :output => 'locations'
17
17
  end
18
18
 
19
19
  private
@@ -22,11 +22,6 @@ module Graticule
22
22
  raise AddressError, 'bad location' unless xml.elements['Locations/Location']
23
23
  end
24
24
 
25
- def make_url(params) # :nodoc:
26
- params[:output] = 'locations'
27
- super params
28
- end
29
-
30
25
  def parse_response(xml) # :nodoc:
31
26
  result = xml.elements['/Locations/Location[1]']
32
27
  coords = result.elements['Centroid/gml:Point/gml:coordinates'].text.split ','
@@ -1,110 +1,18 @@
1
- require 'open-uri'
2
1
  require 'rexml/document'
3
2
 
4
3
  module Graticule #:nodoc:
5
4
  module Geocoder #:nodoc:
6
5
 
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
6
+ # Abstract class for implementing REST geocoders. Passes on a REXML::Document
7
+ # to +check_errors+ and +parse_response+.
8
+ class Rest < Base
48
9
 
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
10
  private
58
11
 
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
12
+ def prepare_response(response)
13
+ REXML::Document.new(response)
89
14
  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
-
15
+
108
16
  end
109
17
  end
110
18
  end