geocoder 1.1.4 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geocoder might be problematic. Click here for more details.

data/CHANGELOG.rdoc CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  Per-release changes to Geocoder.
4
4
 
5
+ == 1.1.5 (2012 Nov 9)
6
+
7
+ * Replace support for old Yahoo Placefinder with Yahoo BOSS (thanks github.com/pwoltman).
8
+ * Add support for actual Mapquest API (was previously just a proxy for Nominatim), including the paid service (thanks github.com/jedschneider).
9
+ * Add support for :select => :id_only option to near scope.
10
+ * Treat a given query as blank (don't do a lookup) if coordinates are given but latitude or longitude is nil.
11
+ * Speed up 'near' queries by adding bounding box condition (thanks github.com/mlandauer).
12
+ * Fix: don't redefine Object#hash in Yahoo result object (thanks github.com/m0thman).
13
+
5
14
  == 1.1.4 (2012 Oct 2)
6
15
 
7
16
  * Deprecate Geocoder::Result::Nominatim#class and #type methods. Use #place_class and #place_type instead.
data/README.md CHANGED
@@ -263,7 +263,7 @@ By default Geocoder uses Google's geocoding API to fetch coordinates and street
263
263
  Geocoder.configure do |config|
264
264
 
265
265
  # geocoding service (see below for supported options):
266
- config.lookup = :yahoo
266
+ config.lookup = :yandex
267
267
 
268
268
  # to use an API key:
269
269
  config.api_key = "..."
@@ -304,17 +304,19 @@ The following is a comparison of the supported geocoding APIs. The "Limitations"
304
304
  * **Limitations**: "You must not use or display the Content without a corresponding Google map, unless you are explicitly permitted to do so in the Maps APIs Documentation, or through written permission from Google." "You must not pre-fetch, cache, or store any Content, except that you may store: (i) limited amounts of Content for the purpose of improving the performance of your Maps API Implementation..."
305
305
  * **Notes**: To use Google Premier set `Geocoder::Configuration.lookup = :google_premier` and `Geocoder::Configuration.api_key = [key, client, channel]`.
306
306
 
307
- #### Yahoo (`:yahoo`)
307
+ #### Yahoo BOSS (`:yahoo`)
308
308
 
309
- * **API key**: optional in development (required for production apps)
310
- * **Key signup**: https://developer.apps.yahoo.com/wsregapp
311
- * **Quota**: 50,000 requests/day, more available by special arrangement
309
+ Yahoo BOSS is **not a free service**. As of November 17, 2012 Yahoo no longer offers a free geocoding API.
310
+
311
+ * **API key**: requires OAuth consumer key and secret (set `Geocoder::Configuration.api_key = [key, secret]`)
312
+ * **Key signup**: http://developer.yahoo.com/boss/geo/
313
+ * **Quota**: unlimited, but subject to usage fees
312
314
  * **Region**: world
313
315
  * **SSL support**: no
314
- * **Languages**: ?
315
- * **Documentation**: http://developer.yahoo.com/geo/placefinder/guide/responses.html
316
- * **Terms of Service**: http://info.yahoo.com/legal/us/yahoo/maps/mapsapi/mapsapi-2141.html
317
- * **Limitations**: "YOU SHALL NOT... (viii) store or allow end users to store map imagery, map data or geocoded location information from the Yahoo! Maps APIs for any future use; (ix) use the stand-alone geocoder for any use other than displaying Yahoo! Maps or displaying points on Yahoo! Maps;"
316
+ * **Languages**: en, fr, de, it, es, pt, nl, zh, ja, ko
317
+ * **Documentation**: http://developer.yahoo.com/boss/geo/docs/index.html
318
+ * **Terms of Service**: http://info.yahoo.com/legal/us/yahoo/boss/tou/?pir=ucJPcJ1ibUn.h.d.lVmlcbcEkoHjwJ_PvxG9SLK9VIbIQAw1XFrnDqY-
319
+ * **Limitations**: No mass downloads, no commercial map production based on the data, no storage of data except for caching.
318
320
 
319
321
  #### Bing (`:bing`)
320
322
 
@@ -363,8 +365,11 @@ The following is a comparison of the supported geocoding APIs. The "Limitations"
363
365
 
364
366
  #### Mapquest (`:mapquest`)
365
367
 
366
- * **API key**: none
368
+ * **API key**: required for the licensed API, do not use for open tier
367
369
  * **Quota**: ?
370
+ * **HTTP Headers**: in order to use the licensed API you can configure the http_headers to include a referer as so:
371
+ `Geocoder::Configuration.http_headers = { "Referer" => "http://foo.com" }`
372
+ You can also allow a blank referer from the API management console via mapquest but it is potentially a security risk that someone else could use your API key from another domain.
368
373
  * **Region**: world
369
374
  * **SSL support**: no
370
375
  * **Languages**: English
@@ -524,6 +529,15 @@ Calling `obj.coordinates` directly returns the internal representation of the co
524
529
 
525
530
  For consistency with the rest of Geocoder, always use the `to_coordinates` method instead.
526
531
 
532
+ Optimisation of Distance Queries
533
+ --------------------------------
534
+
535
+ In MySQL and Postgres the finding of objects near a given point is speeded up by using a bounding box to limit the number of points over which a full distance calculation needs to be done.
536
+
537
+ To take advantage of this optimisation you need to add a composite index on latitude and longitude. In your Rails migration:
538
+
539
+ add_index :table, [:latitude, :longitude]
540
+
527
541
 
528
542
  Distance Queries in SQLite
529
543
  --------------------------
@@ -23,7 +23,7 @@ module Geocoder
23
23
  #
24
24
  # Geocoder.configure do |config|
25
25
  # config.timeout = 5
26
- # config.lookup = :yahoo
26
+ # config.lookup = :yandex
27
27
  # config.api_key = "2a9fsa983jaslfj982fjasd"
28
28
  # config.units = :km
29
29
  # end
@@ -90,6 +90,16 @@ module Geocoder
90
90
  fail
91
91
  end
92
92
 
93
+ ##
94
+ # Key to use for caching a geocoding result. Usually this will be the
95
+ # request URL, but in cases where OAuth is used and the nonce,
96
+ # timestamp, etc varies from one request to another, we need to use
97
+ # something else (like the URL before OAuth encoding).
98
+ #
99
+ def cache_key(query)
100
+ query_url(query)
101
+ end
102
+
93
103
  ##
94
104
  # Class of the result objects
95
105
  #
@@ -144,25 +154,34 @@ module Geocoder
144
154
  end
145
155
 
146
156
  ##
147
- # Fetches a raw search result (JSON string).
157
+ # Fetch a raw geocoding result (JSON string).
158
+ # The result might or might not be cached.
148
159
  #
149
160
  def fetch_raw_data(query)
150
- timeout(Geocoder::Configuration.timeout) do
151
- url = query_url(query)
152
- uri = URI.parse(url)
153
- if cache and body = cache[url]
154
- @cache_hit = true
155
- else
156
- client = http_client.new(uri.host, uri.port)
157
- client.use_ssl = true if Geocoder::Configuration.use_https
158
- response = client.get(uri.request_uri, Geocoder::Configuration.http_headers)
159
- body = response.body
160
- if cache and (200..399).include?(response.code.to_i)
161
- cache[url] = body
162
- end
163
- @cache_hit = false
161
+ key = cache_key(query)
162
+ if cache and body = cache[key]
163
+ @cache_hit = true
164
+ else
165
+ response = make_api_request(query)
166
+ body = response.body
167
+ if cache and (200..399).include?(response.code.to_i)
168
+ cache[key] = body
164
169
  end
165
- body
170
+ @cache_hit = false
171
+ end
172
+ body
173
+ end
174
+
175
+ ##
176
+ # Make an HTTP(S) request to a geocoding API and
177
+ # return the response object.
178
+ #
179
+ def make_api_request(query)
180
+ timeout(Geocoder::Configuration.timeout) do
181
+ uri = URI.parse(query_url(query))
182
+ client = http_client.new(uri.host, uri.port)
183
+ client.use_ssl = true if Geocoder::Configuration.use_https
184
+ client.get(uri.request_uri, Geocoder::Configuration.http_headers)
166
185
  end
167
186
  end
168
187
 
@@ -29,7 +29,7 @@ module Geocoder::Lookup
29
29
  end
30
30
 
31
31
  def query_url(query)
32
- "http://dev.virtualearth.net/REST/v1/Locations" +
32
+ "#{protocol}://dev.virtualearth.net/REST/v1/Locations" +
33
33
  (query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
34
34
  url_query_string(query)
35
35
  end
@@ -37,7 +37,7 @@ module Geocoder::Lookup
37
37
  end
38
38
 
39
39
  def query_url(query)
40
- "http://freegeoip.net/json/#{query.sanitized_text}"
40
+ "#{protocol}://freegeoip.net/json/#{query.sanitized_text}"
41
41
  end
42
42
  end
43
43
  end
@@ -39,7 +39,7 @@ module Geocoder::Lookup
39
39
  end
40
40
 
41
41
  def query_url(query)
42
- "http://geocoder.ca/?" + url_query_string(query)
42
+ "#{protocol}://geocoder.ca/?" + url_query_string(query)
43
43
  end
44
44
 
45
45
  def parse_raw_data(raw_data)
@@ -1,15 +1,36 @@
1
+ require 'cgi'
1
2
  require 'geocoder/lookups/base'
2
- require "geocoder/lookups/nominatim"
3
3
  require "geocoder/results/mapquest"
4
4
 
5
5
  module Geocoder::Lookup
6
- class Mapquest < Nominatim
6
+ class Mapquest < Base
7
7
 
8
8
  private # ---------------------------------------------------------------
9
9
 
10
10
  def query_url(query)
11
- method = query.reverse_geocode? ? "reverse" : "search"
12
- "http://open.mapquestapi.com/#{method}?" + url_query_string(query)
11
+ key = Geocoder::Configuration.api_key
12
+ domain = key ? "www" : "open"
13
+ url = "#{protocol}://#{domain}.mapquestapi.com/geocoding/v1/#{search_type(query)}?"
14
+ url + url_query_string(query)
13
15
  end
16
+
17
+ def search_type(query)
18
+ query.reverse_geocode? ? "reverse" : "address"
19
+ end
20
+
21
+ def query_url_params(query)
22
+ key = Geocoder::Configuration.api_key
23
+ params = { :location => query.sanitized_text }
24
+ if key
25
+ params[:key] = CGI.unescape(key)
26
+ end
27
+ super.merge(params)
28
+ end
29
+
30
+ def results(query)
31
+ return [] unless doc = fetch_data(query)
32
+ doc["results"][0]['locations']
33
+ end
34
+
14
35
  end
15
36
  end
@@ -34,7 +34,7 @@ module Geocoder::Lookup
34
34
 
35
35
  def query_url(query)
36
36
  method = query.reverse_geocode? ? "reverse" : "search"
37
- "http://nominatim.openstreetmap.org/#{method}?" + url_query_string(query)
37
+ "#{protocol}://nominatim.openstreetmap.org/#{method}?" + url_query_string(query)
38
38
  end
39
39
  end
40
40
  end
@@ -1,5 +1,6 @@
1
1
  require 'geocoder/lookups/base'
2
2
  require "geocoder/results/yahoo"
3
+ require 'oauth_util'
3
4
 
4
5
  module Geocoder::Lookup
5
6
  class Yahoo < Base
@@ -12,47 +13,16 @@ module Geocoder::Lookup
12
13
 
13
14
  def results(query)
14
15
  return [] unless doc = fetch_data(query)
15
- doc = doc['ResultSet']
16
- if api_version(doc).to_i == 1
17
- return version_1_results(doc)
18
- elsif api_version(doc).to_i == 2
19
- return version_2_results(doc)
20
- else
21
- warn "Yahoo Geocoding API error: #{doc['Error']} (#{doc['ErrorMessage']})."
22
- return []
23
- end
24
- end
25
-
26
- def api_version(doc)
27
- if doc.include?('version')
28
- return doc['version'].to_f
29
- elsif doc.include?('@version')
30
- return doc['@version'].to_f
31
- end
32
- end
33
-
34
- def version_1_results(doc)
35
- if doc['Error'] == 0
36
- if doc['Found'] > 0
37
- return doc['Results']
38
- else
39
- return []
40
- end
41
- end
42
- end
43
-
44
- ##
45
- # Return array of results, or nil if an error.
46
- #
47
- def version_2_results(doc)
48
- # seems to have Error == 7 when no results, though this is not documented
49
- if [0, 7].include?(doc['Error'].to_i)
50
- if doc['Found'].to_i > 0
51
- r = doc['Result']
52
- return r.is_a?(Array) ? r : [r]
16
+ doc = doc['bossresponse']
17
+ if doc['responsecode'].to_i == 200
18
+ if doc['placefinder']['count'].to_i > 0
19
+ return doc['placefinder']['results']
53
20
  else
54
21
  return []
55
22
  end
23
+ else
24
+ warn "Yahoo Geocoding API error: #{doc['responsecode']} (#{doc['reason']})."
25
+ return []
56
26
  end
57
27
  end
58
28
 
@@ -60,14 +30,28 @@ module Geocoder::Lookup
60
30
  super.merge(
61
31
  :location => query.sanitized_text,
62
32
  :flags => "JXTSR",
63
- :gflags => "AC#{'R' if query.reverse_geocode?}",
64
- :locale => "#{Geocoder::Configuration.language}_US",
65
- :appid => Geocoder::Configuration.api_key
33
+ :gflags => "AC#{'R' if query.reverse_geocode?}"
66
34
  )
67
35
  end
68
36
 
37
+ def cache_key(query)
38
+ raw_url(query)
39
+ end
40
+
41
+ def base_url
42
+ "#{protocol}://yboss.yahooapis.com/geo/placefinder?"
43
+ end
44
+
45
+ def raw_url(query)
46
+ base_url + url_query_string(query)
47
+ end
48
+
69
49
  def query_url(query)
70
- "http://where.yahooapis.com/geocode?" + url_query_string(query)
50
+ parsed_url = URI.parse(raw_url(query))
51
+ o = OauthUtil.new
52
+ o.consumer_key = Geocoder::Configuration.api_key[0]
53
+ o.consumer_secret = Geocoder::Configuration.api_key[1]
54
+ base_url + o.sign(parsed_url).query_string
71
55
  end
72
56
  end
73
57
  end
@@ -40,7 +40,7 @@ module Geocoder::Lookup
40
40
  end
41
41
 
42
42
  def query_url(query)
43
- "http://geocode-maps.yandex.ru/1.x/?" + url_query_string(query)
43
+ "#{protocol}://geocode-maps.yandex.ru/1.x/?" + url_query_string(query)
44
44
  end
45
45
  end
46
46
  end
@@ -18,7 +18,7 @@ module Geocoder
18
18
  super(options)
19
19
  if options[:skip_index] == false
20
20
  # create 2d index
21
- if (::Mongoid::VERSION >= "3")
21
+ if defined?(::Mongoid::VERSION) && ::Mongoid::VERSION >= "3"
22
22
  index({ geocoder_options[:coordinates].to_sym => '2d' },
23
23
  {:min => -180, :max => 180})
24
24
  else
@@ -40,7 +40,13 @@ module Geocoder
40
40
  # Is the Query text blank? (ie, should we not bother searching?)
41
41
  #
42
42
  def blank?
43
- !!text.to_s.match(/^\s*$/)
43
+ # check whether both coordinates given
44
+ if text.is_a?(Array)
45
+ text.compact.size < 2
46
+ # else assume a string
47
+ else
48
+ !!text.to_s.match(/^\s*$/)
49
+ end
44
50
  end
45
51
 
46
52
  ##
@@ -1,10 +1,16 @@
1
1
  module Geocoder
2
2
  module Result
3
3
  class Base
4
- attr_accessor :data, :cache_hit
4
+
5
+ # data (hash) fetched from geocoding service
6
+ attr_accessor :data
7
+
8
+ # true if result came from cache, false if from request to geocoding
9
+ # service; nil if cache is not configured
10
+ attr_accessor :cache_hit
5
11
 
6
12
  ##
7
- # Takes a hash of result data from a parsed Google result document.
13
+ # Takes a hash of data from a parsed geocoding service response.
8
14
  #
9
15
  def initialize(data)
10
16
  @data = data
@@ -1,7 +1,51 @@
1
1
  require 'geocoder/results/base'
2
- require 'geocoder/results/nominatim'
3
2
 
4
3
  module Geocoder::Result
5
- class Mapquest < Nominatim
4
+ class Mapquest < Base
5
+ def latitude
6
+ @data["latLng"]["lat"]
7
+ end
8
+
9
+ def longitude
10
+ @data["latLng"]["lng"]
11
+ end
12
+
13
+ def coordinates
14
+ [latitude, longitude]
15
+ end
16
+
17
+ def city
18
+ @data['adminArea5']
19
+ end
20
+
21
+ def street
22
+ @data['street']
23
+ end
24
+
25
+ def state
26
+ @data['adminArea3']
27
+ end
28
+
29
+ alias_method :state_code, :state
30
+
31
+ #FIXME: these might not be right, unclear with MQ documentation
32
+ alias_method :provinice, :state
33
+ alias_method :province_code, :state
34
+
35
+ def postal_code
36
+ @data['postalCode'].to_s
37
+ end
38
+
39
+ def country
40
+ @data['adminArea1']
41
+ end
42
+
43
+ def country_code
44
+ country
45
+ end
46
+
47
+ def address
48
+ [street, city, state, postal_code, country].reject{|s| s.length == 0 }.join(", ")
49
+ end
6
50
  end
7
51
  end