GUI-graticule 0.2.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/CHANGELOG.txt +46 -0
  2. data/LICENSE.txt +30 -0
  3. data/Manifest.txt +73 -0
  4. data/README.txt +29 -0
  5. data/Rakefile +86 -0
  6. data/bin/geocode +5 -0
  7. data/init.rb +2 -0
  8. data/lib/graticule.rb +26 -0
  9. data/lib/graticule/cli.rb +64 -0
  10. data/lib/graticule/distance.rb +29 -0
  11. data/lib/graticule/distance/haversine.rb +40 -0
  12. data/lib/graticule/distance/spherical.rb +52 -0
  13. data/lib/graticule/distance/vincenty.rb +76 -0
  14. data/lib/graticule/geocoder.rb +21 -0
  15. data/lib/graticule/geocoder/base.rb +138 -0
  16. data/lib/graticule/geocoder/bogus.rb +15 -0
  17. data/lib/graticule/geocoder/geocoder_ca.rb +54 -0
  18. data/lib/graticule/geocoder/geocoder_us.rb +49 -0
  19. data/lib/graticule/geocoder/google.rb +122 -0
  20. data/lib/graticule/geocoder/host_ip.rb +41 -0
  21. data/lib/graticule/geocoder/local_search_maps.rb +45 -0
  22. data/lib/graticule/geocoder/map_quest.rb +111 -0
  23. data/lib/graticule/geocoder/meta_carta.rb +33 -0
  24. data/lib/graticule/geocoder/multi.rb +80 -0
  25. data/lib/graticule/geocoder/postcode_anywhere.rb +63 -0
  26. data/lib/graticule/geocoder/rest.rb +18 -0
  27. data/lib/graticule/geocoder/yahoo.rb +102 -0
  28. data/lib/graticule/location.rb +488 -0
  29. data/lib/graticule/version.rb +9 -0
  30. data/site/index.html +114 -0
  31. data/site/plugin.html +82 -0
  32. data/site/stylesheets/style.css +73 -0
  33. data/test/config.yml.default +36 -0
  34. data/test/fixtures/responses/geocoder_us/success.xml +18 -0
  35. data/test/fixtures/responses/geocoder_us/unknown.xml +1 -0
  36. data/test/fixtures/responses/google/badkey.xml +1 -0
  37. data/test/fixtures/responses/google/limit.xml +10 -0
  38. data/test/fixtures/responses/google/missing_address.xml +1 -0
  39. data/test/fixtures/responses/google/only_coordinates.xml +1 -0
  40. data/test/fixtures/responses/google/partial.xml +1 -0
  41. data/test/fixtures/responses/google/server_error.xml +10 -0
  42. data/test/fixtures/responses/google/success.xml +1 -0
  43. data/test/fixtures/responses/google/unavailable.xml +1 -0
  44. data/test/fixtures/responses/google/unknown_address.xml +1 -0
  45. data/test/fixtures/responses/host_ip/private.txt +4 -0
  46. data/test/fixtures/responses/host_ip/success.txt +4 -0
  47. data/test/fixtures/responses/host_ip/unknown.txt +4 -0
  48. data/test/fixtures/responses/local_search_maps/empty.txt +1 -0
  49. data/test/fixtures/responses/local_search_maps/not_found.txt +1 -0
  50. data/test/fixtures/responses/local_search_maps/success.txt +1 -0
  51. data/test/fixtures/responses/meta_carta/bad_address.xml +17 -0
  52. data/test/fixtures/responses/meta_carta/multiple.xml +33 -0
  53. data/test/fixtures/responses/meta_carta/success.xml +31 -0
  54. data/test/fixtures/responses/postcode_anywhere/badkey.xml +9 -0
  55. data/test/fixtures/responses/postcode_anywhere/canada.xml +16 -0
  56. data/test/fixtures/responses/postcode_anywhere/empty.xml +16 -0
  57. data/test/fixtures/responses/postcode_anywhere/success.xml +16 -0
  58. data/test/fixtures/responses/postcode_anywhere/uk.xml +18 -0
  59. data/test/fixtures/responses/yahoo/success.xml +3 -0
  60. data/test/fixtures/responses/yahoo/unknown_address.xml +6 -0
  61. data/test/mocks/uri.rb +52 -0
  62. data/test/test_helper.rb +32 -0
  63. data/test/unit/graticule/distance_test.rb +58 -0
  64. data/test/unit/graticule/geocoder/geocoder_us_test.rb +43 -0
  65. data/test/unit/graticule/geocoder/google_test.rb +126 -0
  66. data/test/unit/graticule/geocoder/host_ip_test.rb +40 -0
  67. data/test/unit/graticule/geocoder/local_search_maps_test.rb +30 -0
  68. data/test/unit/graticule/geocoder/meta_carta_test.rb +44 -0
  69. data/test/unit/graticule/geocoder/multi_test.rb +43 -0
  70. data/test/unit/graticule/geocoder/postcode_anywhere_test.rb +50 -0
  71. data/test/unit/graticule/geocoder/yahoo_test.rb +58 -0
  72. data/test/unit/graticule/geocoder_test.rb +27 -0
  73. data/test/unit/graticule/location_test.rb +66 -0
  74. metadata +158 -0
