diff --git a/lib/geocoder.rb b/lib/geocoder.rb
index 196cf75dede7e6339705c5d017ca84e1c8b282db..4019e84523f5f00a5ab6e31aebd233804ba5743d 100644
--- a/lib/geocoder.rb
+++ b/lib/geocoder.rb
@@ -1,8 +1,10 @@
 require "geocoder/configuration"
+require "geocoder/query"
 require "geocoder/calculations"
 require "geocoder/exceptions"
 require "geocoder/cache"
 require "geocoder/request"
+require "geocoder/lookup"
 require "geocoder/models/active_record" if defined?(::ActiveRecord)
 require "geocoder/models/mongoid" if defined?(::Mongoid)
 require "geocoder/models/mongo_mapper" if defined?(::MongoMapper)
@@ -13,15 +15,16 @@ module Geocoder
   ##
   # Search for information about an address or a set of coordinates.
   #
-  def search(query)
-    blank_query?(query) ? [] : lookup(query).search(query)
+  def search(query, options = {})
+    query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
+    query.blank? ? [] : query.execute
   end
 
   ##
   # Look up the coordinates of the given street or IP address.
   #
-  def coordinates(address)
-    if (results = search(address)).size > 0
+  def coordinates(address, options = {})
+    if (results = search(address, options)).size > 0
       results.first.coordinates
     end
   end
@@ -30,8 +33,8 @@ module Geocoder
   # Look up the address of the given coordinates ([lat,lon])
   # or IP address (string).
   #
-  def address(query)
-    if (results = search(query)).size > 0
+  def address(query, options = {})
+    if (results = search(query, options)).size > 0
       results.first.address
     end
   end
@@ -45,92 +48,6 @@ module Geocoder
     end
     @cache
   end
