diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc
index 0a5300a9547ef5a93f8f9bd4dc7d6a4dba22dc3a..3591852cad61e7f66fbce8f5c0f8703c1d3f2b9a 100644
--- a/CHANGELOG.rdoc
+++ b/CHANGELOG.rdoc
@@ -2,6 +2,12 @@
 
 Per-release changes to Geocoder.
 
+== 1.2.0 (???)
+
+* Add support for setting arbitrary params in geocoding request URL.
+* Add support for Google's :bounds parameter (thanks to github.com/rosscooperman and github.com/peterjm for submitting suggestions).
+* Code refactoring and cleanup (most notably, added Geocoder::Query class).
+
 == 1.1.3 (2012 Aug 26)
 
 * Add support for Mapquest geocoding service (thanks github.com/razorinc).
diff --git a/README.rdoc b/README.rdoc
index a99a85583196ae5e54f4b9035ec6e2683e170e1b..c1dc8416d69c9a4de23dd7c04d21820c496a23e7 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -269,7 +269,10 @@ By default Geocoder uses Google's geocoding API to fetch coordinates and street
 
   end
 
-Please see lib/geocoder/configuration.rb for a complete list of configuration options.
+Please see lib/geocoder/configuration.rb for a complete list of configuration options. Additionally, some lookups have their own configuration options which are listed in the comparison chart below, and as of version 1.2.0 you can pass arbitrary parameters to any geocoding service. For example, to use Nominatim's <tt>countrycodes</tt> parameter:
+
+  Geocoder::Configuration.lookup = :nominatim
+  Geocoder.search("Paris", :params => {:countrycodes => "gb,de,fr,es,us"})
 
 
 === Listing and Comparison
