sayso-geokit 1.5.0.3.001 → 1.5.0.3.002

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,185 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/geocoders_v3')
2
+
3
+ module Geokit
4
+ module Geocoders
5
+ # -------------------------------------------------------------------------------------------
6
+ # Address geocoders that also provide reverse geocoding
7
+ # -------------------------------------------------------------------------------------------
8
+
9
+ # Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
10
+ # contain a Google API key. Conforms to the interface set by the Geocoder class.
11
+ class GoogleGeocoder < Geocoder
12
+
13
+ private
14
+
15
+ # Template method which does the reverse-geocode lookup.
16
+ def self.do_reverse_geocode(latlng)
17
+ latlng = LatLng.normalize(latlng)
18
+ url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=#{Geokit::Inflector.url_escape(latlng.ll)}&sensor=true"
19
+ res = call_geocoder_service(url)
20
+ return GeoLoc.new if res.nil?
21
+ toGeoLoc(res)
22
+ end
23
+
24
+ # Template method which does the geocode lookup.
25
+ #
26
+ # Supports viewport/country code biasing
27
+ #
28
+ # ==== OPTIONS
29
+ # * :bias - This option makes the Google Geocoder return results biased to a particular
30
+ # country or viewport. Country code biasing is achieved by passing the ccTLD
31
+ # ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
32
+ # look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
33
+ # will be biased to results within the US (ccTLD .com).
34
+ #
35
+ # If you'd like the Google Geocoder to prefer results within a given viewport,
36
+ # you can pass a Geokit::Bounds object as the :bias value.
37
+ #
38
+ # ==== EXAMPLES
39
+ # # By default, the geocoder will return Syracuse, NY
40
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
41
+ # # With country code biasing, it returns Syracuse in Sicily, Italy
42
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
43
+ #
44
+ # # By default, the geocoder will return Winnetka, IL
45
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
46
+ # # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
47
+ # bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
48
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
49
+ #
50
+
51
+ ACCURACY = {
52
+ 'street_address' => 8,
53
+ 'route' => 6,
54
+ # 'intersection' => '',
55
+ 'country' => 1,
56
+ 'administrative_area_level_1' => 2,
57
+ 'administrative_area_level_2' => 3,
58
+ 'administrative_area_level_3' => 3, #supposed value
59
+ 'colloquial_area' => '',
60
+ 'locality' => 4,
61
+ # 'sublocality' => '',
62
+ # 'neighborhood' => '',
63
+ # 'premise' => '',
64
+ # 'subpremise' => '',
65
+ 'postal_code' => 5,
66
+ 'natural_feature' => 0,
67
+ 'airport' => 8,
68
+ 'park' => 8,
69
+ 'point_of_interest' => 9,
70
+ 'post_box' => 9,
71
+ 'street_number' => 9,
72
+ 'transit_station' => 9,
73
+ 'establishment' => 9,
74
+ 'floor' => 9,
75
+ 'room' => 9
76
+ }
77
+
78
+ def self.do_geocode(address, options = {})
79
+ bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
80
+ lang_str = options[:lang].to_s.empty? ? '' : "&language=#{options[:lang]}"
81
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
82
+ return nil if address_str.to_s.strip.empty?
83
+ url = "http://maps.googleapis.com/maps/api/geocode/json?address=#{Geokit::Inflector.url_escape(address_str)}#{bias_str}#{lang_str}&sensor=true"
84
+ res = call_geocoder_service(url)
85
+ raise Geokit::InvalidResponseError if res.nil?
86
+ toGeoLoc(res)
87
+ end
88
+
89
+ def self.construct_bias_string_from_options(bias)
90
+ if bias.is_a?(String) or bias.is_a?(Symbol)
91
+ # country code biasing
92
+ "&gl=#{bias.to_s.downcase}"
93
+ elsif bias.is_a?(Bounds)
94
+ # viewport biasing
95
+ "&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
96
+ end
97
+ end
98
+
99
+ def self.toGeoLoc(res)
100
+ response_code = res["status"]
101
+ if response_code == 'OK'
102
+ geoloc = nil
103
+ # Google can return multiple results as //Placemark elements.
104
+ # iterate through each and extract each placemark as a geoloc
105
+ res['results'].each do |e|
106
+ extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
107
+ if geoloc.nil?
108
+ # first time through, geoloc is still nil, so we make it the geoloc we just extracted
109
+ geoloc = extracted_geoloc
110
+ else
111
+ # second (and subsequent) iterations, we push additional
112
+ # geolocs onto "geoloc.all"
113
+ geoloc.all.push(extracted_geoloc)
114
+ end
115
+ end
116
+ return geoloc
117
+ else
118
+ if response_code == 'ZERO_RESULTS'
119
+ # non-existent address or a latlng in a remote location
120
+ return nil
121
+ elsif response_code == 'OVER_QUERY_LIMIT'
122
+ raise Geokit::TooManyQueriesError
123
+ else
124
+ raise Geokit::GeokitError
125
+ end
126
+ end
127
+ #rescue => e
128
+ logger.error "Caught an error during Google geocoding call: #{e.inspect}"
129
+ return GeoLoc.new
130
+ end
131
+
132
+ # extracts a single geoloc from a //placemark element in the google results xml
133
+ def self.extract_placemark(placemark)
134
+ res = GeoLoc.new
135
+ res.provider = 'google'
136
+
137
+ res.lat = placemark["geometry"]["location"]["lat"]
138
+ res.lng = placemark["geometry"]["location"]["lng"]
139
+
140
+ address_details = placemark['address_components']
141
+ address_details.each do |add|
142
+ case add['types'][0]
143
+ when 'country'
144
+ res.country_code = add['short_name']
145
+ res.country = add['long_name']
146
+ when 'administrative_area_level_1'
147
+ res.state = add['long_name']
148
+ when 'administrative_area_level_2'
149
+ res.province = add['long_name']
150
+ when 'locality'
151
+ res.city = add['long_name']
152
+ when 'sublocality'
153
+ res.district = add['long_name']
154
+ when 'route'
155
+ res.street = add['long_name']
156
+ when 'street_number'
157
+ res.street_number = add['long_name']
158
+ when 'postal_code'
159
+ res.zip = add['long_name']
160
+ end
161
+ end
162
+ res.full_address = placemark['formatted_address']
163
+ res.street_address = res.full_address.sub(Regexp.new("^(.*#{res.street})([^,]*)?(.*)$")){|full_addr| "#{$1}#{$2}"} if res.street
164
+
165
+ # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
166
+ # For Google, 1=low accuracy, 8=high accuracy
167
+ res.accuracy = ACCURACY[placemark['types'][0]] || 0
168
+ res.precision = %w{unknown country state state city zip zip+4 street address building}[res.accuracy]
169
+
170
+ # google returns a set of suggested boundaries for the geocoded result
171
+ if (suggested_bounds = placemark['geometry']['viewport'] || placemark['geometry']['bounds'])
172
+ res.suggested_bounds = Bounds.normalize(
173
+ [suggested_bounds['southwest']['lat'], suggested_bounds['southwest']['lng']],
174
+ [suggested_bounds['northeast']['lat'], suggested_bounds['northeast']['lng']])
175
+ end
176
+
177
+ res.success = true
178
+
179
+ res
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+
@@ -0,0 +1,118 @@
1
+ require 'timeout'
2
+ require 'logger'
3
+ require 'yajl'
4
+ require 'uri'
5
+ require 'yajl/version'
6
+ require 'yajl/http_stream'
7
+ require 'i18n'
8
+ require 'active_support'
9
+ require 'active_support/inflector'
10
+
11
+ module Geokit
12
+
13
+ class GeokitError < StandardError; end
14
+ class GeocoderServiceError < GeokitError; end
15
+ class TimeoutError < GeocoderServiceError; end
16
+ class InvalidResponseError < GeocoderServiceError; end
17
+ class InvalidStatusCodeError < GeocoderServiceError; end
18
+ class TooManyQueriesError < GeocoderServiceError; end
19
+
20
+ module Geocoders
21
+ @@request_timeout = nil
22
+ @@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
23
+ @@logger = Logger.new(STDOUT)
24
+ @@logger.level = Logger::INFO
25
+ @@domain = nil
26
+
27
+ def self.__define_accessors
28
+ class_variables.each do |v|
29
+ sym = v.to_s.delete("@").to_sym
30
+ unless self.respond_to? sym
31
+ module_eval <<-EOS, __FILE__, __LINE__
32
+ def self.#{sym}
33
+ value = if defined?(#{sym.to_s.upcase})
34
+ #{sym.to_s.upcase}
35
+ else
36
+ @@#{sym}
37
+ end
38
+ if value.is_a?(Hash)
39
+ value = (self.domain.nil? ? nil : value[self.domain]) || value.values.first
40
+ end
41
+ value
42
+ end
43
+
44
+ def self.#{sym}=(obj)
45
+ @@#{sym} = obj
46
+ end
47
+ EOS
48
+ end
49
+ end
50
+ end
51
+
52
+ __define_accessors
53
+
54
+ # -------------------------------------------------------------------------------------------
55
+ # Geocoder Base class -- every geocoder should inherit from this
56
+ # -------------------------------------------------------------------------------------------
57
+
58
+ # The Geocoder base class which defines the interface to be used by all
59
+ # other geocoders.
60
+ class Geocoder
61
+
62
+ # Main method which calls the do_geocode template method which subclasses
63
+ # are responsible for implementing. Returns a populated GeoLoc or an
64
+ # nil one with a failed success code.
65
+ def self.geocode(address, options = {})
66
+ if address.is_a?(String)
67
+ converted_address = ActiveSupport::Inflector.transliterate(address)
68
+ else
69
+ converted_address = address
70
+ end
71
+ do_geocode(converted_address, options)
72
+ end
73
+ # Main method which calls the do_reverse_geocode template method which subclasses
74
+ # are responsible for implementing. Returns a populated GeoLoc or an
75
+ # nil one with a failed success code.
76
+ def self.reverse_geocode(latlng)
77
+ do_reverse_geocode(latlng)
78
+ end
79
+
80
+ # Call the geocoder service using the timeout if configured.
81
+ def self.call_geocoder_service(url)
82
+ Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
83
+ logger.info "Getting geocode from #{url}"
84
+ return self.do_get(url)
85
+ rescue TimeoutError, Timeout::Error
86
+ raise Geokit::TimeoutError
87
+ rescue Yajl::HttpStream::HttpError
88
+ raise Geokit::InvalidResponseError
89
+ end
90
+
91
+ # Not all geocoders can do reverse geocoding. So, unless the subclass explicitly overrides this method,
92
+ # a call to reverse_geocode will return an empty GeoLoc. If you happen to be using MultiGeocoder,
93
+ # this will cause it to failover to the next geocoder, which will hopefully be one which supports reverse geocoding.
94
+ def self.do_reverse_geocode(latlng)
95
+ return nil
96
+ end
97
+
98
+ protected
99
+
100
+ def self.logger()
101
+ Geokit::Geocoders::logger
102
+ end
103
+
104
+ private
105
+
106
+ # Wraps the geocoder call around a proxy if necessary.
107
+ def self.do_get(url)
108
+ uri = URI.parse(url)
109
+ result = nil
110
+ Yajl::HttpStream.get(uri, {:check_utf8 => false}) do |data|
111
+ result = data
112
+ end
113
+ result
114
+ end
115
+
116
+ end
117
+ end
118
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: sayso-geokit
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.5.0.3.001
5
+ version: 1.5.0.3.002
6
6
  platform: ruby
7
7
  authors:
8
8
  - SaySo
@@ -50,10 +50,12 @@ files:
50
50
  - README.markdown
51
51
  - Rakefile
52
52
  - lib/geokit/geocoders.rb
53
+ - lib/geokit/geocoders_v3.rb
53
54
  - lib/geokit.rb
54
55
  - lib/geokit/mappable.rb
55
56
  - lib/geokit/inflector.rb
56
57
  - lib/geokit/geocoder_google.rb
58
+ - lib/geokit/geocoder_google_v3.rb
57
59
  - test/fake_geo_requests.rb
58
60
  - test/test_base_geocoder.rb
59
61
  - test/test_bounds.rb