@@ -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(parse_type, 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(parse_type, 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,111 @@
1
+ module Graticule #:nodoc:
2
+ module Geocoder #:nodoc:
3
+
4
+ # First you need a Mapquest API key. You can register for one here:
5
+ # http://www.mapquest.com/features/main.adp?page=developer_tools_oapi
6
+ #
7
+ # Then you create a MapquestGeocode object and start locating addresses:
8
+ #
9
+ # gg = Graticule.service(:map_quest).new(MAPS_API_KEY)
10
+ # location = gg.locate :street => '1600 Amphitheater Pkwy', :locality => 'Mountain View', :region => 'CA'
11
+ # p location.coordinates
12
+ #
13
+ class MapQuest < Rest
14
+ # http://trc.mapquest.com
15
+
16
+ PRECISION = {
17
+ 0 => Precision.unknown,
18
+ 'COUNTRY' => Precision.country,
19
+ 'STATE' => Precision.state,
20
+ 'COUNTY' => Precision.state,
21
+ 'CITY' => Precision.city,
22
+ 'ZIP' => Precision.zip,
23
+ 'ZIP7' => Precision.zip,
24
+ 'ZIP9' => Precision.zip,
25
+ 'INTERSECTIONS' => Precision.street,
26
+ 'STREET' => Precision.street,
27
+ 'ADDRESS' => Precision.address
28
+ }
29
+
30
+ # Creates a new MapquestGeocode that will use Mapquest API key +key+.
31
+ #
32
+ # WARNING: The MapQuest API Keys tend to be already URI encoded. If this is the case
33
+ # be sure to URI.unescape the key in the call to new
34
+ def initialize(key)
35
+ @key = key
36
+ @url = URI.parse 'http://web.openapi.mapquest.com/oapi/transaction'
37
+ end
38
+
39
+ # Locates +address+ returning a Location
40
+ def locate(address)
41
+ get map_attributes(location_from_params(address))
42
+ end
43
+
44
+ private
45
+
46
+ def map_attributes(location)
47
+ mapping = {:street => :address, :locality => :city, :region => :stateProvince, :postal_code => :postalcoe, :country => :country}
48
+ mapping.keys.inject({}) do |result,attribute|
49
+ result[mapping[attribute]] = location.attributes[attribute] unless location.attributes[attribute].blank?
50
+ result
51
+ end
52
+ end
53
+
54
+ # Extracts a Location from +xml+.
55
+ def parse_response(parse_type, xml) #:nodoc:
56
+ address = REXML::XPath.first(xml, '/advantage/geocode/locations/location')
57
+
58
+ Location.new \
59
+ :street => value(address.elements['//address/text()']),
60
+ :locality => value(address.elements['//city/text()']),
61
+ :region => value(address.elements['//stateProvince/text()']),
62
+ :postal_code => value(address.elements['//postalCode/text()']),
63
+ :country => value(address.elements['//country/text()']),
64
+ :latitude => value(address.elements['//latitude/text()']),
65
+ :longitude => value(address.elements['//longitude/text()']),
66
+ :precision => PRECISION[address.elements['//geocodeQuality'].text] || :unknown
67
+ end
68
+
69
+ # Extracts and raises an error from +xml+, if any.
70
+ def check_error(xml) #:nodoc:
71
+ return unless xml.elements['//error']
72
+ status = xml.elements['//error/code'].text.to_i
73
+ msg = xml.elements['//error/text'].text
74
+ case status
75
+ when 255..299 then
76
+ raise CredentialsError, msg
77
+ when 400..500 then
78
+ raise AddressError, msg
79
+ when 600.699 then
80
+ raise Error, msg
81
+ when 900 then
82
+ raise AddressError, 'invalid latitude'
83
+ when 901 then
84
+ raise AddressError, 'invalid longitude'
85
+ when 902 then
86
+ raise AddressError, 'error parsing params'
87
+ when 9902 then
88
+ raise CredentialsError, 'invalid key'
89
+ when 9904 then
90
+ raise CredentialsError, 'key missing'
91
+ else
92
+ raise Error, "unknown error #{status}: #{msg}"
93
+ end
94
+ end
95
+
96
+ # Creates a URL from the Hash +params+. Automatically adds the key and
97
+ # sets the output type to 'xml'.
98
+ def make_url(params) #:nodoc:
99
+ super params.merge({:key => @key, :transaction => 'geocode', :ambiguities => 0})
100
+ end
101
+
102
+ def value(element)
103
+ element.value if element
104
+ end
105
+
106
+ def text(element)
107
+ element.text if element
108
+ end
109
+ end
110
+ end
111
+ 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(parse_type, 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,80 @@
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
+ # result.precision >= Graticule::Precision.street
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(*args)
30
+ mode, address = nil, nil
31
+ case args.first
32
+ when :first
33
+ mode = :first
34
+ address = args[1]
35
+ when :all
36
+ mode = :all
37
+ address = args[1]
38
+ else
39
+ mode = :first
40
+ address = args.first
41
+ end
42
+
43
+ locations = Locations.new
44
+ last_error = nil
45
+
46
+ catch :found_first do
47
+ @geocoders.each do |geocoder|
48
+ begin
49
+ geocoder_locations = geocoder.locate(:all, address)
50
+
51
+ geocoder_locations.each do |location|
52
+ if(@acceptable.call(location))
53
+ locations << location
54
+
55
+ if(mode == :first)
56
+ throw :found_first
57
+ end
58
+ end
59
+ end
60
+ rescue Errno::ECONNREFUSED
61
+ logger.error("Connection refused to #{service}")
62
+ rescue Error => e
63
+ last_error = e
64
+ end
65
+ end
66
+ end
67
+
68
+ if(mode == :first)
69
+ if(locations.first)
70
+ locations.first
71
+ else
72
+ raise last_error || AddressError.new("Couldn't find '#{address}' with any of the services")
73
+ end
74
+ else
75
+ locations
76
+ end
77
+ end
78
+ end
79
+ end
80
+ 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(parse_type, 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,102 @@
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"=> Precision.country,
11
+ "state" => Precision.state,
12
+ "city" => Precision.city,
13
+ "zip+4" => Precision.zip,
14
+ "zip+2" => Precision.zip,
15
+ "zip" => Precision.zip,
16
+ "street" => Precision.street,
17
+ "address" => Precision.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(*args)
41
+ case args.first
42
+ when :first then get :first, :location => extract_location(args[1])
43
+ when :all then get :all, :location => extract_location(args[1])
44
+ else get :first, :location => extract_location(args.first)
45
+ end
46
+ end
47
+
48
+ def extract_location(address)
49
+ location = (address.is_a?(String) ? address : location_from_params(address).to_s(:country => false))
50
+ # yahoo pukes on line breaks
51
+ location.gsub("\n", ', ')
52
+ end
53
+
54
+ def parse_response(parse_type, xml)
55
+ if parse_type == :all
56
+ locations = []
57
+ xml.elements['ResultSet'].each do |element|
58
+ locations << parse_location_xml(element)
59
+ end
60
+ return locations
61
+ else
62
+ return parse_location_xml(xml.elements['ResultSet/Result[1]'])
63
+ end
64
+ end
65
+
66
+ def parse_location_xml(element)
67
+ returning Location.new do |location|
68
+ location.precision = PRECISION[element.attributes['precision']] || Precision.unknown
69
+
70
+ if element.attributes.include? 'warning' then
71
+ location.warning = element.attributes['warning']
72
+ end
73
+ location.latitude = element.elements['Latitude'].text.to_f
74
+ location.longitude = element.elements['Longitude'].text.to_f
75
+
76
+ location.street = element.elements['Address'].text unless element.elements['Address'].text.blank?
77
+ location.locality = element.elements['City'].text unless element.elements['City'].text.blank?
78
+ location.region = element.elements['State'].text
79
+ location.postal_code = element.elements['Zip'].text
80
+ location.country = element.elements['Country'].text
81
+ end
82
+ end
83
+
84
+ # Extracts and raises an error from +xml+, if any.
85
+ def check_error(xml) #:nodoc:
86
+ err = xml.elements['Error']
87
+ raise Error, err.elements['Message'].text if err
88
+ end
89
+
90
+ # Creates a URL from the Hash +params+. Automatically adds the appid and
91
+ # sets the output type to 'xml'.
92
+ def make_url(params) #:nodoc:
93
+ params[:appid] = @appid
94
+ params[:output] = 'xml'
95
+
96
+ super params
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+ end