@@ -284,6 +287,7 @@ Quota:: 2,500 requests/day, 100,000 with Google Maps API Premier
 Region:: world
 SSL support:: yes
 Languages:: ar, eu, bg, bn, ca, cs, da, de, el, en, en-AU, en-GB, es, eu, fa, fi, fil, fr, gl, gu, hi, hr, hu, id, it, iw, ja, kn, ko, lt, lv, ml, mr, nl, no, pl, pt, pt-BR, pt-PT, ro, ru, sk, sl, sr, sv, tl, ta, te, th, tr, uk, vi, zh-CN, zh-TW (see http://spreadsheets.google.com/pub?key=p9pdwsai2hDMsLkXsoM05KQ&gid=1)
+Extra options:: <tt>:bounds</tt> - pass SW and NE coordinates as an array of two arrays to bias results towards a viewport
 Documentation:: http://code.google.com/apis/maps/documentation/geocoding/#JSON
 Terms of Service:: http://code.google.com/apis/maps/terms.html#section_10_12
 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..."
@@ -357,7 +361,7 @@ Documentation:: http://www.mapquestapi.com/geocoding/
 Terms of Service:: http://info.mapquest.com/terms-of-use/
 Limitations:: ?
 
-==== FreeGeoIP
+==== FreeGeoIP (<tt>:freegeoip</tt>)
 
 API key:: none
 Quota:: 1000 requests per hour.  After reaching the hourly quota, all of your requests will result in HTTP 403 (Forbidden) until it clears up on the next roll over.
diff --git a/lib/geocoder.rb b/lib/geocoder.rb
index 76f5ec20cc01a26f4ce05ca73516ce786a6ac815..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,85 +48,6 @@ module Geocoder
     end
     @cache
   end
-
-  ##
-  # Array of valid Lookup names.
-  #
-  def valid_lookups
-    street_lookups + ip_lookups
-  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(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 8822bf627a664ae6cb48ebddaf63f40a91329544..a5648631b10babc22c8583a8f52257cbcc9c8e0e 100644
--- a/lib/geocoder/cli.rb
+++ b/lib/geocoder/cli.rb
@@ -32,9 +32,10 @@ module Geocoder
           Geocoder::Configuration.http_proxy = proxy
         end
 
-        opts.on("-s <service>", Geocoder.street_lookups, "--service <service>",
-          "Geocoding service: #{Geocoder.street_lookups * ', '}") 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
 
         opts.on("-t <seconds>", "--timeout <seconds>",
@@ -92,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/configuration.rb b/lib/geocoder/configuration.rb
index 7bbe787daa8125d6597d5249962ed64ed6ea4688..c2df6d3eb2817afc7d29eaa7febde5df061e3403 100644
--- a/lib/geocoder/configuration.rb
+++ b/lib/geocoder/configuration.rb
@@ -40,6 +40,7 @@ module Geocoder
     OPTIONS = [
       :timeout,
       :lookup,
+      :ip_lookup,
       :language,
       :http_headers,
       :use_https,
@@ -61,7 +62,8 @@ module Geocoder
 
     def set_defaults
       @timeout      = 3           # geocoding service timeout (secs)
-      @lookup       = :google     # name of geocoding service (symbol)
+      @lookup       = :google     # name of street address geocoding service (symbol)
+      @ip_lookup    = :freegeoip  # name of IP address geocoding service (symbol)
       @language     = :en         # ISO-639 language code
       @http_headers = {}          # HTTP headers for lookup
       @use_https    = false       # use HTTPS for lookup requests? (if supported)
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..7c80494a07050aaa74061c2681ff778d52a64328 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,24 @@ module Geocoder
       ##
       # Geocoder::Result object or nil on timeout or other error.
       #
-      def results(query, reverse = false)
+      def results(query)
         fail
       end
 
+      def query_url_params(query)
+        query.options[:params] || {}
+      end
+
+      def url_query_string(query)
+        hash_to_query(
+          query_url_params(query).reject{ |key,value| value.nil? }
+        )
+      end
+
       ##
       # URL to use for querying the geocoding engine.
       #
-      def query_url(query, reverse = false)
+      def query_url(query)
         fail
       end
 
@@ -111,8 +113,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 +146,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 +173,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..1edb567a8532a4b3ddc8c10b0640e0c1ee2861c9 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,13 +21,17 @@ module Geocoder::Lookup
       end
     end
 
-    def query_url(query, reverse = false)
-      params = {:key => Geocoder::Configuration.api_key}
-      params[:query] = query unless reverse
+    def query_url_params(query)
+      super.merge(
+        :key => Geocoder::Configuration.api_key,
+        :query => query.reverse_geocode? ? nil : query.sanitized_text
+      )
+    end
 
-      base_url = "http://dev.virtualearth.net/REST/v1/Locations"
-      url_tail = reverse ? "/#{query}?" : "?"
-      base_url + url_tail + hash_to_query(params)
+    def query_url(query)
+      "http://dev.virtualearth.net/REST/v1/Locations" +
+        (query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
+        url_query_string(query)
     end
   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..97ac2721c756fe0e158f7ac51b3106f7bb9af1bb 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,24 +18,28 @@ module Geocoder::Lookup
       return []
     end
 
-    def query_url(query, reverse = false)
-      params = {
+    def query_url_params(query)
+      params = super.merge(
         :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)
+      params
+    end
+
+    def query_url(query)
+      "http://geocoder.ca/?" + url_query_string(query)
     end
 
     def parse_raw_data(raw_data)
diff --git a/lib/geocoder/lookups/google.rb b/lib/geocoder/lookups/google.rb
index 6898c267551bc056834cf7969fda1bb7fa46f4a3..13bb676351260674d6a7dbc47f469586fac895f3 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,14 +27,26 @@ module Geocoder::Lookup
       return []
     end
 
-    def query_url(query, reverse = false)
+    def query_url_google_params(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
+        :language => Geocoder::Configuration.language
       }
-      "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + hash_to_query(params)
+      unless (bounds = query.options[:bounds]).nil?
+        params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join('|')
+      end
+      params
+    end
+
+    def query_url_params(query)
+      super.merge(query_url_google_params(query)).merge(
+        :key => Geocoder::Configuration.api_key
+      )
+    end
+
+    def query_url(query)
+      "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + url_query_string(query)
     end
   end
 end
diff --git a/lib/geocoder/lookups/google_premier.rb b/lib/geocoder/lookups/google_premier.rb
index 6befdddf585167868fcdbb58a042f0ea8175ea6d..b725b9fb4543aacd07206a7746a92d54f1bcdbf3 100644
--- a/lib/geocoder/lookups/google_premier.rb
+++ b/lib/geocoder/lookups/google_premier.rb
@@ -8,15 +8,16 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
-    def query_url(query, reverse = false)
-      params = {
-        (reverse ? :latlng : :address) => query,
-        :sensor => 'false',
-        :language => Geocoder::Configuration.language,
+    def query_url_params(query)
+      super.merge(query_url_google_params(query)).merge(
+        :key => nil, # don't use param inherited from Google lookup
         :client => Geocoder::Configuration.api_key[1],
         :channel => Geocoder::Configuration.api_key[2]
-      }.reject{ |key, value| value.nil? }
-      path = "/maps/api/geocode/json?#{hash_to_query(params)}"
+      )
+    end
+
+    def query_url(query)
+      path = "/maps/api/geocode/json?" + url_query_string(query)
       "#{protocol}://maps.googleapis.com#{path}&signature=#{sign(path)}"
     end
 
diff --git a/lib/geocoder/lookups/mapquest.rb b/lib/geocoder/lookups/mapquest.rb
index 08cd31d8de0d33d19d985188ddff854311fd17f3..404bda22eef80bfc0e79593a39df3345f6ef5717 100644
--- a/lib/geocoder/lookups/mapquest.rb
+++ b/lib/geocoder/lookups/mapquest.rb
@@ -1,33 +1,15 @@
 require 'geocoder/lookups/base'
+require "geocoder/lookups/nominatim"
 require "geocoder/results/mapquest"
 
 module Geocoder::Lookup
-  class Mapquest < Base
+  class Mapquest < Nominatim
 
     private # ---------------------------------------------------------------
 
-    def results(query, reverse = false)
-      return [] unless doc = fetch_data(query, reverse)
-      doc.is_a?(Array) ? doc : [doc]
-    end
-
-    def query_url(query, reverse = false)
-      params = {
-        :format => "json",
-        :polygon => "1",
-        :addressdetails => "1",
-        :"accept-language" => Geocoder::Configuration.language
-      }
-      if (reverse)
-        method = 'reverse'
-        parts = query.split(/\s*,\s*/);
-        params[:lat] = parts[0]
-        params[:lon] = parts[1]
-      else
-        method = 'search'
-        params[:q] = query
-      end
-      "http://open.mapquestapi.com/#{method}?" + hash_to_query(params)
+    def query_url(query)
+      method = query.reverse_geocode? ? "reverse" : "search"
+      "http://open.mapquestapi.com/#{method}?" + url_query_string(query)
     end
   end
 end
diff --git a/lib/geocoder/lookups/nominatim.rb b/lib/geocoder/lookups/nominatim.rb
index 12f2c1513879cb87346341c96b2aa92f30cab9bf..d79f7312650c2d6d3d7df63285c0b6cea5b4bb19 100644
--- a/lib/geocoder/lookups/nominatim.rb
+++ b/lib/geocoder/lookups/nominatim.rb
@@ -10,28 +10,31 @@ 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)
-      params = {
+    def query_url_params(query)
+      params = super.merge(
         :format => "json",
         :polygon => "1",
         :addressdetails => "1",
         :"accept-language" => Geocoder::Configuration.language
-      }
-      if (reverse)
-        method = 'reverse'
-        parts = query.split(/\s*,\s*/);
-        params[:lat] = parts[0]
-        params[:lon] = parts[1]
+      )
+      if query.reverse_geocode?
+        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)
+      params
+    end
+
+    def query_url(query)
+      method = query.reverse_geocode? ? "reverse" : "search"
+      "http://nominatim.openstreetmap.org/#{method}?" + url_query_string(query)
     end
   end
 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..9282f2e970deedc170889f1d5d2043673cf38251 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,15 +20,18 @@ module Geocoder::Lookup
       end
     end
 
-    def query_url(query, reverse = false)
-      params = {
-        :location => query,
+    def query_url_params(query)
+      super.merge(
+        :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
-      }
-      "http://where.yahooapis.com/geocode?" + hash_to_query(params)
+      )
+    end
+
+    def query_url(query)
+      "http://where.yahooapis.com/geocode?" + url_query_string(query)
     end
   end
 end
diff --git a/lib/geocoder/lookups/yandex.rb b/lib/geocoder/lookups/yandex.rb
index 452d27694eea2728450e6cb05877b909ee441f7f..9b4f63d6e8104fe422f7d48474e51fba30973bf6 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,15 +25,22 @@ module Geocoder::Lookup
       end
     end
 
-    def query_url(query, reverse = false)
-      query = query.split(",").reverse.join(",") if reverse
-      params = {
-        :geocode => query,
+    def query_url_params(query)
+      if query.reverse_geocode?
+        q = query.coordinates.reverse.join(",")
+      else
+        q = query.sanitized_text
+      end
+      super.merge(
+        :geocode => q,
         :format => "json",
         :plng => "#{Geocoder::Configuration.language}", # supports ru, uk, be
         :key => Geocoder::Configuration.api_key
-      }
-      "http://geocode-maps.yandex.ru/1.x/?" + hash_to_query(params)
+      )
+    end
+
+    def query_url(query)
+      "http://geocode-maps.yandex.ru/1.x/?" + url_query_string(query)
     end
   end
 end
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/results/mapquest.rb b/lib/geocoder/results/mapquest.rb
index 8d2b6022f704e68122f1c56c1639349c2f225ee1..f977a6b5f5afb5e26aa8303e29723140daaae04b 100644
--- a/lib/geocoder/results/mapquest.rb
+++ b/lib/geocoder/results/mapquest.rb
@@ -1,66 +1,7 @@
 require 'geocoder/results/base'
+require 'geocoder/results/nominatim'
 
 module Geocoder::Result
-  class Mapquest < Base
-
-    def house_number
-      @data['address']['house_number']
-    end
-
-    def address
-      @data['display_name']
-    end
-
-    def street
-      @data['address']['road']
-    end
-
-    def city
-      @data['address']['city']
-    end
-
-    def village
-      @data['address']['villiage']
-    end
-
-    def town
-      @data['address']['town']
-    end
-
-    def state
-      @data['address']['state']
-    end
-
-    alias_method :state_code, :state
-
-    def postal_code
-      @data['address']['postcode']
-    end
-
-    def county
-      @data['address']['county']
-    end
-
-    def country
-      @data['address']['country']
-    end
-
-    def country_code
-      @data['address']['country_code']
-    end
-
-    def coordinates
-      [@data['lat'].to_f, @data['lon'].to_f]
-    end
-
-    def self.response_attributes
-      %w[place_id boundingbox license polygonpoints display_name class type stadium suburb]
-    end
-
-    response_attributes.each do |a|
-      define_method a do
-        @data[a]
-      end
-    end
+  class Mapquest < Nominatim
   end
 end
diff --git a/lib/geocoder/results/nominatim.rb b/lib/geocoder/results/nominatim.rb
index 59ef04678bef6d94da01c2f9e86c808d86556302..afd9af6f1008ab8c7ca544fcddadc6b850f6ab5d 100644
--- a/lib/geocoder/results/nominatim.rb
+++ b/lib/geocoder/results/nominatim.rb
@@ -5,8 +5,9 @@ module Geocoder::Result
 
     def poi
       %w[stadium bus_stop tram_stop].each do |key|
-        @data['address'][key] if @data['address'].key?(key)
+        return @data['address'][key] if @data['address'].key?(key)
       end
+      return nil
     end
 
     def house_number
diff --git a/lib/geocoder/stores/active_record.rb b/lib/geocoder/stores/active_record.rb
index 96b11e0701a2c3a3873e0930b295e1822303fc14..426622e8b5644b73d225b3f554dbec7521c247a5 100644
--- a/lib/geocoder/stores/active_record.rb
+++ b/lib/geocoder/stores/active_record.rb
@@ -148,9 +148,7 @@ module Geocoder::Store
         distance = full_distance_from_sql(latitude, longitude, options)
         conditions = ["#{distance} <= ?", radius]
         default_near_scope_options(latitude, longitude, radius, options).merge(
-          :select => select_addon(options) +
-            "#{distance} AS distance" +
-            (bearing ? ", #{bearing} AS bearing" : ""),
+          :select => select_clause(options[:select], distance, bearing),
           :conditions => add_exclude_condition(conditions, options[:exclude])
         )
       end
@@ -222,18 +220,22 @@ module Geocoder::Store
           [b[0], b[2], b[1], b[3]
         ]
         default_near_scope_options(latitude, longitude, radius, options).merge(
-          :select => select_addon(options) +
-            "#{distance} AS distance" +
-            (bearing ? ", #{bearing} AS bearing" : ""),
+          :select => select_clause(options[:select], distance, bearing),
           :conditions => add_exclude_condition(conditions, options[:exclude])
         )
       end
 
       ##
-      # Select string to add
+      # Generate the SELECT clause.
       #
-      def select_addon(options)
-        options[:select] == :ignore ? "" : "#{options[:select] || full_column_name("*")}, "
+      def select_clause(columns, distance, bearing)
+        if columns == :geo_only
+          clause = ""
+        else
+          clause = (columns || full_column_name("*")) + ", "
+        end
+        clause + "#{distance} AS distance" +
+          (bearing ? ", #{bearing} AS bearing" : "")
       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/integration/smoke_test.rb b/test/integration/smoke_test.rb
index 8d80e1e14730cd9ad53072ef2ff9c07c5f4cf373..b0c805e61759fc9695c14bde1c0c8eca8d0d1802 100644
--- a/test/integration/smoke_test.rb
+++ b/test/integration/smoke_test.rb
@@ -8,15 +8,17 @@ class SmokeTest < Test::Unit::TestCase
 
   def test_simple_zip_code_search
     result = Geocoder.search "27701"
-    assert_equal "Durham", result.first.city
-    assert_equal "North Carolina", result.first.state
+    assert_not_nil (r = result.first)
+    assert_equal "Durham", r.city
+    assert_equal "North Carolina", r.state
   end
 
   def test_simple_zip_code_search_with_ssl
     Geocoder::Configuration.use_https = true
     result = Geocoder.search "27701"
-    assert_equal "Durham", result.first.city
-    assert_equal "North Carolina", result.first.state
+    assert_not_nil (r = result.first)
+    assert_equal "Durham", r.city
+    assert_equal "North Carolina", r.state
   ensure
     Geocoder::Configuration.use_https = false
   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..07cb694c728458b88f1ac04ab7654aa11bcf05ac 100644
--- a/test/services_test.rb
+++ b/test/services_test.rb
@@ -4,6 +4,18 @@ require 'test_helper'
 class ServicesTest < Test::Unit::TestCase
 
 
+  def test_query_url_contains_values_in_params_hash
+    Geocoder::Lookup.all_services_except_test.each do |l|
+      next if l == :google_premier # TODO: need to set keys to test
+      next if l == :freegeoip # does not use query string
+      url = Geocoder::Lookup.get(l).send(:query_url, Geocoder::Query.new(
+        "test", :params => {:one_in_the_hand => "two in the bush"}
+      ))
+      assert_match /one_in_the_hand=two\+in\+the\+bush/, url,
+        "Lookup #{l} does not appear to support arbitrary params in URL"
+    end
+  end
+
   # --- Google ---
 
   def test_google_result_components
@@ -28,6 +40,14 @@ class ServicesTest < Test::Unit::TestCase
       result.precision
   end
 
+  def test_google_query_url_contains_bounds
+    lookup = Geocoder::Lookup::Google.new
+    url = lookup.send(:query_url, Geocoder::Query.new(
+      "Some Intersection",
+      :bounds => [[40.0, -120.0], [39.0, -121.0]]
+    ))
+    assert_match /bounds=40.0+%2C-120.0+%7C39.0+%2C-121.0+/, url
+  end
 
   # --- Google Premier ---
 
@@ -41,7 +61,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 8ac6b3e0a6a939e7635eca5e6f63479a532c4a4c..fcdd13381f4def69fef9b672ba261aaee3238ee9 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
@@ -163,12 +163,12 @@ module Geocoder
       end
     end
 
-    class Mapquest < Base
+    class Mapquest < Nominatim
       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 - [:test]
-  end
-
-  def street_lookups
-    all_lookups - [:freegeoip]
-  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