diff --git a/lib/geocoder.rb b/lib/geocoder.rb index 3b28027fb8e1ea7924f0f9e2c265e5a31b4e9a28..7a2303a2623a0e2e10b721e215c2a8acfe4e9099 100644 --- a/lib/geocoder.rb +++ b/lib/geocoder.rb @@ -1,6 +1,5 @@ require "geocoder/configuration" require "geocoder/calculations" -require "geocoder/lookup" require "geocoder/result" require "geocoder/active_record" require "geocoder/railtie" @@ -9,10 +8,30 @@ module Geocoder extend self ## - # Alias for Geocoder::Lookup.search. + # Alias for Geocoder.lookup.search. # def search(*args) - Lookup.search(*args) + lookup.search(*args) + end + + ## + # Get the lookup object (which communicates with the remote geocoding API). + # + def lookup + unless defined?(@lookup) + set_lookup Geocoder::Configuration.lookup + end + @lookup + end + + def set_lookup(value) + if value == :yahoo + require "geocoder/lookups/yahoo" + @lookup = Geocoder::Lookup::Yahoo.new + else + require "geocoder/lookups/google" + @lookup = Geocoder::Lookup::Google.new + end end # exception classes diff --git a/lib/geocoder/active_record.rb b/lib/geocoder/active_record.rb index 597998ca5b94f11962fc357ae7cff619d8999d3d..fd52d470e33a6af1e2bf908aff2527a067560b53 100644 --- a/lib/geocoder/active_record.rb +++ b/lib/geocoder/active_record.rb @@ -28,7 +28,7 @@ module Geocoder # scope :near, lambda{ |location, *args| latitude, longitude = location.is_a?(Array) ? - location : Geocoder::Lookup.coordinates(location) + location : Geocoder.lookup.coordinates(location) if latitude and longitude near_scope_options(latitude, longitude, *args) else @@ -189,7 +189,7 @@ module Geocoder "You are attempting to fetch coordinates but have not specified " + "a method which provides an address for the object." end - coords = Geocoder::Lookup.coordinates(send(address_method)) + coords = Geocoder.lookup.coordinates(send(address_method)) unless coords.blank? method = (save ? "update" : "write") + "_attribute" send method, self.class.geocoder_options[:latitude], coords[0] @@ -217,7 +217,7 @@ module Geocoder "You are attempting to fetch an address but have not specified " + "attributes which provide coordinates for the object." end - address = Geocoder::Lookup.address(send(lat_attr), send(lon_attr)) + address = Geocoder.lookup.address(send(lat_attr), send(lon_attr)) unless address.blank? method = (save ? "update" : "write") + "_attribute" send method, self.class.geocoder_options[:fetched_address], address diff --git a/lib/geocoder/configuration.rb b/lib/geocoder/configuration.rb index d10e1be85c60e6ff229dd3d4f3b7adc46160db1c..ba1211cc1de09382525dda819a4e443fb2119b3b 100644 --- a/lib/geocoder/configuration.rb +++ b/lib/geocoder/configuration.rb @@ -1,8 +1,9 @@ module Geocoder class Configuration - cattr_accessor :timeout + cattr_accessor :timeout, :lookup, :yahoo_appid end end -Geocoder::Configuration.timeout = 3 - +Geocoder::Configuration.timeout = 3 +Geocoder::Configuration.lookup = :google +Geocoder::Configuration.yahoo_appid = "" diff --git a/lib/geocoder/lookups/base.rb b/lib/geocoder/lookups/base.rb new file mode 100644 index 0000000000000000000000000000000000000000..65d0f4dbc0332fdb20c435a7339c8f90f9cbeb3a --- /dev/null +++ b/lib/geocoder/lookups/base.rb @@ -0,0 +1,75 @@ +require 'net/http' +require 'active_support/json' + +module Geocoder + module Lookup + class Base + + ## + # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS") + # for geocoding, or coordinates (latitude, longitude) for reverse + # geocoding. Returns an array of Geocoder::Result objects, + # or nil if not found or if network error. + # + def search(*args) + return [] if args[0].blank? + if res = results(args.join(","), args.size == 2) + res.map{ |r| result_class.new(r) } + end + end + + ## + # Look up the coordinates of the given address. + # + def coordinates(address) + if (results = search(address)).size > 0 + results.first.coordinates + end + end + + ## + # Look up the address of the given coordinates. + # + def address(latitude, longitude) + if (results = search(latitude, longitude)).size > 0 + results.first.address + end + end + + + private # ------------------------------------------------------------- + + ## + # Class of the result objects + # + def result_class + fail + end + + ## + # Returns a parsed search result (Ruby hash). + # + def fetch_data(query, reverse = false) + begin + ActiveSupport::JSON.decode(fetch_raw_data(query, reverse)) + rescue SocketError + warn "Geocoding API connection cannot be established." + rescue TimeoutError + warn "Geocoding API not responding fast enough " + + "(see Geocoder::Configuration.timeout to set limit)." + end + end + + ## + # Fetches a raw search result (JSON string). + # + def fetch_raw_data(query, reverse = false) + return nil if query.blank? + url = query_url(query, reverse) + timeout(Geocoder::Configuration.timeout) do + Net::HTTP.get_response(URI.parse(url)).body + end + end + end + end +end diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookups/google.rb similarity index 53% rename from lib/geocoder/lookup.rb rename to lib/geocoder/lookups/google.rb index 21580e7c7f2368276b15396584d4c56fb760a999..3889bf1e503524db7784dc13c74a0bd2a1485c66 100644 --- a/lib/geocoder/lookup.rb +++ b/lib/geocoder/lookups/google.rb @@ -1,12 +1,8 @@ -require 'net/http' +require 'geocoder/lookups/base' -module Geocoder - module Lookup - extend self +module Geocoder::Lookup + class Google < Base - ## - # Query Google for the coordinates of the given address. - # def coordinates(address) if (results = search(address)).size > 0 place = results.first.geometry['location'] @@ -14,27 +10,18 @@ module Geocoder end end - ## - # Query Google for the address of the given coordinates. - # def address(latitude, longitude) if (results = search(latitude, longitude)).size > 0 results.first.formatted_address end end - ## - # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS") for - # geocoding, or coordinates (latitude, longitude) for reverse geocoding. - # Returns an array of Geocoder::Result objects, - # or nil if not found or if network error. - # def search(*args) return [] if args[0].blank? doc = parsed_response(args.join(","), args.size == 2) [].tap do |results| if doc - doc['results'].each{ |r| results << Result.new(r) } + doc['results'].each{ |r| results << Geocoder::Result.new(r) } end end end @@ -47,15 +34,7 @@ module Geocoder # Returns nil if non-200 HTTP response, timeout, or other error. # def parsed_response(query, reverse = false) - begin - doc = ActiveSupport::JSON.decode(fetch_data(query, reverse)) - rescue SocketError - warn "Google Geocoding API connection cannot be established." - rescue TimeoutError - warn "Google Geocoding API not responding fast enough " + - "(see Geocoder::Configuration.timeout to set limit)." - end - + doc = fetch_data(query, reverse) case doc['status']; when "OK" doc when "OVER_QUERY_LIMIT" @@ -67,17 +46,6 @@ module Geocoder end end - ## - # Fetches a raw Google geocoder search result (JSON string). - # - def fetch_data(query, reverse = false) - return nil if query.blank? - url = query_url(query, reverse) - timeout(Geocoder::Configuration.timeout) do - Net::HTTP.get_response(URI.parse(url)).body - end - end - def query_url(query, reverse = false) params = { (reverse ? :latlng : :address) => query, diff --git a/test/geocoder_test.rb b/test/geocoder_test.rb index f6a07392fef12e330334103ad0a7ad96f935a0b3..27a43afa330f1c8eb081d2097b8103357b7d5270 100644 --- a/test/geocoder_test.rb +++ b/test/geocoder_test.rb @@ -29,7 +29,7 @@ class GeocoderTest < Test::Unit::TestCase end def test_result_address_components_of_type - results = Geocoder::Lookup.search("Madison Square Garden, New York, NY") + results = Geocoder.search("Madison Square Garden, New York, NY") assert_equal "Manhattan", results.first.address_components_of_type(:sublocality).first['long_name'] end diff --git a/test/test_helper.rb b/test/test_helper.rb index 7d2813614a052a85224e9a924d84713ec0e0552b..dfc82fa9dd3db11ee8510988bff072546244d7c0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,18 +33,20 @@ end # Require Geocoder after ActiveRecord simulator. require 'geocoder' +require "geocoder/lookups/base" ## # Mock HTTP request to geocoding service. # module Geocoder module Lookup - extend self + class Base private #----------------------------------------------------------------- - def fetch_data(query, reverse = false) + def fetch_raw_data(query, reverse = false) File.read(File.join("test", "fixtures", "madison_square_garden.json")) end end + end end ##