-
-  ##
-  # Array of valid Lookup names.
-  #
-  def valid_lookups
-    street_lookups + ip_lookups
-  end
-
-  ##
-  # Array of valid Lookup names, excluding :test.
-  #
-  def valid_lookups_except_test
-    valid_lookups - [:test]
-  end
-
-  ##
-  # All street address lookups, default first.
-  #
-  def street_lookups
-    [:google, :google_premier, :yahoo, :bing, :geocoder_ca, :yandex, :nominatim, :mapquest, :test]
-  end
-
-  ##
-  # All IP address lookups, default first.
-  #
-  def ip_lookups
-    [:freegeoip]
-  end
-
-
-  private # -----------------------------------------------------------------
-
-  ##
-  # Get a Lookup object (which communicates with the remote geocoding API).
-  # Takes a search query and returns an IP or street address Lookup
-  # depending on the query contents.
-  #
-  def lookup(query)
-    if ip_address?(query)
-      get_lookup(Configuration.ip_lookup || ip_lookups.first)
-    else
-      get_lookup(Configuration.lookup || street_lookups.first)
-    end
-  end
-
-  ##
-  # Retrieve a Lookup object from the store.
-  #
-  def get_lookup(name)
-    @lookups = {} unless defined?(@lookups)
-    @lookups[name] = spawn_lookup(name) unless @lookups.include?(name)
-    @lookups[name]
-  end
-
-  ##
-  # Spawn a Lookup of the given name.
-  #
-  def spawn_lookup(name)
-    if valid_lookups.include?(name)
-      name = name.to_s
-      require "geocoder/lookups/#{name}"
-      klass = name.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
-      Geocoder::Lookup.const_get(klass).new
-    else
-      valids = valid_lookups.map(&:inspect).join(", ")
-      raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
-        "(#{name.inspect} is not one of: #{valids})."
-    end
-  end
-
-  ##
-  # Does the given value look like an IP address?
-  #
-  # Does not check for actual validity, just the appearance of four
-  # dot-delimited numbers.
-  #
-  def ip_address?(value)
-    !!value.to_s.match(/^(::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
-  end
-
-  ##
-  # Is the given search query blank? (ie, should we not bother searching?)
-  #
-  def blank_query?(value)
-    !!value.to_s.match(/^\s*$/)
-  end
 end
 
 # load Railtie if Rails exists
diff --git a/lib/geocoder/cli.rb b/lib/geocoder/cli.rb
index 9a7fa13b0d6aad7fb546f09782df6da38f2f81e3..a5648631b10babc22c8583a8f52257cbcc9c8e0e 100644
--- a/lib/geocoder/cli.rb
+++ b/lib/geocoder/cli.rb
@@ -32,8 +32,8 @@ module Geocoder
           Geocoder::Configuration.http_proxy = proxy
         end
 
-        opts.on("-s <service>", Geocoder.valid_lookups_except_test, "--service <service>",
-          "Geocoding service: #{Geocoder.valid_lookups_except_test * ', '}") do |service|
+        opts.on("-s <service>", Geocoder::Lookup.all_services_except_test, "--service <service>",
+          "Geocoding service: #{Geocoder::Lookup.all_services_except_test * ', '}") do |service|
           Geocoder::Configuration.lookup = service.to_sym
           Geocoder::Configuration.ip_lookup = service.to_sym
         end
@@ -93,7 +93,7 @@ module Geocoder
       end
 
       if (result = Geocoder.search(query).first)
-        lookup = Geocoder.send(:get_lookup, :google)
+        lookup = Geocoder::Lookup.get(:google)
         lines = [
           ["Latitude",       result.latitude],
           ["Longitude",      result.longitude],
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
new file mode 100644
index 0000000000000000000000000000000000000000..11d0a8a20d5fac110f3516c2791479aced6b176b
--- /dev/null
+++ b/lib/geocoder/lookup.rb
@@ -0,0 +1,62 @@
+module Geocoder
+  module Lookup
+
+    ##
+    # Array of valid Lookup service names.
+    #
+    def self.all_services
+      street_services + ip_services
+    end
+
+    ##
+    # Array of valid Lookup service names, excluding :test.
+    #
+    def self.all_services_except_test
+      all_services - [:test]
+    end
+
+    ##
+    # All street address lookup services, default first.
+    #
+    def self.street_services
+      [:google, :google_premier, :yahoo, :bing, :geocoder_ca, :yandex, :nominatim, :mapquest, :test]
+    end
+
+    ##
+    # All IP address lookup services, default first.
+    #
+    def self.ip_services
+      [:freegeoip]
+    end
+
+    ##
+    # Retrieve a Lookup object from the store.
+    # Use this instead of Geocoder::Lookup::X.new to get an
+    # already-configured Lookup object.
+    #
+    def self.get(name)
+      @services = {} unless defined?(@services)
+      @services[name] = spawn(name) unless @services.include?(name)
+      @services[name]
+    end
+
+
+    private # -----------------------------------------------------------------
+
+    ##
+    # Spawn a Lookup of the given name.
+    #
+    def self.spawn(name)
+      if all_services.include?(name)
+        name = name.to_s
+        require "geocoder/lookups/#{name}"
+        klass = name.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
+        Geocoder::Lookup.const_get(klass).new
+      else
+        valids = all_services.map(&:inspect).join(", ")
+        raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
+          "(#{name.inspect} is not one of: #{valids})."
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/base.rb b/lib/geocoder/lookups/base.rb
index 3665e954b51dfded0e8576335bf0f4c851a10645..a770f97f11546f5519bab2ca1dc8cc8da97e8e33 100644
--- a/lib/geocoder/lookups/base.rb
+++ b/lib/geocoder/lookups/base.rb
@@ -13,6 +13,7 @@ end
 
 module Geocoder
   module Lookup
+
     class Base
 
       ##
@@ -23,18 +24,9 @@ module Geocoder
       # "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
       # for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
       #
-      def search(query)
-
-        # if coordinates given as string, turn into array
-        query = query.split(/\s*,\s*/) if coordinates?(query)
-
-        if query.is_a?(Array)
-          reverse = true
-          query = query.join(',')
-        else
-          reverse = false
-        end
-        results(query, reverse).map{ |r| 
+      def search(query, options = {})
+        query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
+        results(query).map{ |r|
           result = result_class.new(r)
           result.cache_hit = @cache_hit if cache
           result
@@ -77,14 +69,14 @@ module Geocoder
       ##
       # Geocoder::Result object or nil on timeout or other error.
       #
-      def results(query, reverse = false)
+      def results(query)
         fail
       end
 
       ##
       # URL to use for querying the geocoding engine.
       #
-      def query_url(query, reverse = false)
+      def query_url(query)
         fail
       end
 
@@ -111,8 +103,8 @@ module Geocoder
       ##
       # Returns a parsed search result (Ruby hash).
       #
-      def fetch_data(query, reverse = false)
-        parse_raw_data fetch_raw_data(query, reverse)
+      def fetch_data(query)
+        parse_raw_data fetch_raw_data(query)
       rescue SocketError => err
         raise_error(err) or warn "Geocoding API connection cannot be established."
       rescue TimeoutError => err
@@ -144,9 +136,9 @@ module Geocoder
       ##
       # Fetches a raw search result (JSON string).
       #
-      def fetch_raw_data(query, reverse = false)
+      def fetch_raw_data(query)
         timeout(Geocoder::Configuration.timeout) do
-          url = query_url(query, reverse)
+          url = query_url(query)
           uri = URI.parse(url)
           if cache and body = cache[url]
             @cache_hit = true
@@ -171,20 +163,6 @@ module Geocoder
         Geocoder.cache
       end
 
-      ##
-      # Is the given string a loopback IP address?
-      #
-      def loopback_address?(ip)
-        !!(ip == "0.0.0.0" or ip.to_s.match(/^127/))
-      end
-
-      ##
-      # Does the given string look like latitude/longitude coordinates?
-      #
-      def coordinates?(value)
-        value.is_a?(String) and !!value.to_s.match(/^-?[0-9\.]+, *-?[0-9\.]+$/)
-      end
-
       ##
       # Simulate ActiveSupport's Object#to_query.
       # Removes any keys with nil value.
diff --git a/lib/geocoder/lookups/bing.rb b/lib/geocoder/lookups/bing.rb
index 2bec5a4f1ec09a6443692b8a1fc356fe42dbbdfe..a5d254c36fd52c85024656aacb300b0aedc2a038 100644
--- a/lib/geocoder/lookups/bing.rb
+++ b/lib/geocoder/lookups/bing.rb
@@ -10,8 +10,8 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
+    def results(query)
+      return [] unless doc = fetch_data(query)
 
       if doc['statusDescription'] == "OK"
         return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
@@ -21,12 +21,12 @@ module Geocoder::Lookup
       end
     end
 
-    def query_url(query, reverse = false)
+    def query_url(query)
       params = {:key => Geocoder::Configuration.api_key}
-      params[:query] = query unless reverse
+      params[:query] = query.sanitized_text unless query.reverse_geocode?
 
       base_url = "http://dev.virtualearth.net/REST/v1/Locations"
-      url_tail = reverse ? "/#{query}?" : "?"
+      url_tail = query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?"
       base_url + url_tail + hash_to_query(params)
     end
   end
diff --git a/lib/geocoder/lookups/freegeoip.rb b/lib/geocoder/lookups/freegeoip.rb
index a5526667cd343808446934a627d3d8f6c0dee102..a3f6dc91a6662cf40ccc0a37a2461f822975ab0c 100644
--- a/lib/geocoder/lookups/freegeoip.rb
+++ b/lib/geocoder/lookups/freegeoip.rb
@@ -10,11 +10,11 @@ module Geocoder::Lookup
       raw_data.match(/^<html><title>404/) ? nil : super(raw_data)
     end
 
-    def results(query, reverse = false)
+    def results(query)
       # don't look up a loopback address, just return the stored result
-      return [reserved_result(query)] if loopback_address?(query)
+      return [reserved_result(query)] if query.loopback_ip_address?
       begin
-        return (doc = fetch_data(query, reverse)) ? [doc] : []
+        return (doc = fetch_data(query)) ? [doc] : []
       rescue StandardError => err # Freegeoip.net returns HTML on bad request
         raise_error(err)
         return []
@@ -36,8 +36,8 @@ module Geocoder::Lookup
       }
     end
 
-    def query_url(query, reverse = false)
-      "http://freegeoip.net/json/#{query}"
+    def query_url(query)
+      "http://freegeoip.net/json/#{query.sanitized_text}"
     end
   end
 end
diff --git a/lib/geocoder/lookups/geocoder_ca.rb b/lib/geocoder/lookups/geocoder_ca.rb
index 32ea293ab6af7388b0484fd1676ee3af9f57f6ab..379d0fd40c53a123d74a3b6d08d5f4d6c0a9079a 100644
--- a/lib/geocoder/lookups/geocoder_ca.rb
+++ b/lib/geocoder/lookups/geocoder_ca.rb
@@ -6,8 +6,8 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
+    def results(query)
+      return [] unless doc = fetch_data(query)
       if doc['error'].nil?
         return [doc]
       elsif doc['error']['code'] == "005"
@@ -18,21 +18,21 @@ module Geocoder::Lookup
       return []
     end
 
-    def query_url(query, reverse = false)
+    def query_url(query)
       params = {
         :geoit    => "xml",
         :jsonp    => 1,
         :callback => "test",
         :auth     => Geocoder::Configuration.api_key
       }
-      if reverse
-        lat,lon = query.split(',')
+      if query.reverse_geocode?
+        lat,lon = query.coordinates
         params[:latt] = lat
         params[:longt] = lon
         params[:corner] = 1
         params[:reverse] = 1
       else
-        params[:locate] = query
+        params[:locate] = query.sanitized_text
         params[:showpostal] = 1
       end
       "http://geocoder.ca/?" + hash_to_query(params)
diff --git a/lib/geocoder/lookups/google.rb b/lib/geocoder/lookups/google.rb
index 6898c267551bc056834cf7969fda1bb7fa46f4a3..ed7f1cd3aadbbb161c03eca6a0d16242b5d995d4 100644
--- a/lib/geocoder/lookups/google.rb
+++ b/lib/geocoder/lookups/google.rb
@@ -10,8 +10,8 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
+    def results(query)
+      return [] unless doc = fetch_data(query)
       case doc['status']; when "OK" # OK status implies >0 results
         return doc['results']
       when "OVER_QUERY_LIMIT"
@@ -27,9 +27,9 @@ module Geocoder::Lookup
       return []
     end
 
-    def query_url(query, reverse = false)
+    def query_url(query)
       params = {
-        (reverse ? :latlng : :address) => query,
+        (query.reverse_geocode? ? :latlng : :address) => query.sanitized_text,
         :sensor => "false",
         :language => Geocoder::Configuration.language,
         :key => Geocoder::Configuration.api_key
diff --git a/lib/geocoder/lookups/google_premier.rb b/lib/geocoder/lookups/google_premier.rb
index 6befdddf585167868fcdbb58a042f0ea8175ea6d..14cbdac8ee911cf268901d6fa78457d71d85ce25 100644
--- a/lib/geocoder/lookups/google_premier.rb
+++ b/lib/geocoder/lookups/google_premier.rb
@@ -8,9 +8,9 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def query_url(query, reverse = false)
+    def query_url(query)
       params = {
-        (reverse ? :latlng : :address) => query,
+        (query.reverse_geocode? ? :latlng : :address) => query.sanitized_text,
         :sensor => 'false',
         :language => Geocoder::Configuration.language,
         :client => Geocoder::Configuration.api_key[1],
diff --git a/lib/geocoder/lookups/mapquest.rb b/lib/geocoder/lookups/mapquest.rb
index 08cd31d8de0d33d19d985188ddff854311fd17f3..08bdaaebd2e5701c0f0c8aa9da3d34704048934e 100644
--- a/lib/geocoder/lookups/mapquest.rb
+++ b/lib/geocoder/lookups/mapquest.rb
@@ -6,26 +6,26 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
+    def results(query)
+      return [] unless doc = fetch_data(query)
       doc.is_a?(Array) ? doc : [doc]
     end
 
-    def query_url(query, reverse = false)
+    def query_url(query)
       params = {
         :format => "json",
         :polygon => "1",
         :addressdetails => "1",
         :"accept-language" => Geocoder::Configuration.language
       }
-      if (reverse)
+      if (query.reverse_geocode?)
         method = 'reverse'
-        parts = query.split(/\s*,\s*/);
-        params[:lat] = parts[0]
-        params[:lon] = parts[1]
+        lat,lon = query.coordinates
+        params[:lat] = lat
+        params[:lon] = lon
       else
         method = 'search'
-        params[:q] = query
+        params[:q] = query.sanitized_text
       end
       "http://open.mapquestapi.com/#{method}?" + hash_to_query(params)
     end
diff --git a/lib/geocoder/lookups/nominatim.rb b/lib/geocoder/lookups/nominatim.rb
index 12f2c1513879cb87346341c96b2aa92f30cab9bf..4ded59b19614740db2f3507c2ab980416834cf1b 100644
--- a/lib/geocoder/lookups/nominatim.rb
+++ b/lib/geocoder/lookups/nominatim.rb
@@ -10,26 +10,26 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
+    def results(query)
+      return [] unless doc = fetch_data(query)
       doc.is_a?(Array) ? doc : [doc]
     end
 
-    def query_url(query, reverse = false)
+    def query_url(query)
       params = {
         :format => "json",
         :polygon => "1",
         :addressdetails => "1",
         :"accept-language" => Geocoder::Configuration.language
       }
-      if (reverse)
+      if (query.reverse_geocode?)
         method = 'reverse'
-        parts = query.split(/\s*,\s*/);
-        params[:lat] = parts[0]
-        params[:lon] = parts[1]
+        lat,lon = query.coordinates
+        params[:lat] = lat
+        params[:lon] = lon
       else
         method = 'search'
-        params[:q] = query
+        params[:q] = query.sanitized_text
       end
       "http://nominatim.openstreetmap.org/#{method}?" + hash_to_query(params)
     end
diff --git a/lib/geocoder/lookups/test.rb b/lib/geocoder/lookups/test.rb
index e06171c9dbc96a4ce03a54b2832c7262efa95b33..89aa97f4445d7d65c75caea6b0fe6d67c3d75dbe 100644
--- a/lib/geocoder/lookups/test.rb
+++ b/lib/geocoder/lookups/test.rb
@@ -5,12 +5,14 @@ module Geocoder
   module Lookup
     class Test < Base
 
-      def self.add_stub(query, results)
-        stubs[query] = results
+      def self.add_stub(query_text, results)
+        stubs[query_text] = results
       end
 
-      def self.read_stub(query)
-        stubs.fetch(query) { raise ArgumentError, "unknown stub request #{query}" }
+      def self.read_stub(query_text)
+        stubs.fetch(query_text) {
+          raise ArgumentError, "unknown stub request #{query_text}"
+        }
       end
 
       def self.stubs
@@ -23,8 +25,8 @@ module Geocoder
 
       private
 
-      def results(query, reverse = false)
-        Geocoder::Lookup::Test.read_stub(query)
+      def results(query)
+        Geocoder::Lookup::Test.read_stub(query.text)
       end
 
     end
diff --git a/lib/geocoder/lookups/yahoo.rb b/lib/geocoder/lookups/yahoo.rb
index 1ce523ecc304a7973f5062e275cfc78473972a43..e240fb4a9361939143c0c9351c818ef2a594bc98 100644
--- a/lib/geocoder/lookups/yahoo.rb
+++ b/lib/geocoder/lookups/yahoo.rb
@@ -10,8 +10,8 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
+    def results(query)
+      return [] unless doc = fetch_data(query)
       if doc = doc['ResultSet'] and doc['Error'] == 0
         return doc['Found'] > 0 ? doc['Results'] : []
       else
@@ -20,11 +20,11 @@ module Geocoder::Lookup
       end
     end
 
-    def query_url(query, reverse = false)
+    def query_url(query)
       params = {
-        :location => query,
+        :location => query.sanitized_text,
         :flags => "JXTSR",
-        :gflags => "AC#{'R' if reverse}",
+        :gflags => "AC#{'R' if query.reverse_geocode?}",
         :locale => "#{Geocoder::Configuration.language}_US",
         :appid => Geocoder::Configuration.api_key
       }
diff --git a/lib/geocoder/lookups/yandex.rb b/lib/geocoder/lookups/yandex.rb
index 452d27694eea2728450e6cb05877b909ee441f7f..11f092d791b39f9576c0380f86d5e492d838a100 100644
--- a/lib/geocoder/lookups/yandex.rb
+++ b/lib/geocoder/lookups/yandex.rb
@@ -10,8 +10,8 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
+    def results(query)
+      return [] unless doc = fetch_data(query)
       if err = doc['error']
         warn "Yandex Geocoding API error: #{err['status']} (#{err['message']})."
         return []
@@ -25,10 +25,14 @@ module Geocoder::Lookup
       end
     end
 
-    def query_url(query, reverse = false)
-      query = query.split(",").reverse.join(",") if reverse
+    def query_url(query)
+      if query.reverse_geocode?
+        q = query.coordinates.reverse.join(",")
+      else
+        q = query.sanitized_text
+      end
       params = {
-        :geocode => query,
+        :geocode => q,
         :format => "json",
         :plng => "#{Geocoder::Configuration.language}", # supports ru, uk, be
         :key => Geocoder::Configuration.api_key
diff --git a/lib/geocoder/query.rb b/lib/geocoder/query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3da86ef17430b0227dcc9a8cf285a97835197551
--- /dev/null
+++ b/lib/geocoder/query.rb
@@ -0,0 +1,88 @@
+module Geocoder
+  class Query
+    attr_accessor :text, :options
+
+    def initialize(text, options = {})
+      self.text = text
+      self.options = options
+    end
+
+    def execute
+      lookup.search(text, options)
+    end
+
+    def to_s
+      text
+    end
+
+    def sanitized_text
+      if coordinates?
+        text.split(/\s*,\s*/).join(',')
+      else
+        text
+      end
+    end
+
+    ##
+    # Get a Lookup object (which communicates with the remote geocoding API)
+    # appropriate to the Query text.
+    #
+    def lookup
+      if ip_address?
+        name = Configuration.ip_lookup || Geocoder::Lookup.ip_services.first
+      else
+        name = Configuration.lookup || Geocoder::Lookup.street_services.first
+      end
+      Lookup.get(name)
+    end
+
+    ##
+    # Is the Query text blank? (ie, should we not bother searching?)
+    #
+    def blank?
+      !!text.to_s.match(/^\s*$/)
+    end
+
+    ##
+    # Does the Query text look like an IP address?
+    #
+    # Does not check for actual validity, just the appearance of four
+    # dot-delimited numbers.
+    #
+    def ip_address?
+      !!text.to_s.match(/^(::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
+    end
+
+    ##
+    # Is the Query text a loopback IP address?
+    #
+    def loopback_ip_address?
+      !!(text == "0.0.0.0" or text.to_s.match(/^127/))
+    end
+
+    ##
+    # Does the given string look like latitude/longitude coordinates?
+    #
+    def coordinates?
+      text.is_a?(Array) or (
+        text.is_a?(String) and
+        !!text.to_s.match(/^-?[0-9\.]+, *-?[0-9\.]+$/)
+      )
+    end
+
+    ##
+    # Return the latitude/longitude coordinates specified in the query,
+    # or nil if none.
+    #
+    def coordinates
+      sanitized_text.split(',') if coordinates?
+    end
+
+    ##
+    # Should reverse geocoding be performed for this query?
+    #
+    def reverse_geocode?
+      coordinates?
+    end
+  end
+end
diff --git a/lib/geocoder/version.rb b/lib/geocoder/version.rb
index 0eb5a04215f806f4d4b75facf7c6e9b2335e31bb..3416c7e7efeb7a1f91029a9b3913b328fda8d0af 100644
--- a/lib/geocoder/version.rb
+++ b/lib/geocoder/version.rb
@@ -1,3 +1,3 @@
 module Geocoder
-  VERSION = "1.1.3"
+  VERSION = "1.2.0"
 end
diff --git a/test/calculations_test.rb b/test/calculations_test.rb
index 4f9fef0df46df8131fb82d67bd7f9b93e61f68d8..902d8d680f8926bb89115b383b89acf72f07bbe3 100644
--- a/test/calculations_test.rb
+++ b/test/calculations_test.rb
@@ -185,4 +185,11 @@ class CalculationsTest < Test::Unit::TestCase
     assert !Geocoder::Calculations.coordinates_present?(Geocoder::Calculations::NAN)
     assert !Geocoder::Calculations.coordinates_present?(3.23, nil)
   end
+
+  def test_extract_coordinates
+    coords = [-23,47]
+    l = Landmark.new("Madagascar", coords[0], coords[1])
+    assert_equal coords, Geocoder::Calculations.extract_coordinates(l)
+    assert_equal coords, Geocoder::Calculations.extract_coordinates(coords)
+  end
 end
diff --git a/test/error_handling_test.rb b/test/error_handling_test.rb
index 9d84bdfdc008fe2d25671d36d052e3dd8fadaa40..369613e69540d3c33a039fd8ccf35800ee2445a3 100644
--- a/test/error_handling_test.rb
+++ b/test/error_handling_test.rb
@@ -10,7 +10,7 @@ class ErrorHandlingTest < Test::Unit::TestCase
   def test_does_not_choke_on_timeout
     # keep test output clean: suppress timeout warning
     orig = $VERBOSE; $VERBOSE = nil
-    all_lookups_except_test.each do |l|
+    Geocoder::Lookup.all_services_except_test.each do |l|
       Geocoder::Configuration.lookup = l
       assert_nothing_raised { Geocoder.search("timeout") }
     end
@@ -19,20 +19,20 @@ class ErrorHandlingTest < Test::Unit::TestCase
 
   def test_always_raise_timeout_error
     Geocoder::Configuration.always_raise = [TimeoutError]
-    all_lookups_except_test.each do |l|
-      lookup = Geocoder.send(:get_lookup, l)
+    Geocoder::Lookup.all_services_except_test.each do |l|
+      lookup = Geocoder::Lookup.get(l)
       assert_raises TimeoutError do
-        lookup.send(:results, "timeout")
+        lookup.send(:results, Geocoder::Query.new("timeout"))
       end
     end
   end
 
   def test_always_raise_socket_error
     Geocoder::Configuration.always_raise = [SocketError]
-    all_lookups_except_test.each do |l|
-      lookup = Geocoder.send(:get_lookup, l)
+    Geocoder::Lookup.all_services_except_test.each do |l|
+      lookup = Geocoder::Lookup.get(l)
       assert_raises SocketError do
-        lookup.send(:results, "socket_error")
+        lookup.send(:results, Geocoder::Query.new("socket_error"))
       end
     end
   end
diff --git a/test/https_test.rb b/test/https_test.rb
index 58219d33cc8f013746583b0d20a699e93b534ad8..aba96acf7e53aea456c91ba503659a5f23160c2a 100644
--- a/test/https_test.rb
+++ b/test/https_test.rb
@@ -6,11 +6,11 @@ class HttpsTest < Test::Unit::TestCase
   def test_uses_https_for_secure_query
     Geocoder::Configuration.use_https = true
     g = Geocoder::Lookup::Google.new
-    assert_match /^https:/, g.send(:query_url, {:a => 1, :b => 2})
+    assert_match /^https:/, g.send(:query_url, Geocoder::Query.new("test"))
   end
 
   def test_uses_http_by_default
     g = Geocoder::Lookup::Google.new
-    assert_match /^http:/, g.send(:query_url, {:a => 1, :b => 2})
+    assert_match /^http:/, g.send(:query_url, Geocoder::Query.new("test"))
   end
 end
diff --git a/test/input_handling_test.rb b/test/input_handling_test.rb
deleted file mode 100644
index f5da1f13c9df941fdbf08a157464a0dbb0738d7c..0000000000000000000000000000000000000000
--- a/test/input_handling_test.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# encoding: utf-8
-require 'test_helper'
-
-class InputHandlingTest < Test::Unit::TestCase
-
-  def test_ip_address_detection
-    assert Geocoder.send(:ip_address?, "232.65.123.94")
-    assert Geocoder.send(:ip_address?, "666.65.123.94") # technically invalid
-    assert Geocoder.send(:ip_address?, "::ffff:12.34.56.78")
-    assert !Geocoder.send(:ip_address?, "232.65.123.94.43")
-    assert !Geocoder.send(:ip_address?, "232.65.123")
-    assert !Geocoder.send(:ip_address?, "::ffff:123.456.789")
-  end
-
-  def test_blank_query_detection
-    assert Geocoder.send(:blank_query?, nil)
-    assert Geocoder.send(:blank_query?, "")
-    assert Geocoder.send(:blank_query?, "\t  ")
-    assert !Geocoder.send(:blank_query?, "a")
-    assert !Geocoder.send(:blank_query?, "Москва") # no ASCII characters
-  end
-
-  def test_coordinates_detection
-    lookup = Geocoder::Lookup::Google.new
-    assert lookup.send(:coordinates?, "51.178844,5")
-    assert lookup.send(:coordinates?, "51.178844, -1.826189")
-    assert !lookup.send(:coordinates?, "232.65.123")
-  end
-
-  def test_does_not_choke_on_nil_address
-    all_lookups.each do |l|
-      Geocoder::Configuration.lookup = l
-      assert_nothing_raised { Venue.new("Venue", nil).geocode }
-    end
-  end
-
-  def test_extract_coordinates
-    coords = [-23,47]
-    l = Landmark.new("Madagascar", coords[0], coords[1])
-    assert_equal coords, Geocoder::Calculations.extract_coordinates(l)
-    assert_equal coords, Geocoder::Calculations.extract_coordinates(coords)
-  end
-end
diff --git a/test/lookup_test.rb b/test/lookup_test.rb
index 9cce80b7751c06470161fa5062c5399808abf08f..5b4550c6c5045705395fb7bc687a6eda16bb7803 100644
--- a/test/lookup_test.rb
+++ b/test/lookup_test.rb
@@ -4,13 +4,20 @@ require 'test_helper'
 class LookupTest < Test::Unit::TestCase
 
   def test_search_returns_empty_array_when_no_results
-    all_lookups_except_test.each do |l|
-      lookup = Geocoder.send(:get_lookup, l)
-      assert_equal [], lookup.send(:results, "no results"),
+    Geocoder::Lookup.all_services_except_test.each do |l|
+      lookup = Geocoder::Lookup.get(l)
+      assert_equal [], lookup.send(:results, Geocoder::Query.new("no results")),
         "Lookup #{l} does not return empty array when no results."
     end
   end
 
+  def test_does_not_choke_on_nil_address
+    Geocoder::Lookup.all_services.each do |l|
+      Geocoder::Configuration.lookup = l
+      assert_nothing_raised { Venue.new("Venue", nil).geocode }
+    end
+  end
+
   def test_hash_to_query
     g = Geocoder::Lookup::Google.new
     assert_equal "a=1&b=2", g.send(:hash_to_query, {:a => 1, :b => 2})
@@ -19,19 +26,19 @@ class LookupTest < Test::Unit::TestCase
   def test_google_api_key
     Geocoder::Configuration.api_key = "MY_KEY"
     g = Geocoder::Lookup::Google.new
-    assert_match "key=MY_KEY", g.send(:query_url, "Madison Square Garden, New York, NY  10001, United States")
+    assert_match "key=MY_KEY", g.send(:query_url, Geocoder::Query.new("Madison Square Garden, New York, NY  10001, United States"))
   end
 
   def test_yahoo_app_id
     Geocoder::Configuration.api_key = "MY_KEY"
     g = Geocoder::Lookup::Yahoo.new
-    assert_match "appid=MY_KEY", g.send(:query_url, "Madison Square Garden, New York, NY  10001, United States")
+    assert_match "appid=MY_KEY", g.send(:query_url, Geocoder::Query.new("Madison Square Garden, New York, NY  10001, United States"))
   end
 
   def test_geocoder_ca_showpostal
     Geocoder::Configuration.api_key = "MY_KEY"
     g = Geocoder::Lookup::GeocoderCa.new
-    assert_match "showpostal=1", g.send(:query_url, "Madison Square Garden, New York, NY  10001, United States")
+    assert_match "showpostal=1", g.send(:query_url, Geocoder::Query.new("Madison Square Garden, New York, NY  10001, United States"))
   end
 
 end
diff --git a/test/query_test.rb b/test/query_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..22a90b5c674d181f7815b19e6949c4ea87d66c80
--- /dev/null
+++ b/test/query_test.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+require 'test_helper'
+
+class QueryTest < Test::Unit::TestCase
+
+  def test_ip_address_detection
+    assert Geocoder::Query.new("232.65.123.94").ip_address?
+    assert Geocoder::Query.new("666.65.123.94").ip_address? # technically invalid
+    assert Geocoder::Query.new("::ffff:12.34.56.78").ip_address?
+    assert !Geocoder::Query.new("232.65.123.94.43").ip_address?
+    assert !Geocoder::Query.new("232.65.123").ip_address?
+    assert !Geocoder::Query.new("::ffff:123.456.789").ip_address?
+  end
+
+  def test_blank_query_detection
+    assert Geocoder::Query.new(nil).blank?
+    assert Geocoder::Query.new("").blank?
+    assert Geocoder::Query.new("\t  ").blank?
+    assert !Geocoder::Query.new("a").blank?
+    assert !Geocoder::Query.new("Москва").blank? # no ASCII characters
+  end
+
+  def test_coordinates_detection
+    assert Geocoder::Query.new("51.178844,5").coordinates?
+    assert Geocoder::Query.new("51.178844, -1.826189").coordinates?
+    assert !Geocoder::Query.new("232.65.123").coordinates?
+  end
+
+  def test_loopback_ip_address
+    assert Geocoder::Query.new("0.0.0.0").loopback_ip_address?
+    assert Geocoder::Query.new("127.0.0.1").loopback_ip_address?
+    assert !Geocoder::Query.new("232.65.123.234").loopback_ip_address?
+  end
+end
diff --git a/test/result_test.rb b/test/result_test.rb
index 85bb0f4f7ee26bf2534a3cc3ce8562c7eecd58b3..ab207c5fb6f0bd4834cf88b3764708d2db91ebee 100644
--- a/test/result_test.rb
+++ b/test/result_test.rb
@@ -4,7 +4,7 @@ require 'test_helper'
 class ResultTest < Test::Unit::TestCase
 
   def test_result_has_required_attributes
-    all_lookups_except_test.each do |l|
+    Geocoder::Lookup.all_services_except_test.each do |l|
       Geocoder::Configuration.lookup = l
       result = Geocoder.search([45.423733, -75.676333]).first
       assert_result_has_required_attributes(result)
diff --git a/test/services_test.rb b/test/services_test.rb
index 83fe571a0eaaa21a066339da4857ddf9cfedc32b..ef701510675f4df140030812dfe73c3e01b78279 100644
--- a/test/services_test.rb
+++ b/test/services_test.rb
@@ -41,7 +41,7 @@ class ServicesTest < Test::Unit::TestCase
   def test_google_premier_query_url
     Geocoder::Configuration.api_key = ["deadbeef", "gme-test", "test-dev"]
     assert_equal "http://maps.googleapis.com/maps/api/geocode/json?address=Madison+Square+Garden%2C+New+York%2C+NY&channel=test-dev&client=gme-test&language=en&sensor=false&signature=doJvJqX7YJzgV9rJ0DnVkTGZqTg=",
-      Geocoder::Lookup::GooglePremier.new.send(:query_url, "Madison Square Garden, New York, NY", false)
+      Geocoder::Lookup::GooglePremier.new.send(:query_url, Geocoder::Query.new("Madison Square Garden, New York, NY"))
   end
 
 
diff --git a/test/test_helper.rb b/test/test_helper.rb
index a9cf367fe3a1ab9e02faea9617da9d9f2808bf85..aa7d6d7a2b5607fde88ed8359170922fe05d57d0 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -60,10 +60,10 @@ module Geocoder
 
     class Google < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        file = case query
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        file = case query.text
           when "no results";   :no_results
           when "no locality";  :no_locality
           when "no city data"; :no_city_data
@@ -78,10 +78,10 @@ module Geocoder
 
     class Yahoo < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        file = case query
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        file = case query.text
           when "no results";  :no_results
           else                :madison_square_garden
         end
@@ -91,10 +91,10 @@ module Geocoder
 
     class Yandex < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        file = case query
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        file = case query.text
           when "no results";  :no_results
           when "invalid key"; :invalid_key
           else                :kremlin
@@ -105,13 +105,13 @@ module Geocoder
 
     class GeocoderCa < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        if reverse
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        if query.reverse_geocode?
           read_fixture "geocoder_ca_reverse.json"
         else
-          file = case query
+          file = case query.text
             when "no results";  :no_results
             else                :madison_square_garden
           end
@@ -122,10 +122,10 @@ module Geocoder
 
     class Freegeoip < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        file = case query
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        file = case query.text
           when "no results";  :no_results
           else                "74_200_247_59"
         end
@@ -135,13 +135,13 @@ module Geocoder
 
     class Bing < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        if reverse
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        if query.reverse_geocode?
           read_fixture "bing_reverse.json"
         else
-          file = case query
+          file = case query.text
             when "no results";  :no_results
             else                :madison_square_garden
           end
@@ -152,10 +152,10 @@ module Geocoder
 
     class Nominatim < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        file = case query
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        file = case query.text
           when "no results";  :no_results
           else                :madison_square_garden
         end
@@ -165,10 +165,10 @@ module Geocoder
 
     class Mapquest < Base
       private #-----------------------------------------------------------------
-      def fetch_raw_data(query, reverse = false)
-        raise TimeoutError if query == "timeout"
-        raise SocketError if query == "socket_error"
-        file = case query
+      def fetch_raw_data(query)
+        raise TimeoutError if query.text == "timeout"
+        raise SocketError if query.text == "socket_error"
+        file = case query.text
           when "no results";  :no_results
           else                :madison_square_garden
         end
@@ -277,18 +277,6 @@ class Test::Unit::TestCase
     }[abbrev]
   end
 
-  def all_lookups
-    Geocoder.valid_lookups
-  end
-
-  def all_lookups_except_test
-    Geocoder.valid_lookups_except_test
-  end
-
-  def street_lookups
-    Geocoder.street_lookups
-  end
-
   def is_nan_coordinates?(coordinates)
     return false unless coordinates.respond_to? :size # Should be an array
     return false unless coordinates.size == 2 # Should have dimension 2