New Upstream Release - ruby-geocoder

Ready changes

Summary

Merged new upstream version: 1.8.1 (was: 1.5.1).

Resulting package

Built on 2023-01-11T04:08 (took 2m55s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-geocoder

Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5cf518a..4aaaddf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,93 @@ Changelog
 
 Major changes to Geocoder for each release. Please see the Git log for complete list of changes.
 
+1.8.1 (2022 Sep 23)
+-------------------
+* Add support for IPBase lookup (thanks github.com/jonallured).
+* Test cleanup (thanks github.com/jonallured).
+* Prevent errors when existing constant name shadows a lookup class (thanks github.com/avram-twitch).
+
+1.8.0 (2022 May 17)
+-------------------
+* Add support for 2GIS lookup (thanks github.com/ggrikgg).
+* Change cache configuration structure and add an expiration option. Cache prefix is now set via {cache_options: {prefix: ...}} instead of {cache_prefix: ...}. See README for details.
+* Add `:fields` parameter for :google_places_details and :google_places_search lookups. If you haven't been requesting specific fields, you may start getting different data (defaults are now the APIs' defaults). See for details: https://github.com/alexreisner/geocoder/pull/1572 (thanks github.com/czlee).
+* Update :here lookup to use API version 7. Query options are different, API key must be a string (not an array). See API docs at https://developer.here.com/documentation/geocoding-search-api/api-reference-swagger.html (thanks github.com/Pritilender).
+
+1.7.5 (2022 Mar 14)
+-------------------
+* Avoid lookup naming collisions in some environments.
+
+1.7.4 (2022 Mar 14)
+-------------------
+* Add ability to use app-defined lookups (thanks github.com/januszm).
+* Updates to LocationIQ and FreeGeoIP lookups.
+
+1.7.3 (2022 Jan 17)
+-------------------
+* Get rid of unnecessary cache_prefix deprecation warnings.
+
+1.7.2 (2022 Jan  2)
+-------------------
+* Fix uninitialized constant error (occurring on some systems with v1.7.1).
+
+1.7.1 (2022 Jan  1)
+-------------------
+* Various bugfixes and refactorings.
+
+1.7.0 (2021 Oct 11)
+-------------------
+* Add support for Geoapify and Photo lookups (thanks github.com/ahukkanen).
+* Add support for IPQualityScore IP lookup (thanks github.com/jamesbebbington).
+* Add support for Amazon Location Service lookup (thanks github.com/mplewis).
+* Add support for Melissa lookup (thanks github.com/ALacker).
+* Drop official support for Ruby 2.0.x and Rails 4.x.
+
+1.6.7 (2021 Apr 17)
+-------------------
+* Add support for Abstract API lookup (thanks github.com/randoum).
+
+1.6.6 (2021 Mar  4)
+-------------------
+* Rescue from exception on cache read/write error. Issue warning instead.
+
+1.6.5 (2021 Feb 10)
+-------------------
+* Fix backward coordinates bug in NationaalregisterNl lookup (thanks github.com/Marthyn).
+* Allow removal of single stubs in test mode (thanks github.com/jmmastey).
+* Improve results for :ban_data_gouv_fr lookup (thanks github.com/Intrepidd).
+
+1.6.4 (2020 Oct  6)
+-------------------
+* Various updates in response to geocoding API changes.
+* Refactor of Google Places Search lookup (thanks github.com/maximilientyc).
+
+1.6.3 (2020 Apr 30)
+-------------------
+* Update URL for :telize lookup (thanks github.com/alexwalling).
+* Fix bug parsing IPv6 with port (thanks github.com/gdomingu).
+
+1.6.2 (2020 Mar 16)
+-------------------
+* Add support for :nationaal_georegister_nl lookup (thanks github.com/opensourceame).
+* Add support for :uk_ordnance_survey_names lookup (thanks github.com/pezholio).
+* Refactor and fix bugs in Yandex lookup (thanks github.com/iarie and stereodenis).
+
+1.6.1 (2020 Jan 23)
+-------------------
+* Sanitize lat/lon values passed to within_bounding_box to prevent SQL injection.
+
+1.6.0 (2020 Jan  6)
+-------------------
+* Drop support for Rails 3.x.
+* Add support for :osmnames lookup (thanks github.com/zacviandier).
+* Add support for :ipgeolocation IP lookup (thanks github.com/ahsannawaz111).
+
+1.5.2 (2019 Oct  3)
+-------------------
+* Add support for :ipregistry lookup (thanks github.com/ipregistry).
+* Various fixes for Yandex lookup.
+
 1.5.1 (2019 Jan 23)
 -------------------
 * Add support for :tencent lookup (thanks github.com/Anders-E).
diff --git a/LICENSE b/LICENSE
index 767580c..523692c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2009-11 Alex Reisner
+Copyright (c) 2009-2021 Alex Reisner
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/README.md b/README.md
index 4e2c258..f1719b9 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,27 @@
 Geocoder
 ========
 
-**A complete geocoding solution for Ruby.**
+**Complete geocoding solution for Ruby.**
 
 [![Gem Version](https://badge.fury.io/rb/geocoder.svg)](http://badge.fury.io/rb/geocoder)
 [![Code Climate](https://codeclimate.com/github/alexreisner/geocoder/badges/gpa.svg)](https://codeclimate.com/github/alexreisner/geocoder)
-[![Build Status](https://travis-ci.org/alexreisner/geocoder.svg?branch=master)](https://travis-ci.org/alexreisner/geocoder)
-[![GitHub Issues](https://img.shields.io/github/issues/alexreisner/geocoder.svg)](https://github.com/alexreisner/geocoder/issues)
-[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
+[![Build Status](https://travis-ci.com/alexreisner/geocoder.svg?branch=master)](https://travis-ci.com/alexreisner/geocoder)
 
 Key features:
 
-* Forward and reverse geocoding, and IP address geocoding.
+* Forward and reverse geocoding.
+* IP address geocoding.
 * Connects to more than 40 APIs worldwide.
 * Performance-enhancing features like caching.
-* Advanced configuration allows different parameters and APIs to be used in different conditions.
 * Integrates with ActiveRecord and Mongoid.
 * Basic geospatial queries: search within radius (or rectangle, or ring).
 
 Compatibility:
 
-* Supports multiple Ruby versions: Ruby 2.x, and JRuby.
-* Supports multiple databases: MySQL, PostgreSQL, SQLite, and MongoDB (1.7.0 and higher).
-* Supports Rails 3, 4, and 5. If you need to use it with Rails 2 please see the `rails2` branch (no longer maintained, limited feature set).
-* Works very well outside of Rails, you just need to install either the `json` (for MRI) or `json_pure` (for JRuby) gem.
+* Ruby versions: 2.1+, and JRuby.
+* Databases: MySQL, PostgreSQL, SQLite, and MongoDB.
+* Rails: 5.x, 6.x, and 7.x.
+* Works outside of Rails with the `json` (for MRI) or `json_pure` (for JRuby) gem.
 
 
 Table of Contents
@@ -53,8 +51,8 @@ The Rest:
 * [Technical Discussions](#technical-discussions)
 * [Troubleshooting](#troubleshooting)
 * [Known Issues](#known-issues)
-* [Reporting Issues](#reporting-issues)
-* [Contributing](#contributing)
+* [Reporting Issues](https://github.com/alexreisner/geocoder/blob/master/CONTRIBUTING.md#reporting-bugs)
+* [Contributing](https://github.com/alexreisner/geocoder/blob/master/CONTRIBUTING.md#making-changes)
 
 See Also:
 
@@ -66,23 +64,29 @@ Basic Search
 
 In its simplest form, Geocoder takes an address and searches for its latitude/longitude coordinates:
 
-    results = Geocoder.search("Paris")
-    results.first.coordinates
-    => [48.856614, 2.3522219]  # latitude and longitude
+```ruby
+results = Geocoder.search("Paris")
+results.first.coordinates
+# => [48.856614, 2.3522219]  # latitude and longitude
+```
 
 The reverse is possible too. Given coordinates, it finds an address:
 
-    results = Geocoder.search([48.856614, 2.3522219])
-    results.first.address
-    => "Hôtel de Ville, 75004 Paris, France"
+```ruby
+results = Geocoder.search([48.856614, 2.3522219])
+results.first.address
+# => "Hôtel de Ville, 75004 Paris, France"
+```
 
-You can also look up the location of an IP addresses:
+You can also look up the location of an IP address:
 
-    results = Geocoder.search("172.56.21.89")
-    results.first.coordinates
-    => [30.267153, -97.7430608]
-    results.first.country
-    => "United States"
+```ruby
+results = Geocoder.search("172.56.21.89")
+results.first.coordinates
+# => [30.267153, -97.7430608]
+results.first.country
+# => "United States"
+```
 
 **The success and accuracy of geocoding depends entirely on the API being used to do these lookups.** Most queries work fairly well with the default configuration, but every application has different needs and every API has its particular strengths and weaknesses. If you need better coverage for your application you'll want to get familiar with the large number of supported APIs, listed in the [API Guide](https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md).
 
@@ -94,30 +98,40 @@ To automatically geocode your objects:
 
 **1.** Your model must provide a method that returns an address to geocode. This can be a single attribute, but it can also be a method that returns a string assembled from different attributes (eg: `city`, `state`, and `country`). For example, if your model has `street`, `city`, `state`, and `country` attributes you might do something like this:
 
-    def address
-      [street, city, state, country].compact.join(', ')
-    end
+```ruby
+def address
+  [street, city, state, country].compact.join(', ')
+end
+```
 
 **2.** Your model must have a way to store latitude/longitude coordinates. With ActiveRecord, add two attributes/columns (of type float or decimal) called `latitude` and `longitude`. For MongoDB, use a single field (of type Array) called `coordinates` (i.e., `field :coordinates, type: Array`). (See [Advanced Model Configuration](#advanced-model-configuration) for using different attribute names.)
 
 **3.** In your model, tell geocoder where to find the object's address:
 
-    geocoded_by :address
+```ruby
+geocoded_by :address
+```
 
 This adds a `geocode` method which you can invoke via callback:
 
-    after_validation :geocode
+```ruby
+after_validation :geocode
+```
 
 Reverse geocoding (given lat/lon coordinates, find an address) is similar:
 
-    reverse_geocoded_by :latitude, :longitude
-    after_validation :reverse_geocode
+```ruby
+reverse_geocoded_by :latitude, :longitude
+after_validation :reverse_geocode
+```
 
 With any geocoded objects, you can do the following:
 
-    obj.distance_to([43.9,-98.6])  # distance from obj to point
-    obj.bearing_to([43.9,-98.6])   # bearing from obj to point
-    obj.bearing_from(obj2)         # bearing from obj2 to obj
+```ruby
+obj.distance_to([43.9,-98.6])  # distance from obj to point
+obj.bearing_to([43.9,-98.6])   # bearing from obj to point
+obj.bearing_from(obj2)         # bearing from obj2 to obj
+```
 
 The `bearing_from/to` methods take a single argument which can be: a `[lat,lon]` array, a geocoded object, or a geocodable address (string). The `distance_from/to` methods also take a units argument (`:mi`, `:km`, or `:nm` for nautical miles). See [Distance and Bearing](#distance-and-bearing) below for more info.
 
@@ -125,18 +139,24 @@ The `bearing_from/to` methods take a single argument which can be: a `[lat,lon]`
 
 Before you can call `geocoded_by` you'll need to include the necessary module using one of the following:
 
-    include Geocoder::Model::Mongoid
-    include Geocoder::Model::MongoMapper
+```ruby
+include Geocoder::Model::Mongoid
+include Geocoder::Model::MongoMapper
+```
 
 ### Latitude/Longitude Order in MongoDB
 
 Everywhere coordinates are passed to methods as two-element arrays, Geocoder expects them to be in the order: `[lat, lon]`. However, as per [the GeoJSON spec](http://geojson.org/geojson-spec.html#positions), MongoDB requires that coordinates be stored longitude-first (`[lon, lat]`), so internally they are stored "backwards." Geocoder's methods attempt to hide this, so calling `obj.to_coordinates` (a method added to the object by Geocoder via `geocoded_by`) returns coordinates in the conventional order:
 
-    obj.to_coordinates  # => [37.7941013, -122.3951096] # [lat, lon]
+```ruby
+obj.to_coordinates  # => [37.7941013, -122.3951096] # [lat, lon]
+```
 
 whereas calling the object's coordinates attribute directly (`obj.coordinates` by default) returns the internal representation which is probably the reverse of what you want:
 
-    obj.coordinates     # => [-122.3951096, 37.7941013] # [lon, lat]
+```ruby
+obj.coordinates     # => [-122.3951096, 37.7941013] # [lon, lat]
+```
 
 So, be careful.
 
@@ -144,7 +164,9 @@ So, be careful.
 
 To use Geocoder with ActiveRecord and a framework other than Rails (like Sinatra or Padrino), you will need to add this in your model before calling Geocoder methods:
 
-    extend Geocoder::Model::ActiveRecord
+```ruby
+extend Geocoder::Model::ActiveRecord
+```
 
 
 Geospatial Database Queries
@@ -154,19 +176,23 @@ Geospatial Database Queries
 
 To find objects by location, use the following scopes:
 
-    Venue.near('Omaha, NE, US')                   # venues within 20 miles of Omaha
-    Venue.near([40.71, -100.23], 50)              # venues within 50 miles of a point
-    Venue.near([40.71, -100.23], 50, units: :km)  # venues within 50 kilometres of a point
-    Venue.geocoded                                # venues with coordinates
-    Venue.not_geocoded                            # venues without coordinates
+```ruby
+Venue.near('Omaha, NE, US')                   # venues within 20 miles of Omaha
+Venue.near([40.71, -100.23], 50)              # venues within 50 miles of a point
+Venue.near([40.71, -100.23], 50, units: :km)  # venues within 50 kilometres of a point
+Venue.geocoded                                # venues with coordinates
+Venue.not_geocoded                            # venues without coordinates
+```
 
 With geocoded objects you can do things like this:
 
-    if obj.geocoded?
-      obj.nearbys(30)                       # other objects within 30 miles
-      obj.distance_from([40.714,-100.234])  # distance from arbitrary point to object
-      obj.bearing_to("Paris, France")       # direction from object to arbitrary point
-    end
+```ruby
+if obj.geocoded?
+  obj.nearbys(30)                       # other objects within 30 miles
+  obj.distance_from([40.714,-100.234])  # distance from arbitrary point to object
+  obj.bearing_to("Paris, France")       # direction from object to arbitrary point
+end
+```
 
 ### For MongoDB-backed models:
 
@@ -178,8 +204,10 @@ Geocoding HTTP Requests
 
 Geocoder adds `location` and `safe_location` methods to the standard `Rack::Request` object so you can easily look up the location of any HTTP request by IP address. For example, in a Rails controller or a Sinatra app:
 
-    # returns Geocoder::Result object
-    result = request.location
+```ruby
+# returns Geocoder::Result object
+result = request.location
+```
 
 **The `location` method is vulnerable to trivial IP address spoofing via HTTP headers.**  If that's a problem for your application, use `safe_location` instead, but be aware that `safe_location` will *not* try to trace a request's originating IP through proxy headers; you will instead get the location of the last proxy the request passed through, if any (excepting any proxies you have explicitly whitelisted in your Rack config).
 
@@ -193,71 +221,82 @@ Geocoder supports a variety of street and IP address geocoding services. The def
 
 To create a Rails initializer with sample configuration:
 
-    rails generate geocoder:config
+```sh
+rails generate geocoder:config
+```
 
 Some common options are:
 
-    # config/initializers/geocoder.rb
-    Geocoder.configure(
+```ruby
+# config/initializers/geocoder.rb
+Geocoder.configure(
+  # street address geocoding service (default :nominatim)
+  lookup: :yandex,
 
-      # street address geocoding service (default :nominatim)
-      lookup: :yandex,
+  # IP address geocoding service (default :ipinfo_io)
+  ip_lookup: :maxmind,
 
-      # IP address geocoding service (default :ipinfo_io)
-      ip_lookup: :maxmind,
+  # to use an API key:
+  api_key: "...",
 
-      # to use an API key:
-      api_key: "...",
+  # geocoding service request timeout, in seconds (default 3):
+  timeout: 5,
 
-      # geocoding service request timeout, in seconds (default 3):
-      timeout: 5,
+  # set default units to kilometers:
+  units: :km,
 
-      # set default units to kilometers:
-      units: :km,
-
-      # caching (see [below](#caching) for details):
-      cache: Redis.new,
-      cache_prefix: "..."
-
-    )
+  # caching (see Caching section below for details):
+  cache: Redis.new,
+  cache_options: {
+    expiration: 1.day, # Defaults to `nil`
+    prefix: "another_key:" # Defaults to `geocoder:`
+  }
+)
+```
 
 Please see [`lib/geocoder/configuration.rb`](https://github.com/alexreisner/geocoder/blob/master/lib/geocoder/configuration.rb) for a complete list of configuration options. Additionally, some lookups have their own special configuration options which are directly supported by Geocoder. For example, to specify a value for Google's `bounds` parameter:
 
-    # with Google:
-    Geocoder.search("Middletown", bounds: [[40.6,-77.9], [39.9,-75.9]])
+```ruby
+# with Google:
+Geocoder.search("Middletown", bounds: [[40.6,-77.9], [39.9,-75.9]])
+```
 
 Please see the [source code for each lookup](https://github.com/alexreisner/geocoder/tree/master/lib/geocoder/lookups) to learn about directly supported parameters. Parameters which are not directly supported can be specified using the `:params` option, which appends options to the query string of the geocoding request. For example:
 
-    # Nominatim's `countrycodes` parameter:
-    Geocoder.search("Rome", params: {countrycodes: "us,ca"})
+```ruby
+# Nominatim's `countrycodes` parameter:
+Geocoder.search("Rome", params: {countrycodes: "us,ca"})
 
-    # Google's `region` parameter:
-    Geocoder.search("Rome", params: {region: "..."})
+# Google's `region` parameter:
+Geocoder.search("Rome", params: {region: "..."})
+```
 
 ### Configuring Multiple Services
 
 You can configure multiple geocoding services at once by using the service's name as a key for a sub-configuration hash, like this:
 
-    Geocoder.configure(
+```ruby
+Geocoder.configure(
 
-      timeout: 2,
-      cache: Redis.new,
+  timeout: 2,
+  cache: Redis.new,
 
-      yandex: {
-        api_key: "...",
-        timeout: 5
-      },
+  yandex: {
+    api_key: "...",
+    timeout: 5
+  },
 
-      baidu: {
-        api_key: "..."
-      },
+  baidu: {
+    api_key: "..."
+  },
 
-      maxmind: {
-        api_key: "...",
-        service: :omni
-      }
+  maxmind: {
+    api_key: "...",
+    service: :omni
+  }
 
-    )
+)
+```
 
 Lookup-specific settings override global settings so, in this example, the timeout for all lookups is 2 seconds, except for Yandex which is 5.
 
@@ -269,12 +308,16 @@ Performance and Optimization
 
 In MySQL and Postgres, queries use a bounding box to limit the number of points over which a more precise distance calculation needs to be done. To take advantage of this optimisation, you need to add a composite index on latitude and longitude. In your Rails migration:
 
-    add_index :table, [:latitude, :longitude]
+```ruby
+add_index :table, [:latitude, :longitude]
+```
 
 In MongoDB, by default, the methods `geocoded_by` and `reverse_geocoded_by` create a geospatial index. You can avoid index creation with the `:skip_index option`, for example:
 
-    include Geocoder::Model::Mongoid
-    geocoded_by :address, skip_index: true
+```ruby
+include Geocoder::Model::Mongoid
+geocoded_by :address, skip_index: true
+```
 
 ### Avoiding Unnecessary API Requests
 
@@ -285,13 +328,17 @@ Geocoding only needs to be performed under certain conditions. To avoid unnecess
 
 The exact code will vary depending on the method you use for your geocodable string, but it would be something like this:
 
-    after_validation :geocode, if: ->(obj){ obj.address.present? and obj.address_changed? }
+```ruby
+after_validation :geocode, if: ->(obj){ obj.address.present? and obj.address_changed? }
+```
 
 ### Caching
 
 When relying on any external service, it's always a good idea to cache retrieved data. When implemented correctly, it improves your app's response time and stability. It's easy to cache geocoding results with Geocoder -- just configure a cache store:
 
-    Geocoder.configure(cache: Redis.new)
+```ruby
+Geocoder.configure(cache: Redis.new)
+```
 
 This example uses Redis, but the cache store can be any object that supports these methods:
 
@@ -302,20 +349,30 @@ This example uses Redis, but the cache store can be any object that supports the
 
 Even a plain Ruby hash will work, though it's not a great choice (cleared out when app is restarted, not shared between app instances, etc).
 
+When using Rails use the Generic cache store as an adapter around `Rails.cache`:
+
+```ruby
+Geocoder.configure(cache: Geocoder::CacheStore::Generic.new(Rails.cache, {}))
+```
+
 You can also set a custom prefix to be used for cache keys:
 
-    Geocoder.configure(cache_prefix: "...")
+```ruby
+Geocoder.configure(cache_options: { prefix: "..." })
+```
 
 By default the prefix is `geocoder:`
 
 If you need to expire cached content:
 
-    Geocoder::Lookup.get(Geocoder.config[:lookup]).cache.expire(:all)  # expire cached results for current Lookup
-    Geocoder::Lookup.get(:nominatim).cache.expire("http://...")        # expire cached result for a specific URL
-    Geocoder::Lookup.get(:nominatim).cache.expire(:all)                # expire cached results for Google Lookup
-    # expire all cached results for all Lookups.
-    # Be aware that this methods spawns a new Lookup object for each Service
-    Geocoder::Lookup.all_services.each{|service| Geocoder::Lookup.get(service).cache.expire(:all)}
+```ruby
+Geocoder::Lookup.get(Geocoder.config[:lookup]).cache.expire(:all)  # expire cached results for current Lookup
+Geocoder::Lookup.get(:nominatim).cache.expire("http://...")        # expire cached result for a specific URL
+Geocoder::Lookup.get(:nominatim).cache.expire(:all)                # expire cached results for Google Lookup
+# expire all cached results for all Lookups.
+# Be aware that this methods spawns a new Lookup object for each Service
+Geocoder::Lookup.all_services.each{|service| Geocoder::Lookup.get(service).cache.expire(:all)}
+```
 
 Do *not* include the prefix when passing a URL to be expired. Expiring `:all` will only expire keys with the configured prefix -- it will *not* expire every entry in your key/value store.
 
@@ -329,44 +386,55 @@ Advanced Model Configuration
 
 You are not stuck with the `latitude` and `longitude` database column names (with ActiveRecord) or the `coordinates` array (Mongo) for storing coordinates. For example:
 
-    geocoded_by :address, latitude: :lat, longitude: :lon  # ActiveRecord
-    geocoded_by :address, coordinates: :coords             # MongoDB
+```ruby
+geocoded_by :address, latitude: :lat, longitude: :lon  # ActiveRecord
+geocoded_by :address, coordinates: :coords             # MongoDB
+```
 
 For reverse geocoding, you can specify the attribute where the address will be stored. For example:
 
-    reverse_geocoded_by :latitude, :longitude, address: :loc    # ActiveRecord
-    reverse_geocoded_by :coordinates, address: :street_address  # MongoDB
+```ruby
+reverse_geocoded_by :latitude, :longitude, address: :loc    # ActiveRecord
+reverse_geocoded_by :coordinates, address: :street_address  # MongoDB
+```
 
 To specify geocoding parameters in your model:
 
-    geocoded_by :address, params: {region: "..."}
+```ruby
+geocoded_by :address, params: {region: "..."}
+```
 
 Supported parameters: `:lookup`, `:ip_lookup`, `:language`, and `:params`. You can specify an anonymous function if you want to set these on a per-request basis. For example, to use different lookups for objects in different regions:
 
-    geocoded_by :address, lookup: lambda{ |obj| obj.geocoder_lookup }
+```ruby
+geocoded_by :address, lookup: lambda{ |obj| obj.geocoder_lookup }
 
-    def geocoder_lookup
-      if country_code == "RU"
-        :yandex
-      elsif country_code == "CN"
-        :baidu
-      else
-        :nominatim
-      end
-    end
+def geocoder_lookup
+  if country_code == "RU"
+    :yandex
+  elsif country_code == "CN"
+    :baidu
+  else
+    :nominatim
+  end
+end
+```
 
 ### Custom Result Handling
 
 So far we have seen examples where geocoding results are assigned automatically to predefined object attributes. However, you can skip the auto-assignment by providing a block which handles the parsed geocoding results any way you like, for example:
 
-    reverse_geocoded_by :latitude, :longitude do |obj,results|
-      if geo = results.first
-        obj.city    = geo.city
-        obj.zipcode = geo.postal_code
-        obj.country = geo.country_code
-      end
-    end
-    after_validation :reverse_geocode
+```ruby
+reverse_geocoded_by :latitude, :longitude do |obj,results|
+  if geo = results.first
+    obj.city    = geo.city
+    obj.zipcode = geo.postal_code
+    obj.country = geo.country_code
+  end
+end
+
+after_validation :reverse_geocode
+```
 
 Every `Geocoder::Result` object, `result`, provides the following data:
 
@@ -392,23 +460,26 @@ You can apply both forward and reverse geocoding to the same model (i.e. users c
 
 For example:
 
-    class Venue
-
-      # build an address from street, city, and state attributes
-      geocoded_by :address_from_components
+```ruby
+class Venue
+  # build an address from street, city, and state attributes
+  geocoded_by :address_from_components
 
-      # store the fetched address in the full_address attribute
-      reverse_geocoded_by :latitude, :longitude, address: :full_address
-    end
+  # store the fetched address in the full_address attribute
+  reverse_geocoded_by :latitude, :longitude, address: :full_address
+end
+```
 
 The same goes for latitude/longitude. However, for purposes of querying the database, there can be only one authoritative set of latitude/longitude attributes for use in database queries. This is whichever you specify last. For example, here the attributes *without* the `fetched_` prefix will be authoritative:
 
-    class Venue
-      geocoded_by :address,
-        latitude: :fetched_latitude,
-        longitude: :fetched_longitude
-      reverse_geocoded_by :latitude, :longitude
-    end
+```ruby
+class Venue
+  geocoded_by :address,
+    latitude: :fetched_latitude,
+    longitude: :fetched_longitude
+  reverse_geocoded_by :latitude, :longitude
+end
+```
 
 
 Advanced Database Queries
@@ -418,21 +489,29 @@ Advanced Database Queries
 
 The default `near` search looks for objects within a circle. To search within a doughnut or ring use the `:min_radius` option:
 
-    Venue.near("Austin, TX", 200, min_radius: 40)
+```ruby
+Venue.near("Austin, TX", 200, min_radius: 40)
+```
 
 To search within a rectangle (note that results will *not* include `distance` and `bearing` attributes):
 
-    sw_corner = [40.71, 100.23]
-    ne_corner = [36.12, 88.65]
-    Venue.within_bounding_box(sw_corner, ne_corner)
+```ruby
+sw_corner = [40.71, 100.23]
+ne_corner = [36.12, 88.65]
+Venue.within_bounding_box(sw_corner, ne_corner)
+```
 
 To search for objects near a certain point where each object has a different distance requirement (which is defined in the database), you can pass a column name for the radius:
 
-    Venue.near([40.71, 99.23], :effective_radius)
+```ruby
+Venue.near([40.71, 99.23], :effective_radius)
+```
 
 If you store multiple sets of coordinates for each object, you can specify latitude and longitude columns to use for a search:
 
-    Venue.near("Paris", 50, latitude: :secondary_latitude, longitude: :secondary_longitude)
+```ruby
+Venue.near("Paris", 50, latitude: :secondary_latitude, longitude: :secondary_longitude)
+```
 
 ### Distance and Bearing
 
@@ -452,9 +531,11 @@ Results are automatically sorted by distance from the search point, closest to f
 
 You can convert these to compass point names via provided method:
 
-    Geocoder::Calculations.compass_point(355) # => "N"
-    Geocoder::Calculations.compass_point(45)  # => "NE"
-    Geocoder::Calculations.compass_point(208) # => "SW"
+```ruby
+Geocoder::Calculations.compass_point(355) # => "N"
+Geocoder::Calculations.compass_point(45)  # => "NE"
+Geocoder::Calculations.compass_point(208) # => "SW"
+```
 
 _Note: when running queries on SQLite, `distance` and `bearing` are provided for consistency only. They are not very accurate._
 
@@ -466,13 +547,15 @@ Geospatial Calculations
 
 The `Geocoder::Calculations` module contains some useful methods:
 
-    # find the distance between two arbitrary points
-    Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655])
-     => 3619.77359999382 # in configured units (default miles)
+```ruby
+# find the distance between two arbitrary points
+Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655])
+ => 3619.77359999382 # in configured units (default miles)
 
-    # find the geographic center (aka center of gravity) of objects or points
-    Geocoder::Calculations.geographic_center([city1, city2, [40.22,-73.99], city4])
-     => [35.14968, -90.048929]
+# find the geographic center (aka center of gravity) of objects or points
+Geocoder::Calculations.geographic_center([city1, city2, [40.22,-73.99], city4])
+ => [35.14968, -90.048929]
+```
 
 See [the code](https://github.com/alexreisner/geocoder/blob/master/lib/geocoder/calculations.rb) for more!
 
@@ -482,19 +565,27 @@ Batch Geocoding
 
 If you have just added geocoding to an existing application with a lot of objects, you can use this Rake task to geocode them all:
 
-    rake geocode:all CLASS=YourModel
+```sh
+rake geocode:all CLASS=YourModel
+```
 
 If you need reverse geocoding instead, call the task with REVERSE=true:
 
-    rake geocode:all CLASS=YourModel REVERSE=true
+```sh
+rake geocode:all CLASS=YourModel REVERSE=true
+```
 
 In either case, it won't try to geocode objects that are already geocoded. The task will print warnings if you exceed the rate limit for your geocoding service. Some services enforce a per-second limit in addition to a per-day limit. To avoid exceeding the per-second limit, you can add a `SLEEP` option to pause between requests for a given amount of time. You can also load objects in batches to save memory, for example:
 
-    rake geocode:all CLASS=YourModel SLEEP=0.25 BATCH=100
+```sh
+rake geocode:all CLASS=YourModel SLEEP=0.25 BATCH=100
+```
 
 To avoid exceeding per-day limits you can add a `LIMIT` option. However, this will ignore the `BATCH` value, if provided.
 
-    rake geocode:all CLASS=YourModel LIMIT=1000
+```sh
+rake geocode:all CLASS=YourModel LIMIT=1000
+```
 
 
 Testing
@@ -502,42 +593,54 @@ Testing
 
 When writing tests for an app that uses Geocoder it may be useful to avoid network calls and have Geocoder return consistent, configurable results. To do this, configure the `:test` lookup and/or `:ip_lookup`
 
-    Geocoder.configure(lookup: :test, ip_lookup: :test)
+```ruby
+Geocoder.configure(lookup: :test, ip_lookup: :test)
+```
 
 Add stubs to define the results that will be returned:
 
-    Geocoder::Lookup::Test.add_stub(
-      "New York, NY", [
-        {
-          'coordinates'  => [40.7143528, -74.0059731],
-          'address'      => 'New York, NY, USA',
-          'state'        => 'New York',
-          'state_code'   => 'NY',
-          'country'      => 'United States',
-          'country_code' => 'US'
-        }
-      ]
-    )
+```ruby
+Geocoder::Lookup::Test.add_stub(
+  "New York, NY", [
+    {
+      'coordinates'  => [40.7143528, -74.0059731],
+      'address'      => 'New York, NY, USA',
+      'state'        => 'New York',
+      'state_code'   => 'NY',
+      'country'      => 'United States',
+      'country_code' => 'US'
+    }
+  ]
+)
+```
 
 With the above stub defined, any query for "New York, NY" will return the results array that follows. You can also set a default stub, to be returned when no other stub matches a given query:
 
-    Geocoder::Lookup::Test.set_default_stub(
-      [
-        {
-          'coordinates'  => [40.7143528, -74.0059731],
-          'address'      => 'New York, NY, USA',
-          'state'        => 'New York',
-          'state_code'   => 'NY',
-          'country'      => 'United States',
-          'country_code' => 'US'
-        }
-      ]
-    )
+```ruby
+Geocoder::Lookup::Test.set_default_stub(
+  [
+    {
+      'coordinates'  => [40.7143528, -74.0059731],
+      'address'      => 'New York, NY, USA',
+      'state'        => 'New York',
+      'state_code'   => 'NY',
+      'country'      => 'United States',
+      'country_code' => 'US'
+    }
+  ]
+)
+```
+
+You may also delete a single stub, or reset all stubs _including the default stub_:
+
+```ruby
+Geocoder::Lookup::Test.delete_stub('New York, NY')
+Geocoder::Lookup::Test.reset
+```
 
 Notes:
 
 - Keys must be strings (not symbols) when calling `add_stub` or `set_default_stub`. For example `'country' =>` not `:country =>`.
-- To clear stubs (e.g. prior to another spec), use `Geocoder::Lookup::Test.reset`. This will clear all stubs _including the default stub_.
 - The stubbed result objects returned by the Test lookup do not support all the methods real result objects do. If you need to test interaction with real results it may be better to use an external stubbing tool and something like WebMock or VCR to prevent network calls.
 
 
@@ -546,21 +649,27 @@ Error Handling
 
 By default Geocoder will rescue any exceptions raised by calls to a geocoding service and return an empty array. You can override this on a per-exception basis, and also have Geocoder raise its own exceptions for certain events (eg: API quota exceeded) by using the `:always_raise` option:
 
-    Geocoder.configure(always_raise: [SocketError, Timeout::Error])
+```ruby
+Geocoder.configure(always_raise: [SocketError, Timeout::Error])
+```
 
 You can also do this to raise all exceptions:
 
-    Geocoder.configure(always_raise: :all)
+```ruby
+Geocoder.configure(always_raise: :all)
+```
 
 The raise-able exceptions are:
 
-    SocketError
-    Timeout::Error
-    Geocoder::OverQueryLimitError
-    Geocoder::RequestDenied
-    Geocoder::InvalidRequest
-    Geocoder::InvalidApiKey
-    Geocoder::ServiceUnavailable
+```ruby
+SocketError
+Timeout::Error
+Geocoder::OverQueryLimitError
+Geocoder::RequestDenied
+Geocoder::InvalidRequest
+Geocoder::InvalidApiKey
+Geocoder::ServiceUnavailable
+```
 
 Note that only a few of the above exceptions are raised by any given lookup, so there's no guarantee if you configure Geocoder to raise `ServiceUnavailable` that it will actually be raised under those conditions (because most APIs don't return 503 when they should; you may get a `Timeout::Error` instead). Please see the source code for your particular lookup for details.
 
@@ -570,15 +679,17 @@ Command Line Interface
 
 When you install the Geocoder gem it adds a `geocode` command to your shell. You can search for a street address, IP address, postal code, coordinates, etc just like you can with the Geocoder.search method for example:
 
-    $ geocode 29.951,-90.081
-    Latitude:         29.952211
-    Longitude:        -90.080563
-    Full address:     1500 Sugar Bowl Dr, New Orleans, LA 70112, USA
-    City:             New Orleans
-    State/province:   Louisiana
-    Postal code:      70112
-    Country:          United States
-    Map:              http://maps.google.com/maps?q=29.952211,-90.080563
+```sh
+$ geocode 29.951,-90.081
+Latitude:         29.952211
+Longitude:        -90.080563
+Full address:     1500 Sugar Bowl Dr, New Orleans, LA 70112, USA
+City:             New Orleans
+State/province:   Louisiana
+Postal code:      70112
+Country:          United States
+Map:              http://maps.google.com/maps?q=29.952211,-90.080563
+```
 
 There are also a number of options for setting the geocoding API, key, and language, viewing the raw JSON response, and more. Please run `geocode -h` for details.
 
@@ -614,8 +725,10 @@ Troubleshooting
 
 If you get one of these errors:
 
-    uninitialized constant Geocoder::Model::Mongoid
-    uninitialized constant Geocoder::Model::Mongoid::Mongo
+```ruby
+uninitialized constant Geocoder::Model::Mongoid
+uninitialized constant Geocoder::Model::Mongoid::Mongo
+```
 
 you should check your Gemfile to make sure the Mongoid gem is listed _before_ Geocoder. If Mongoid isn't loaded when Geocoder is initialized, Geocoder will not load support for Mongoid.
 
@@ -634,19 +747,23 @@ If your application requires quick geocoding responses you will probably need to
 
 For IP address lookups in Rails applications, it is generally NOT a good idea to run `request.location` during a synchronous page load without understanding the speed/behavior of your configured lookup. If the lookup becomes slow, so will your website.
 
-For the most part, the speed of geocoding requests has little to do with the Geocoder gem. Please take the time to learn about your configured lookup (links to documentation are provided above) before posting performance-related issues.
+For the most part, the speed of geocoding requests has little to do with the Geocoder gem. Please take the time to learn about your configured lookup before posting performance-related issues.
 
 ### Unexpected Responses from Geocoding Services
 
 Take a look at the server's raw response. You can do this by getting the request URL in an app console:
 
-    Geocoder::Lookup.get(:nominatim).query_url(Geocoder::Query.new("..."))
+```ruby
+Geocoder::Lookup.get(:nominatim).query_url(Geocoder::Query.new("..."))
+```
 
 Replace `:nominatim` with the lookup you are using and replace `...` with the address you are trying to geocode. Then visit the returned URL in your web browser. Often the API will return an error message that helps you resolve the problem. If, after reading the raw response, you believe there is a problem with Geocoder, please post an issue and include both the URL and raw response body.
 
 You can also fetch the response in the console:
 
-    Geocoder::Lookup.get(:nominatim).send(:fetch_raw_data, Geocoder::Query.new("..."))
+```ruby
+Geocoder::Lookup.get(:nominatim).send(:fetch_raw_data, Geocoder::Query.new("..."))
+```
 
 
 Known Issues
@@ -662,14 +779,16 @@ You cannot use the `near` scope with another scope that provides an `includes` o
 
 Instead of using `includes` to reduce the number of database queries, try using `joins` with either the `:select` option or a call to `preload`. For example:
 
-    # Pass a :select option to the near scope to get the columns you want.
-    # Instead of City.near(...).includes(:venues), try:
-    City.near("Omaha, NE", 20, select: "cities.*, venues.*").joins(:venues)
+```ruby
+# Pass a :select option to the near scope to get the columns you want.
+# Instead of City.near(...).includes(:venues), try:
+City.near("Omaha, NE", 20, select: "cities.*, venues.*").joins(:venues)
 
-    # This preload call will normally trigger two queries regardless of the
-    # number of results; one query on hotels, and one query on administrators.
-    # Instead of Hotel.near(...).includes(:administrator), try:
-    Hotel.near("London, UK", 50).joins(:administrator).preload(:administrator)
+# This preload call will normally trigger two queries regardless of the
+# number of results; one query on hotels, and one query on administrators.
+# Instead of Hotel.near(...).includes(:administrator), try:
+Hotel.near("London, UK", 50).joins(:administrator).preload(:administrator)
+```
 
 If anyone has a more elegant solution to this problem I am very interested in seeing it.
 
@@ -678,29 +797,4 @@ If anyone has a more elegant solution to this problem I am very interested in se
 The `near` method will not look across the 180th meridian to find objects close to a given point. In practice this is rarely an issue outside of New Zealand and certain surrounding islands. This problem does not exist with the zero-meridian. The problem is due to a shortcoming of the Haversine formula which Geocoder uses to calculate distances.
 
 
-Reporting Issues
-----------------
-
-When reporting an issue, please list the version of Geocoder you are using and any relevant information about your application (Rails version, database type and version, etc). Please describe as specifically as you can what behavior you are seeing (eg: an error message? a nil return value?).
-
-Please DO NOT use GitHub issues to ask questions about how to use Geocoder. Sites like [StackOverflow](http://www.stackoverflow.com/) are a better forum for such discussions.
-
-
-Contributing
-------------
-
-Contributions are welcome via Github pull requests. If you are new to the project and looking for a way to get involved, try picking up an issue with a "beginner-task" label. Hints about what needs to be done are usually provided.
-
-For all contributions, please respect the following guidelines:
-
-* Each pull request should implement ONE feature or bugfix. If you want to add or fix more than one thing, submit more than one pull request.
-* Do not commit changes to files that are irrelevant to your feature or bugfix (eg: `.gitignore`).
-* Do not add dependencies on other gems.
-* Do not add unnecessary `require` statements which could cause LoadErrors on certain systems.
-* Remember: Geocoder needs to run outside of Rails. Don't assume things like ActiveSupport are available.
-* Be willing to accept criticism and work on improving your code; Geocoder is used by thousands of developers and care must be taken not to introduce bugs.
-* Be aware that the pull request review process is not immediate, and is generally proportional to the size of the pull request.
-* If your pull request is merged, please do not ask for an immediate release of the gem. There are many factors contributing to when releases occur (remember that they affect thousands of apps with Geocoder in their Gemfiles). If necessary, please install from the Github source until the next official release.
-
-
-Copyright (c) 2009-18 Alex Reisner, released under the MIT license.
+Copyright :copyright: 2009-2021 Alex Reisner, released under the MIT license.
diff --git a/bin/console b/bin/console
new file mode 100755
index 0000000..8a177d5
--- /dev/null
+++ b/bin/console
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+
+require 'bundler/setup'
+require 'geocoder'
+
+if File.exist?("api_keys.yml")
+  require 'yaml'
+  @api_keys = YAML.load(File.read("api_keys.yml"), symbolize_names: true)
+  Geocoder.configure(@api_keys)
+end
+
+require 'irb'
+IRB.start
diff --git a/debian/changelog b/debian/changelog
index b083519..fed70d2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+ruby-geocoder (1.8.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * Drop patch CVE-2020-7981.patch, present upstream.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 11 Jan 2023 04:07:18 -0000
+
 ruby-geocoder (1.5.1-3) unstable; urgency=medium
 
   * Add patch to fix CVE-2020-7981 (Closes: #949870)
diff --git a/debian/patches/CVE-2020-7981.patch b/debian/patches/CVE-2020-7981.patch
deleted file mode 100644
index a844667..0000000
--- a/debian/patches/CVE-2020-7981.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From dcdc3d8675411edce3965941a2ca7c441ca48613 Mon Sep 17 00:00:00 2001
-From: Alex Reisner <alex@alexreisner.com>
-Date: Thu, 23 Jan 2020 09:08:45 -0700
-Subject: [PATCH] Sanitize lat/lon for SQL query.
-
----
- lib/geocoder/sql.rb | 8 ++++----
- 1 file changed, 4 insertions(+), 4 deletions(-)
-
---- a/lib/geocoder/sql.rb
-+++ b/lib/geocoder/sql.rb
-@@ -44,13 +44,13 @@
-     end
- 
-     def within_bounding_box(sw_lat, sw_lng, ne_lat, ne_lng, lat_attr, lon_attr)
--      spans = "#{lat_attr} BETWEEN #{sw_lat} AND #{ne_lat} AND "
-+      spans = "#{lat_attr} BETWEEN #{sw_lat.to_f} AND #{ne_lat.to_f} AND "
-       # handle box that spans 180 longitude
-       if sw_lng.to_f > ne_lng.to_f
--        spans + "(#{lon_attr} BETWEEN #{sw_lng} AND 180 OR " +
--        "#{lon_attr} BETWEEN -180 AND #{ne_lng})"
-+        spans + "(#{lon_attr} BETWEEN #{sw_lng.to_f} AND 180 OR " +
-+        "#{lon_attr} BETWEEN -180 AND #{ne_lng.to_f})"
-       else
--        spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
-+        spans + "#{lon_attr} BETWEEN #{sw_lng.to_f} AND #{ne_lng.to_f}"
-       end
-     end
- 
diff --git a/debian/patches/series b/debian/patches/series
index 45067fd..e69de29 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +0,0 @@
-CVE-2020-7981.patch
diff --git a/examples/app_defined_lookup_services.rb b/examples/app_defined_lookup_services.rb
new file mode 100644
index 0000000..7875cee
--- /dev/null
+++ b/examples/app_defined_lookup_services.rb
@@ -0,0 +1,22 @@
+# To extend the Geocoder with additional lookups that come from the application,
+# not shipped with the gem, define a "child" lookup in your application, based on existing one.
+# This is required because the Geocoder::Configuration is a Singleton and stores one api key per lookup.
+
+# in app/libs/geocoder/lookup/my_preciousss.rb
+module Geocoder::Lookup
+  class MyPreciousss < Google
+  end
+end
+
+# Update Geocoder's street_services on initialize:
+# config/initializers/geocoder.rb
+Geocoder::Lookup.street_services << :my_preciousss
+
+# Override the configuration when necessary (e.g. provide separate Google API key for the account):
+Geocoder.configure(my_preciousss: { api_key: 'abcdef' })
+
+# Lastly, search using your custom lookup service/api keys
+Geocoder.search("Paris", lookup: :my_preciousss)
+
+# This is useful when we have groups of users in the application who use Google paid services
+# and we want to properly separate them and allow using individual API KEYS or timeouts.
diff --git a/examples/autoexpire_cache_dalli.rb b/examples/autoexpire_cache_dalli.rb
deleted file mode 100644
index 97b3773..0000000
--- a/examples/autoexpire_cache_dalli.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# This class implements a cache with simple delegation to the the Dalli Memcached client
-# https://github.com/mperham/dalli
-#
-# A TTL is set on initialization
-
-class AutoexpireCacheDalli
-  def initialize(store, ttl = 86400)
-    @store = store
-    @keys = 'GeocoderDalliClientKeys'
-    @ttl = ttl
-  end
-
-  def [](url)
-    res = @store.get(url)
-    res = YAML::load(res) if res.present?
-    res
-  end
-
-  def []=(url, value)
-    if value.nil?
-      del(url)
-    else
-      key_cache_add(url) if @store.add(url, YAML::dump(value), @ttl)
-    end
-    value
-  end
-
-  def keys
-    key_cache
-  end
-
-  def del(url)
-    key_cache_delete(url) if @store.delete(url)
-  end
-
-  private
-
-  def key_cache
-    the_keys = @store.get(@keys)
-    if the_keys.nil?
-      @store.add(@keys, YAML::dump([]))
-      []
-    else
-      YAML::load(the_keys)
-    end
-  end
-
-  def key_cache_add(key)
-    @store.replace(@keys, YAML::dump(key_cache << key))
-  end
-
-  def key_cache_delete(key)
-    tmp = key_cache
-    tmp.delete(key)
-    @store.replace(@keys, YAML::dump(tmp))
-  end
-end
-
-# Here Dalli is set up as on Heroku using the Memcachier gem.
-# https://devcenter.heroku.com/articles/memcachier#ruby
-# On other setups you might have to specify your Memcached server in Dalli::Client.new
-Geocoder.configure(:cache => AutoexpireCacheDalli.new(Dalli::Client.new))
diff --git a/examples/autoexpire_cache_redis.rb b/examples/autoexpire_cache_redis.rb
deleted file mode 100644
index 395a71e..0000000
--- a/examples/autoexpire_cache_redis.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# This class implements a cache with simple delegation to the Redis store, but
-# when it creates a key/value pair, it also sends an EXPIRE command with a TTL.
-# It should be fairly simple to do the same thing with Memcached.
-class AutoexpireCacheRedis
-  def initialize(store, ttl = 86400)
-    @store = store
-    @ttl = ttl
-  end
-
-  def [](url)
-    @store.get(url)
-  end
-
-  def []=(url, value)
-    @store.set(url, value)
-    @store.expire(url, @ttl)
-  end
-
-  def keys
-    @store.keys
-  end
-
-  def del(url)
-    @store.del(url)
-  end
-end
-
-Geocoder.configure(:cache => AutoexpireCacheRedis.new(Redis.new))
diff --git a/geocoder.gemspec b/geocoder.gemspec
index 1b90b41..ef35110 100644
--- a/geocoder.gemspec
+++ b/geocoder.gemspec
@@ -2,25 +2,24 @@
 # This file has been automatically generated by gem2tgz #
 #########################################################
 # -*- encoding: utf-8 -*-
-# stub: geocoder 1.5.1 ruby lib
+# stub: geocoder 1.8.1 ruby lib
 
 Gem::Specification.new do |s|
   s.name = "geocoder".freeze
-  s.version = "1.5.1"
+  s.version = "1.8.1"
 
   s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
   s.metadata = { "changelog_uri" => "https://github.com/alexreisner/geocoder/blob/master/CHANGELOG.md", "source_code_uri" => "https://github.com/alexreisner/geocoder" } if s.respond_to? :metadata=
   s.require_paths = ["lib".freeze]
   s.authors = ["Alex Reisner".freeze]
-  s.date = "2019-01-23"
-  s.description = "Provides object geocoding (by street or IP address), reverse geocoding (coordinates to street address), distance queries for ActiveRecord and Mongoid, result caching, and more. Designed for Rails but works with Sinatra and other Rack frameworks too.".freeze
+  s.date = "2022-09-23"
+  s.description = "Object geocoding (by street or IP address), reverse geocoding (coordinates to street address), distance queries for ActiveRecord and Mongoid, result caching, and more. Designed for Rails but works with Sinatra and other Rack frameworks too.".freeze
   s.email = ["alex@alexreisner.com".freeze]
   s.executables = ["geocode".freeze]
-  s.files = ["CHANGELOG.md".freeze, "LICENSE".freeze, "README.md".freeze, "bin/geocode".freeze, "examples/autoexpire_cache_dalli.rb".freeze, "examples/autoexpire_cache_redis.rb".freeze, "examples/cache_bypass.rb".freeze, "examples/reverse_geocode_job.rb".freeze, "lib/generators/geocoder/config/config_generator.rb".freeze, "lib/generators/geocoder/config/templates/initializer.rb".freeze, "lib/generators/geocoder/maxmind/geolite_city_generator.rb".freeze, "lib/generators/geocoder/maxmind/geolite_country_generator.rb".freeze, "lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb".freeze, "lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb".freeze, "lib/generators/geocoder/migration_version.rb".freeze, "lib/geocoder.rb".freeze, "lib/geocoder/cache.rb".freeze, "lib/geocoder/calculations.rb".freeze, "lib/geocoder/cli.rb".freeze, "lib/geocoder/configuration.rb".freeze, "lib/geocoder/configuration_hash.rb".freeze, "lib/geocoder/esri_token.rb".freeze, "lib/geocoder/exceptions.rb".freeze, "lib/geocoder/ip_address.rb".freeze, "lib/geocoder/kernel_logger.rb".freeze, "lib/geocoder/logger.rb".freeze, "lib/geocoder/lookup.rb".freeze, "lib/geocoder/lookups/amap.rb".freeze, "lib/geocoder/lookups/baidu.rb".freeze, "lib/geocoder/lookups/baidu_ip.rb".freeze, "lib/geocoder/lookups/ban_data_gouv_fr.rb".freeze, "lib/geocoder/lookups/base.rb".freeze, "lib/geocoder/lookups/bing.rb".freeze, "lib/geocoder/lookups/db_ip_com.rb".freeze, "lib/geocoder/lookups/dstk.rb".freeze, "lib/geocoder/lookups/esri.rb".freeze, "lib/geocoder/lookups/freegeoip.rb".freeze, "lib/geocoder/lookups/geocoder_ca.rb".freeze, "lib/geocoder/lookups/geocoder_us.rb".freeze, "lib/geocoder/lookups/geocodio.rb".freeze, "lib/geocoder/lookups/geoip2.rb".freeze, "lib/geocoder/lookups/geoportail_lu.rb".freeze, "lib/geocoder/lookups/google.rb".freeze, "lib/geocoder/lookups/google_places_details.rb".freeze, "lib/geocoder/lookups/google_places_search.rb".freeze, "lib/geocoder/lookups/google_premier.rb".freeze, "lib/geocoder/lookups/here.rb".freeze, "lib/geocoder/lookups/ip2location.rb".freeze, "lib/geocoder/lookups/ipapi_com.rb".freeze, "lib/geocoder/lookups/ipdata_co.rb".freeze, "lib/geocoder/lookups/ipinfo_io.rb".freeze, "lib/geocoder/lookups/ipstack.rb".freeze, "lib/geocoder/lookups/latlon.rb".freeze, "lib/geocoder/lookups/location_iq.rb".freeze, "lib/geocoder/lookups/mapbox.rb".freeze, "lib/geocoder/lookups/mapquest.rb".freeze, "lib/geocoder/lookups/maxmind.rb".freeze, "lib/geocoder/lookups/maxmind_geoip2.rb".freeze, "lib/geocoder/lookups/maxmind_local.rb".freeze, "lib/geocoder/lookups/nominatim.rb".freeze, "lib/geocoder/lookups/opencagedata.rb".freeze, "lib/geocoder/lookups/pelias.rb".freeze, "lib/geocoder/lookups/pickpoint.rb".freeze, "lib/geocoder/lookups/pointpin.rb".freeze, "lib/geocoder/lookups/postcode_anywhere_uk.rb".freeze, "lib/geocoder/lookups/postcodes_io.rb".freeze, "lib/geocoder/lookups/smarty_streets.rb".freeze, "lib/geocoder/lookups/telize.rb".freeze, "lib/geocoder/lookups/tencent.rb".freeze, "lib/geocoder/lookups/test.rb".freeze, "lib/geocoder/lookups/yandex.rb".freeze, "lib/geocoder/models/active_record.rb".freeze, "lib/geocoder/models/base.rb".freeze, "lib/geocoder/models/mongo_base.rb".freeze, "lib/geocoder/models/mongo_mapper.rb".freeze, "lib/geocoder/models/mongoid.rb".freeze, "lib/geocoder/query.rb".freeze, "lib/geocoder/railtie.rb".freeze, "lib/geocoder/request.rb".freeze, "lib/geocoder/results/amap.rb".freeze, "lib/geocoder/results/baidu.rb".freeze, "lib/geocoder/results/baidu_ip.rb".freeze, "lib/geocoder/results/ban_data_gouv_fr.rb".freeze, "lib/geocoder/results/base.rb".freeze, "lib/geocoder/results/bing.rb".freeze, "lib/geocoder/results/db_ip_com.rb".freeze, "lib/geocoder/results/dstk.rb".freeze, "lib/geocoder/results/esri.rb".freeze, "lib/geocoder/results/freegeoip.rb".freeze, "lib/geocoder/results/geocoder_ca.rb".freeze, "lib/geocoder/results/geocoder_us.rb".freeze, "lib/geocoder/results/geocodio.rb".freeze, "lib/geocoder/results/geoip2.rb".freeze, "lib/geocoder/results/geoportail_lu.rb".freeze, "lib/geocoder/results/google.rb".freeze, "lib/geocoder/results/google_places_details.rb".freeze, "lib/geocoder/results/google_places_search.rb".freeze, "lib/geocoder/results/google_premier.rb".freeze, "lib/geocoder/results/here.rb".freeze, "lib/geocoder/results/ip2location.rb".freeze, "lib/geocoder/results/ipapi_com.rb".freeze, "lib/geocoder/results/ipdata_co.rb".freeze, "lib/geocoder/results/ipinfo_io.rb".freeze, "lib/geocoder/results/ipstack.rb".freeze, "lib/geocoder/results/latlon.rb".freeze, "lib/geocoder/results/location_iq.rb".freeze, "lib/geocoder/results/mapbox.rb".freeze, "lib/geocoder/results/mapquest.rb".freeze, "lib/geocoder/results/maxmind.rb".freeze, "lib/geocoder/results/maxmind_geoip2.rb".freeze, "lib/geocoder/results/maxmind_local.rb".freeze, "lib/geocoder/results/nominatim.rb".freeze, "lib/geocoder/results/opencagedata.rb".freeze, "lib/geocoder/results/pelias.rb".freeze, "lib/geocoder/results/pickpoint.rb".freeze, "lib/geocoder/results/pointpin.rb".freeze, "lib/geocoder/results/postcode_anywhere_uk.rb".freeze, "lib/geocoder/results/postcodes_io.rb".freeze, "lib/geocoder/results/smarty_streets.rb".freeze, "lib/geocoder/results/telize.rb".freeze, "lib/geocoder/results/tencent.rb".freeze, "lib/geocoder/results/test.rb".freeze, "lib/geocoder/results/yandex.rb".freeze, "lib/geocoder/sql.rb".freeze, "lib/geocoder/stores/active_record.rb".freeze, "lib/geocoder/stores/base.rb".freeze, "lib/geocoder/stores/mongo_base.rb".freeze, "lib/geocoder/stores/mongo_mapper.rb".freeze, "lib/geocoder/stores/mongoid.rb".freeze, "lib/geocoder/version.rb".freeze, "lib/hash_recursive_merge.rb".freeze, "lib/maxmind_database.rb".freeze, "lib/tasks/geocoder.rake".freeze, "lib/tasks/maxmind.rake".freeze]
+  s.files = ["CHANGELOG.md".freeze, "LICENSE".freeze, "README.md".freeze, "bin/console".freeze, "bin/geocode".freeze, "examples/app_defined_lookup_services.rb".freeze, "examples/cache_bypass.rb".freeze, "examples/reverse_geocode_job.rb".freeze, "lib/easting_northing.rb".freeze, "lib/generators/geocoder/config/config_generator.rb".freeze, "lib/generators/geocoder/config/templates/initializer.rb".freeze, "lib/generators/geocoder/maxmind/geolite_city_generator.rb".freeze, "lib/generators/geocoder/maxmind/geolite_country_generator.rb".freeze, "lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb".freeze, "lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb".freeze, "lib/generators/geocoder/migration_version.rb".freeze, "lib/geocoder.rb".freeze, "lib/geocoder/cache.rb".freeze, "lib/geocoder/cache_stores/base.rb".freeze, "lib/geocoder/cache_stores/generic.rb".freeze, "lib/geocoder/cache_stores/redis.rb".freeze, "lib/geocoder/calculations.rb".freeze, "lib/geocoder/cli.rb".freeze, "lib/geocoder/configuration.rb".freeze, "lib/geocoder/configuration_hash.rb".freeze, "lib/geocoder/esri_token.rb".freeze, "lib/geocoder/exceptions.rb".freeze, "lib/geocoder/ip_address.rb".freeze, "lib/geocoder/kernel_logger.rb".freeze, "lib/geocoder/logger.rb".freeze, "lib/geocoder/lookup.rb".freeze, "lib/geocoder/lookups/abstract_api.rb".freeze, "lib/geocoder/lookups/amap.rb".freeze, "lib/geocoder/lookups/amazon_location_service.rb".freeze, "lib/geocoder/lookups/baidu.rb".freeze, "lib/geocoder/lookups/baidu_ip.rb".freeze, "lib/geocoder/lookups/ban_data_gouv_fr.rb".freeze, "lib/geocoder/lookups/base.rb".freeze, "lib/geocoder/lookups/bing.rb".freeze, "lib/geocoder/lookups/db_ip_com.rb".freeze, "lib/geocoder/lookups/dstk.rb".freeze, "lib/geocoder/lookups/esri.rb".freeze, "lib/geocoder/lookups/freegeoip.rb".freeze, "lib/geocoder/lookups/geoapify.rb".freeze, "lib/geocoder/lookups/geocoder_ca.rb".freeze, "lib/geocoder/lookups/geocodio.rb".freeze, "lib/geocoder/lookups/geoip2.rb".freeze, "lib/geocoder/lookups/geoportail_lu.rb".freeze, "lib/geocoder/lookups/google.rb".freeze, "lib/geocoder/lookups/google_places_details.rb".freeze, "lib/geocoder/lookups/google_places_search.rb".freeze, "lib/geocoder/lookups/google_premier.rb".freeze, "lib/geocoder/lookups/here.rb".freeze, "lib/geocoder/lookups/ip2location.rb".freeze, "lib/geocoder/lookups/ipapi_com.rb".freeze, "lib/geocoder/lookups/ipbase.rb".freeze, "lib/geocoder/lookups/ipdata_co.rb".freeze, "lib/geocoder/lookups/ipgeolocation.rb".freeze, "lib/geocoder/lookups/ipinfo_io.rb".freeze, "lib/geocoder/lookups/ipqualityscore.rb".freeze, "lib/geocoder/lookups/ipregistry.rb".freeze, "lib/geocoder/lookups/ipstack.rb".freeze, "lib/geocoder/lookups/latlon.rb".freeze, "lib/geocoder/lookups/location_iq.rb".freeze, "lib/geocoder/lookups/mapbox.rb".freeze, "lib/geocoder/lookups/mapquest.rb".freeze, "lib/geocoder/lookups/maxmind.rb".freeze, "lib/geocoder/lookups/maxmind_geoip2.rb".freeze, "lib/geocoder/lookups/maxmind_local.rb".freeze, "lib/geocoder/lookups/melissa_street.rb".freeze, "lib/geocoder/lookups/nationaal_georegister_nl.rb".freeze, "lib/geocoder/lookups/nominatim.rb".freeze, "lib/geocoder/lookups/opencagedata.rb".freeze, "lib/geocoder/lookups/osmnames.rb".freeze, "lib/geocoder/lookups/pelias.rb".freeze, "lib/geocoder/lookups/photon.rb".freeze, "lib/geocoder/lookups/pickpoint.rb".freeze, "lib/geocoder/lookups/pointpin.rb".freeze, "lib/geocoder/lookups/postcode_anywhere_uk.rb".freeze, "lib/geocoder/lookups/postcodes_io.rb".freeze, "lib/geocoder/lookups/smarty_streets.rb".freeze, "lib/geocoder/lookups/telize.rb".freeze, "lib/geocoder/lookups/tencent.rb".freeze, "lib/geocoder/lookups/test.rb".freeze, "lib/geocoder/lookups/twogis.rb".freeze, "lib/geocoder/lookups/uk_ordnance_survey_names.rb".freeze, "lib/geocoder/lookups/yandex.rb".freeze, "lib/geocoder/models/active_record.rb".freeze, "lib/geocoder/models/base.rb".freeze, "lib/geocoder/models/mongo_base.rb".freeze, "lib/geocoder/models/mongo_mapper.rb".freeze, "lib/geocoder/models/mongoid.rb".freeze, "lib/geocoder/query.rb".freeze, "lib/geocoder/railtie.rb".freeze, "lib/geocoder/request.rb".freeze, "lib/geocoder/results/abstract_api.rb".freeze, "lib/geocoder/results/amap.rb".freeze, "lib/geocoder/results/amazon_location_service.rb".freeze, "lib/geocoder/results/baidu.rb".freeze, "lib/geocoder/results/baidu_ip.rb".freeze, "lib/geocoder/results/ban_data_gouv_fr.rb".freeze, "lib/geocoder/results/base.rb".freeze, "lib/geocoder/results/bing.rb".freeze, "lib/geocoder/results/db_ip_com.rb".freeze, "lib/geocoder/results/dstk.rb".freeze, "lib/geocoder/results/esri.rb".freeze, "lib/geocoder/results/freegeoip.rb".freeze, "lib/geocoder/results/geoapify.rb".freeze, "lib/geocoder/results/geocoder_ca.rb".freeze, "lib/geocoder/results/geocodio.rb".freeze, "lib/geocoder/results/geoip2.rb".freeze, "lib/geocoder/results/geoportail_lu.rb".freeze, "lib/geocoder/results/google.rb".freeze, "lib/geocoder/results/google_places_details.rb".freeze, "lib/geocoder/results/google_places_search.rb".freeze, "lib/geocoder/results/google_premier.rb".freeze, "lib/geocoder/results/here.rb".freeze, "lib/geocoder/results/ip2location.rb".freeze, "lib/geocoder/results/ipapi_com.rb".freeze, "lib/geocoder/results/ipbase.rb".freeze, "lib/geocoder/results/ipdata_co.rb".freeze, "lib/geocoder/results/ipgeolocation.rb".freeze, "lib/geocoder/results/ipinfo_io.rb".freeze, "lib/geocoder/results/ipqualityscore.rb".freeze, "lib/geocoder/results/ipregistry.rb".freeze, "lib/geocoder/results/ipstack.rb".freeze, "lib/geocoder/results/latlon.rb".freeze, "lib/geocoder/results/location_iq.rb".freeze, "lib/geocoder/results/mapbox.rb".freeze, "lib/geocoder/results/mapquest.rb".freeze, "lib/geocoder/results/maxmind.rb".freeze, "lib/geocoder/results/maxmind_geoip2.rb".freeze, "lib/geocoder/results/maxmind_local.rb".freeze, "lib/geocoder/results/melissa_street.rb".freeze, "lib/geocoder/results/nationaal_georegister_nl.rb".freeze, "lib/geocoder/results/nominatim.rb".freeze, "lib/geocoder/results/opencagedata.rb".freeze, "lib/geocoder/results/osmnames.rb".freeze, "lib/geocoder/results/pelias.rb".freeze, "lib/geocoder/results/photon.rb".freeze, "lib/geocoder/results/pickpoint.rb".freeze, "lib/geocoder/results/pointpin.rb".freeze, "lib/geocoder/results/postcode_anywhere_uk.rb".freeze, "lib/geocoder/results/postcodes_io.rb".freeze, "lib/geocoder/results/smarty_streets.rb".freeze, "lib/geocoder/results/telize.rb".freeze, "lib/geocoder/results/tencent.rb".freeze, "lib/geocoder/results/test.rb".freeze, "lib/geocoder/results/twogis.rb".freeze, "lib/geocoder/results/uk_ordnance_survey_names.rb".freeze, "lib/geocoder/results/yandex.rb".freeze, "lib/geocoder/sql.rb".freeze, "lib/geocoder/stores/active_record.rb".freeze, "lib/geocoder/stores/base.rb".freeze, "lib/geocoder/stores/mongo_base.rb".freeze, "lib/geocoder/stores/mongo_mapper.rb".freeze, "lib/geocoder/stores/mongoid.rb".freeze, "lib/geocoder/util.rb".freeze, "lib/geocoder/version.rb".freeze, "lib/maxmind_database.rb".freeze, "lib/tasks/geocoder.rake".freeze, "lib/tasks/maxmind.rake".freeze]
   s.homepage = "http://www.rubygeocoder.com".freeze
   s.licenses = ["MIT".freeze]
-  s.post_install_message = "\n\nNOTE: Geocoder's default IP address lookup has changed from FreeGeoIP.net to IPInfo.io. If you explicitly specify :freegeoip in your configuration you must choose a different IP lookup before FreeGeoIP is discontinued on July 1, 2018. If you do not explicitly specify :freegeoip you do not need to change anything.\n\n".freeze
   s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze)
-  s.rubygems_version = "2.5.2.1".freeze
+  s.rubygems_version = "3.2.5".freeze
   s.summary = "Complete geocoding solution for Ruby.".freeze
 end
diff --git a/lib/easting_northing.rb b/lib/easting_northing.rb
new file mode 100644
index 0000000..0b9d2a9
--- /dev/null
+++ b/lib/easting_northing.rb
@@ -0,0 +1,171 @@
+module Geocoder
+  class EastingNorthing
+    attr_reader :easting, :northing, :lat_lng
+
+    def initialize(opts)
+      @easting = opts[:easting]
+      @northing = opts[:northing]
+
+      @lat_lng = to_WGS84(to_osgb_36)
+    end
+
+    private
+
+    def to_osgb_36
+      osgb_fo  = 0.9996012717
+      northing0 = -100_000.0
+      easting0 = 400_000.0
+      phi0 = deg_to_rad(49.0)
+      lambda0 = deg_to_rad(-2.0)
+      a = 6_377_563.396
+      b = 6_356_256.909
+      eSquared = ((a * a) - (b * b)) / (a * a)
+      phi = 0.0
+      lambda = 0.0
+      n = (a - b) / (a + b)
+      m = 0.0
+      phiPrime = ((northing - northing0) / (a * osgb_fo)) + phi0
+
+      while (northing - northing0 - m) >= 0.001
+        m =
+          (b * osgb_fo)\
+            * (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n))\
+              * (phiPrime - phi0))\
+              - (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))\
+                * Math.sin(phiPrime - phi0)\
+                * Math.cos(phiPrime + phi0))\
+              + ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))\
+                * Math.sin(2.0 * (phiPrime - phi0))\
+                * Math.cos(2.0 * (phiPrime + phi0)))\
+              - (((35.0 / 24.0) * n * n * n)\
+                * Math.sin(3.0 * (phiPrime - phi0))\
+                * Math.cos(3.0 * (phiPrime + phi0))))
+
+        phiPrime += (northing - northing0 - m) / (a * osgb_fo)
+      end
+
+      v = a * osgb_fo * ((1.0 - eSquared * sin_pow_2(phiPrime))**-0.5)
+      rho =
+        a\
+          * osgb_fo\
+          * (1.0 - eSquared)\
+          * ((1.0 - eSquared * sin_pow_2(phiPrime))**-1.5)
+      etaSquared = (v / rho) - 1.0
+      vii = Math.tan(phiPrime) / (2 * rho * v)
+      viii =
+        (Math.tan(phiPrime) / (24.0 * rho * (v**3.0)))\
+          * (5.0\
+            + (3.0 * tan_pow_2(phiPrime))\
+            + etaSquared\
+            - (9.0 * tan_pow_2(phiPrime) * etaSquared))
+      ix =
+        (Math.tan(phiPrime) / (720.0 * rho * (v**5.0)))\
+          * (61.0\
+            + (90.0 * tan_pow_2(phiPrime))\
+            + (45.0 * tan_pow_2(phiPrime) * tan_pow_2(phiPrime)))
+      x = sec(phiPrime) / v
+      xi =
+        (sec(phiPrime) / (6.0 * v * v * v))\
+          * ((v / rho) + (2 * tan_pow_2(phiPrime)))
+      xiii =
+        (sec(phiPrime) / (120.0 * (v**5.0)))\
+          * (5.0\
+            + (28.0 * tan_pow_2(phiPrime))\
+            + (24.0 * tan_pow_2(phiPrime) * tan_pow_2(phiPrime)))
+      xiia =
+        (sec(phiPrime) / (5040.0 * (v**7.0)))\
+          * (61.0\
+            + (662.0 * tan_pow_2(phiPrime))\
+            + (1320.0 * tan_pow_2(phiPrime) * tan_pow_2(phiPrime))\
+            + (720.0\
+              * tan_pow_2(phiPrime)\
+              * tan_pow_2(phiPrime)\
+              * tan_pow_2(phiPrime)))
+      phi =
+        phiPrime\
+          - (vii * ((easting - easting0)**2.0))\
+          + (viii * ((easting - easting0)**4.0))\
+          - (ix * ((easting - easting0)**6.0))
+      lambda =
+        lambda0\
+          + (x * (easting - easting0))\
+          - (xi * ((easting - easting0)**3.0))\
+          + (xiii * ((easting - easting0)**5.0))\
+          - (xiia * ((easting - easting0)**7.0))
+
+      [rad_to_deg(phi), rad_to_deg(lambda)]
+    end
+
+    def to_WGS84(latlng)
+      latitude = latlng[0]
+      longitude = latlng[1]
+
+      a = 6_377_563.396
+      b = 6_356_256.909
+      eSquared = ((a * a) - (b * b)) / (a * a)
+
+      phi = deg_to_rad(latitude)
+      lambda = deg_to_rad(longitude)
+      v = a / Math.sqrt(1 - eSquared * sin_pow_2(phi))
+      h = 0
+      x = (v + h) * Math.cos(phi) * Math.cos(lambda)
+      y = (v + h) * Math.cos(phi) * Math.sin(lambda)
+      z = ((1 - eSquared) * v + h) * Math.sin(phi)
+
+      tx = 446.448
+      ty = -124.157
+      tz = 542.060
+
+      s  = -0.0000204894
+      rx = deg_to_rad(0.00004172222)
+      ry = deg_to_rad(0.00006861111)
+      rz = deg_to_rad(0.00023391666)
+
+      xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z)
+      yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z)
+      zB = tz + (-ry * x) + (rx * y) + (z * (1 + s))
+
+      a = 6_378_137.000
+      b = 6_356_752.3141
+      eSquared = ((a * a) - (b * b)) / (a * a)
+
+      lambdaB = rad_to_deg(Math.atan(yB / xB))
+      p = Math.sqrt((xB * xB) + (yB * yB))
+      phiN = Math.atan(zB / (p * (1 - eSquared)))
+
+      (1..10).each do |_i|
+        v = a / Math.sqrt(1 - eSquared * sin_pow_2(phiN))
+        phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p)
+        phiN = phiN1
+      end
+
+      phiB = rad_to_deg(phiN)
+
+      [phiB, lambdaB]
+    end
+
+    def deg_to_rad(degrees)
+      degrees / 180.0 * Math::PI
+    end
+
+    def rad_to_deg(r)
+      (r / Math::PI) * 180
+    end
+
+    def sin_pow_2(x)
+      Math.sin(x) * Math.sin(x)
+    end
+
+    def cos_pow_2(x)
+      Math.cos(x) * Math.cos(x)
+    end
+
+    def tan_pow_2(x)
+      Math.tan(x) * Math.tan(x)
+    end
+
+    def sec(x)
+      1.0 / Math.cos(x)
+    end
+  end
+end
diff --git a/lib/generators/geocoder/config/templates/initializer.rb b/lib/generators/geocoder/config/templates/initializer.rb
index 0e64173..b653516 100644
--- a/lib/generators/geocoder/config/templates/initializer.rb
+++ b/lib/generators/geocoder/config/templates/initializer.rb
@@ -9,7 +9,6 @@ Geocoder.configure(
   # https_proxy: nil,           # HTTPS proxy server (user:pass@host:port)
   # api_key: nil,               # API key for geocoding service
   # cache: nil,                 # cache object (must respond to #[], #[]=, and #del)
-  # cache_prefix: 'geocoder:',  # prefix (string) to use for all cache keys
 
   # Exceptions that should not be rescued by default
   # (if you want to implement custom error handling);
@@ -19,4 +18,10 @@ Geocoder.configure(
   # Calculation options
   # units: :mi,                 # :km for kilometers or :mi for miles
   # distances: :linear          # :spherical or :linear
+
+  # Cache configuration
+  # cache_options: {
+  #   expiration: 2.days,
+  #   prefix: 'geocoder:'
+  # }
 )
diff --git a/lib/geocoder/cache.rb b/lib/geocoder/cache.rb
index 9eb032a..e4eec67 100644
--- a/lib/geocoder/cache.rb
+++ b/lib/geocoder/cache.rb
@@ -1,37 +1,29 @@
+Dir["#{__dir__}/cache_stores/*.rb"].each {|file| require file }
+
 module Geocoder
   class Cache
 
-    def initialize(store, prefix)
-      @store = store
-      @prefix = prefix
+    def initialize(store, config)
+      @class = (Object.const_get("Geocoder::CacheStore::#{store.class}") rescue Geocoder::CacheStore::Generic)
+      @store_service = @class.new(store, config)
     end
 
     ##
     # Read from the Cache.
     #
     def [](url)
-      interpret case
-        when store.respond_to?(:[])
-          store[key_for(url)]
-        when store.respond_to?(:get)
-          store.get key_for(url)
-        when store.respond_to?(:read)
-          store.read key_for(url)
-      end
+      interpret store_service.read(url)
+    rescue => e
+      Geocoder.log(:warn, "Geocoder cache read error: #{e}")
     end
 
     ##
     # Write to the Cache.
     #
     def []=(url, value)
-      case
-        when store.respond_to?(:[]=)
-          store[key_for(url)] = value
-        when store.respond_to?(:set)
-          store.set key_for(url), value
-        when store.respond_to?(:write)
-          store.write key_for(url), value
-      end
+      store_service.write(url, value)
+    rescue => e
+      Geocoder.log(:warn, "Geocoder cache write error: #{e}")
     end
 
     ##
@@ -40,7 +32,7 @@ module Geocoder
     #
     def expire(url)
       if url == :all
-        if store.respond_to?(:keys)
+        if store_service.respond_to?(:keys)
           urls.each{ |u| expire(u) }
         else
           raise(NoMethodError, "The Geocoder cache store must implement `#keys` for `expire(:all)` to work")
@@ -53,29 +45,21 @@ module Geocoder
 
     private # ----------------------------------------------------------------
 
-    def prefix; @prefix; end
-    def store; @store; end
-
-    ##
-    # Cache key for a given URL.
-    #
-    def key_for(url)
-      [prefix, url].join
-    end
+    def store_service; @store_service; end
 
     ##
     # Array of keys with the currently configured prefix
     # that have non-nil values.
     #
     def keys
-      store.keys.select{ |k| k.match(/^#{prefix}/) and self[k] }
+      store_service.keys
     end
 
     ##
     # Array of cached URLs.
     #
     def urls
-      keys.map{ |k| k[/^#{prefix}(.*)/, 1] }
+      store_service.urls
     end
 
     ##
@@ -87,8 +71,7 @@ module Geocoder
     end
 
     def expire_single_url(url)
-      key = key_for(url)
-      store.respond_to?(:del) ? store.del(key) : store.delete(key)
+      store_service.remove(url)
     end
   end
 end
diff --git a/lib/geocoder/cache_stores/base.rb b/lib/geocoder/cache_stores/base.rb
new file mode 100644
index 0000000..14b253b
--- /dev/null
+++ b/lib/geocoder/cache_stores/base.rb
@@ -0,0 +1,40 @@
+module Geocoder::CacheStore
+  class Base
+    def initialize(store, options)
+      @store = store
+      @config = options
+      @prefix = config[:prefix]
+    end
+
+    ##
+    # Array of keys with the currently configured prefix
+    # that have non-nil values.
+    def keys
+      store.keys.select { |k| k.match(/^#{prefix}/) and self[k] }
+    end
+
+    ##
+    # Array of cached URLs.
+    #
+    def urls
+      keys
+    end
+
+    protected # ----------------------------------------------------------------
+
+    def prefix; @prefix; end
+    def store; @store; end
+    def config; @config; end
+
+    ##
+    # Cache key for a given URL.
+    #
+    def key_for(url)
+      if url.match(/^#{prefix}/)
+        url
+      else
+        [prefix, url].join
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/geocoder/cache_stores/generic.rb b/lib/geocoder/cache_stores/generic.rb
new file mode 100644
index 0000000..791b3cf
--- /dev/null
+++ b/lib/geocoder/cache_stores/generic.rb
@@ -0,0 +1,35 @@
+require 'geocoder/cache_stores/base'
+
+module Geocoder::CacheStore
+  class Generic < Base
+    def write(url, value)
+      case
+      when store.respond_to?(:[]=)
+        store[key_for(url)] = value
+      when store.respond_to?(:set)
+        store.set key_for(url), value
+      when store.respond_to?(:write)
+        store.write key_for(url), value
+      end
+    end
+
+    def read(url)
+      case
+      when store.respond_to?(:[])
+        store[key_for(url)]
+      when store.respond_to?(:get)
+        store.get key_for(url)
+      when store.respond_to?(:read)
+        store.read key_for(url)
+      end
+    end
+
+    def keys
+      store.keys
+    end
+
+    def remove(key)
+      store.delete(key)
+    end
+  end
+end
diff --git a/lib/geocoder/cache_stores/redis.rb b/lib/geocoder/cache_stores/redis.rb
new file mode 100644
index 0000000..39c3798
--- /dev/null
+++ b/lib/geocoder/cache_stores/redis.rb
@@ -0,0 +1,34 @@
+require 'geocoder/cache_stores/base'
+
+module Geocoder::CacheStore
+  class Redis < Base
+    def initialize(store, options)
+      super
+      @expiration = options[:expiration]
+    end
+
+    def write(url, value, expire = @expiration)
+      if expire.present?
+        store.set key_for(url), value, ex: expire
+      else
+        store.set key_for(url), value
+      end
+    end
+
+    def read(url)
+      store.get key_for(url)
+    end
+
+    def keys
+      store.keys("#{prefix}*")
+    end
+
+    def remove(key)
+      store.del(key)
+    end
+
+    private # ----------------------------------------------------------------
+
+    def expire; @expiration; end
+  end
+end
diff --git a/lib/geocoder/configuration.rb b/lib/geocoder/configuration.rb
index 8e097a2..d352200 100644
--- a/lib/geocoder/configuration.rb
+++ b/lib/geocoder/configuration.rb
@@ -1,5 +1,6 @@
 require 'singleton'
 require 'geocoder/configuration_hash'
+require 'geocoder/util'
 
 module Geocoder
 
@@ -54,19 +55,20 @@ module Geocoder
       :lookup,
       :ip_lookup,
       :language,
+      :host,
       :http_headers,
       :use_https,
       :http_proxy,
       :https_proxy,
       :api_key,
       :cache,
-      :cache_prefix,
       :always_raise,
       :units,
       :distances,
       :basic_auth,
       :logger,
-      :kernel_logger_level
+      :kernel_logger_level,
+      :cache_options
     ]
 
     attr_accessor :data
@@ -75,6 +77,10 @@ module Geocoder
       instance.set_defaults
     end
 
+    def self.initialize
+      instance.send(:initialize)
+    end
+
     OPTIONS.each do |o|
       define_method o do
         @data[o]
@@ -85,7 +91,7 @@ module Geocoder
     end
 
     def configure(options)
-      @data.rmerge!(options)
+      Util.recursive_hash_merge(@data, options)
     end
 
     def initialize # :nodoc
@@ -105,8 +111,6 @@ module Geocoder
       @data[:http_proxy]   = nil         # HTTP proxy server (user:pass@host:port)
       @data[:https_proxy]  = nil         # HTTPS proxy server (user:pass@host:port)
       @data[:api_key]      = nil         # API key for geocoding service
-      @data[:cache]        = nil         # cache object (must respond to #[], #[]=, and #keys)
-      @data[:cache_prefix] = "geocoder:" # prefix (string) to use for all cache keys
       @data[:basic_auth]   = {}          # user and password for basic auth ({:user => "user", :password => "password"})
       @data[:logger]       = :kernel     # :kernel or Logger instance
       @data[:kernel_logger_level] = ::Logger::WARN # log level, if kernel logger is used
@@ -119,6 +123,16 @@ module Geocoder
       # calculation options
       @data[:units]     = :mi      # :mi or :km
       @data[:distances] = :linear  # :linear or :spherical
+
+      # Set the default values for the caching mechanism
+      # By default, the cache keys will not expire as IP addresses and phyiscal
+      # addresses will rarely change.
+      @data[:cache]        = nil   # cache object (must respond to #[], #[]=, and optionally #keys)
+      @data[:cache_prefix] = nil   # - DEPRECATED - prefix (string) to use for all cache keys
+      @data[:cache_options] = {
+        prefix: 'geocoder:',
+        expiration: nil
+      }
     end
 
     instance_eval(OPTIONS.map do |option|
diff --git a/lib/geocoder/configuration_hash.rb b/lib/geocoder/configuration_hash.rb
index 70569c3..19d21ba 100644
--- a/lib/geocoder/configuration_hash.rb
+++ b/lib/geocoder/configuration_hash.rb
@@ -1,11 +1,11 @@
-require 'hash_recursive_merge'
-
 module Geocoder
   class ConfigurationHash < Hash
-    include HashRecursiveMerge
-
     def method_missing(meth, *args, &block)
       has_key?(meth) ? self[meth] : super
     end
+
+    def respond_to_missing?(meth, include_private = false)
+      has_key?(meth) || super
+    end
   end
 end
diff --git a/lib/geocoder/ip_address.rb b/lib/geocoder/ip_address.rb
index c6858cc..cdce2ce 100644
--- a/lib/geocoder/ip_address.rb
+++ b/lib/geocoder/ip_address.rb
@@ -7,6 +7,12 @@ module Geocoder
       '192.168.0.0/16',
     ].map { |ip| IPAddr.new(ip) }.freeze
 
+    def initialize(ip)
+      ip = ip.to_string if ip.is_a?(IPAddr)
+
+      super(ip)
+    end
+
     def internal?
       loopback? || private?
     end
@@ -20,7 +26,8 @@ module Geocoder
     end
 
     def valid?
-      !!((self =~ Resolv::IPv4::Regex) || (self =~ Resolv::IPv6::Regex))
+      ip = self[/(?<=\[)(.*?)(?=\])/] || self
+      !!((ip =~ Resolv::IPv4::Regex) || (ip =~ Resolv::IPv6::Regex))
     end
   end
 end
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index 485e28c..7c23dab 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -18,6 +18,14 @@ module Geocoder
       all_services - [:test]
     end
 
+    ##
+    # Array of valid Lookup service names, excluding any that do not build their own HTTP requests.
+    # For example, Amazon Location Service uses the AWS gem, not HTTP REST requests, to fetch data.
+    #
+    def all_services_with_http_requests
+      all_services_except_test - [:amazon_location_service]
+    end
+
     ##
     # All street address lookup services, default first.
     #
@@ -32,11 +40,12 @@ module Geocoder
         :google_places_search,
         :bing,
         :geocoder_ca,
-        :geocoder_us,
         :yandex,
+        :nationaal_georegister_nl,
         :nominatim,
         :mapbox,
         :mapquest,
+        :uk_ordnance_survey_names,
         :opencagedata,
         :pelias,
         :pickpoint,
@@ -51,7 +60,13 @@ module Geocoder
         :ban_data_gouv_fr,
         :test,
         :latlon,
-        :amap
+        :amap,
+        :osmnames,
+        :melissa_street,
+        :amazon_location_service,
+        :geoapify,
+        :photon,
+        :twogis
       ]
     end
 
@@ -61,6 +76,7 @@ module Geocoder
     def ip_services
       @ip_services ||= [
         :baidu_ip,
+        :abstract_api,
         :freegeoip,
         :geoip2,
         :maxmind,
@@ -69,11 +85,15 @@ module Geocoder
         :pointpin,
         :maxmind_geoip2,
         :ipinfo_io,
+        :ipregistry,
         :ipapi_com,
         :ipdata_co,
         :db_ip_com,
         :ipstack,
-        :ip2location
+        :ip2location,
+        :ipgeolocation,
+        :ipqualityscore,
+        :ipbase
       ]
     end
 
@@ -99,8 +119,7 @@ module Geocoder
     def spawn(name)
       if all_services.include?(name)
         name = name.to_s
-        require "geocoder/lookups/#{name}"
-        Geocoder::Lookup.const_get(classify_name(name)).new
+        instantiate_lookup(name)
       else
         valids = all_services.map(&:inspect).join(", ")
         raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
@@ -114,5 +133,18 @@ module Geocoder
     def classify_name(filename)
       filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
     end
+
+    ##
+    # Safely instantiate Lookup
+    #
+    def instantiate_lookup(name)
+      class_name = classify_name(name)
+      begin
+        Geocoder::Lookup.const_get(class_name, inherit=false)
+      rescue NameError
+        require "geocoder/lookups/#{name}"
+      end
+      Geocoder::Lookup.const_get(class_name).new
+    end
   end
 end
diff --git a/lib/geocoder/lookups/abstract_api.rb b/lib/geocoder/lookups/abstract_api.rb
new file mode 100644
index 0000000..f75baeb
--- /dev/null
+++ b/lib/geocoder/lookups/abstract_api.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+require 'geocoder/lookups/base'
+require 'geocoder/results/abstract_api'
+
+module Geocoder::Lookup
+  class AbstractApi < Base
+
+    def name
+      "Abstract API"
+    end
+
+    def required_api_key_parts
+      ['api_key']
+    end
+
+    def supported_protocols
+      [:https]
+    end
+
+    private # ---------------------------------------------------------------
+
+    def base_query_url(query)
+      "#{protocol}://ipgeolocation.abstractapi.com/v1/?"
+    end
+
+    def query_url_params(query)
+      params = {api_key: configuration.api_key}
+
+      ip_address = query.sanitized_text
+      if ip_address.is_a?(String) && ip_address.length > 0
+        params[:ip_address] = ip_address
+      end
+
+      params.merge(super)
+    end
+
+    def results(query, reverse = false)
+      if doc = fetch_data(query)
+        [doc]
+      else
+        []
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/amap.rb b/lib/geocoder/lookups/amap.rb
index ff2ac18..57d8d4a 100644
--- a/lib/geocoder/lookups/amap.rb
+++ b/lib/geocoder/lookups/amap.rb
@@ -32,10 +32,10 @@ module Geocoder::Lookup
         return doc['geocodes'] unless doc['geocodes'].blank?
       when ['0', 'INVALID_USER_KEY']
         raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
-          warn("#{self.name} Geocoding API error: invalid api key.")
+          Geocoder.log(:warn, "#{self.name} Geocoding API error: invalid api key.")
       else
         raise_error(Geocoder::Error, "server error.") ||
-          warn("#{self.name} Geocoding API error: server error - [#{doc['info']}]")
+          Geocoder.log(:warn, "#{self.name} Geocoding API error: server error - [#{doc['info']}]")
       end
       return []
     end
diff --git a/lib/geocoder/lookups/amazon_location_service.rb b/lib/geocoder/lookups/amazon_location_service.rb
new file mode 100644
index 0000000..a60e51c
--- /dev/null
+++ b/lib/geocoder/lookups/amazon_location_service.rb
@@ -0,0 +1,54 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/amazon_location_service'
+
+module Geocoder::Lookup
+  class AmazonLocationService < Base
+    def results(query)
+      params = query.options.dup
+
+      # index_name is required
+      # Aws::ParamValidator raises ArgumentError on missing required keys
+      params.merge!(index_name: configuration[:index_name])
+
+      # Aws::ParamValidator raises ArgumentError on unexpected keys
+      params.delete(:lookup) 
+      
+      resp = if query.reverse_geocode?
+        client.search_place_index_for_position(params.merge(position: query.coordinates.reverse))
+      else
+        client.search_place_index_for_text(params.merge(text: query.text))
+      end
+      
+      resp.results.map(&:place)
+    end
+
+    private
+
+    def client
+      return @client if @client
+      require_sdk
+      keys = configuration.api_key
+      if keys
+        @client = Aws::LocationService::Client.new(
+          access_key_id: keys[:access_key_id],
+          secret_access_key: keys[:secret_access_key],
+        )
+      else
+        @client = Aws::LocationService::Client.new
+      end
+    end
+
+    def require_sdk
+      begin
+        require 'aws-sdk-locationservice'
+      rescue LoadError
+        raise_error(Geocoder::ConfigurationError) ||
+          Geocoder.log(
+            :error,
+            "Couldn't load the Amazon Location Service SDK. " +
+            "Install it with: gem install aws-sdk-locationservice -v '~> 1.4'"
+          )
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/ban_data_gouv_fr.rb b/lib/geocoder/lookups/ban_data_gouv_fr.rb
index b4b2e81..56f0e87 100644
--- a/lib/geocoder/lookups/ban_data_gouv_fr.rb
+++ b/lib/geocoder/lookups/ban_data_gouv_fr.rb
@@ -22,7 +22,7 @@ module Geocoder::Lookup
     end
 
     def any_result?(doc)
-      doc['features'].any?
+      doc['features'] and doc['features'].any?
     end
 
     def results(query)
@@ -86,6 +86,12 @@ module Geocoder::Lookup
       unless (citycode = query.options[:citycode]).nil? || !code_param_is_valid?(citycode)
         params[:citycode] = citycode.to_s
       end
+      unless (lat = query.options[:lat]).nil? || !latitude_is_valid?(lat)
+        params[:lat] = lat
+      end
+      unless (lon = query.options[:lon]).nil? || !longitude_is_valid?(lon)
+        params[:lon] = lon
+      end
       params
     end
 
@@ -126,5 +132,12 @@ module Geocoder::Lookup
       (1..99999).include?(param.to_i)
     end
 
+    def latitude_is_valid?(param)
+      param.to_f <= 90 && param.to_f >= -90
+    end
+
+    def longitude_is_valid?(param)
+      param.to_f <= 180 && param.to_f >= -180
+    end
   end
 end
diff --git a/lib/geocoder/lookups/base.rb b/lib/geocoder/lookups/base.rb
index 8169960..f0c1137 100644
--- a/lib/geocoder/lookups/base.rb
+++ b/lib/geocoder/lookups/base.rb
@@ -84,7 +84,8 @@ module Geocoder
       #
       def cache
         if @cache.nil? and store = configuration.cache
-          @cache = Cache.new(store, configuration.cache_prefix)
+          cache_options = configuration.cache_options
+          @cache = Cache.new(store, cache_options)
         end
         @cache
       end
@@ -197,6 +198,8 @@ module Geocoder
         raise_error(err) or Geocoder.log(:warn, "Geocoding API connection cannot be established.")
       rescue Errno::ECONNREFUSED => err
         raise_error(err) or Geocoder.log(:warn, "Geocoding API connection refused.")
+      rescue Geocoder::NetworkError => err
+        raise_error(err) or Geocoder.log(:warn, "Geocoding API connection is either unreacheable or reset by the peer")
       rescue Timeout::Error => err
         raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " +
           "(use Geocoder.configure(:timeout => ...) to set limit).")
diff --git a/lib/geocoder/lookups/bing.rb b/lib/geocoder/lookups/bing.rb
index c2d0edc..7ab4945 100644
--- a/lib/geocoder/lookups/bing.rb
+++ b/lib/geocoder/lookups/bing.rb
@@ -54,7 +54,7 @@ module Geocoder::Lookup
     def query_url_params(query)
       {
         key: configuration.api_key,
-        language: (query.language || configuration.language)
+        culture: (query.language || configuration.language)
       }.merge(super)
     end
 
diff --git a/lib/geocoder/lookups/esri.rb b/lib/geocoder/lookups/esri.rb
index 6f6cf0e..c12deb0 100644
--- a/lib/geocoder/lookups/esri.rb
+++ b/lib/geocoder/lookups/esri.rb
@@ -9,6 +9,10 @@ module Geocoder::Lookup
       "Esri"
     end
 
+      def supported_protocols
+        [:https]
+      end
+
     private # ---------------------------------------------------------------
 
     def base_query_url(query)
@@ -47,6 +51,8 @@ module Geocoder::Lookup
         params[:forStorage] = for_storage_value
       end
       params[:sourceCountry] = configuration[:source_country] if configuration[:source_country]
+      params[:preferredLabelValues] = configuration[:preferred_label_values] if configuration[:preferred_label_values]
+
       params.merge(super)
     end
 
diff --git a/lib/geocoder/lookups/freegeoip.rb b/lib/geocoder/lookups/freegeoip.rb
index 6b551a2..b712949 100644
--- a/lib/geocoder/lookups/freegeoip.rb
+++ b/lib/geocoder/lookups/freegeoip.rb
@@ -10,21 +10,23 @@ module Geocoder::Lookup
 
     def supported_protocols
       if configuration[:host]
-        [:http, :https]
+        [:https]
       else
         # use https for default host
         [:https]
       end
     end
 
-    def query_url(query)
-      "#{protocol}://#{host}/json/#{query.sanitized_text}"
-    end
-
     private # ---------------------------------------------------------------
 
-    def cache_key(query)
-      query_url(query)
+    def base_query_url(query)
+      "#{protocol}://#{host}/json/#{query.sanitized_text}?"
+    end
+
+    def query_url_params(query)
+      {
+        :apikey => configuration.api_key
+      }.merge(super)
     end
 
     def parse_raw_data(raw_data)
@@ -44,8 +46,8 @@ module Geocoder::Lookup
         "city"         => "",
         "region_code"  => "",
         "region_name"  => "",
-        "metrocode"    => "",
-        "zipcode"      => "",
+        "metro_code"    => "",
+        "zip_code"      => "",
         "latitude"     => "0",
         "longitude"    => "0",
         "country_name" => "Reserved",
@@ -54,7 +56,7 @@ module Geocoder::Lookup
     end
 
     def host
-      configuration[:host] || "freegeoip.net"
+      configuration[:host] || "freegeoip.app"
     end
   end
 end
diff --git a/lib/geocoder/lookups/geoapify.rb b/lib/geocoder/lookups/geoapify.rb
new file mode 100644
index 0000000..c9d74e7
--- /dev/null
+++ b/lib/geocoder/lookups/geoapify.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'geocoder/lookups/base'
+require 'geocoder/results/geoapify'
+
+module Geocoder
+  module Lookup
+    # https://apidocs.geoapify.com/docs/geocoding/api
+    class Geoapify < Base
+      def name
+        'Geoapify'
+      end
+
+      def required_api_key_parts
+        ['api_key']
+      end
+
+      def supported_protocols
+        [:https]
+      end
+
+      private
+
+      def base_query_url(query)
+        method = if query.reverse_geocode?
+          'reverse'
+        elsif query.options[:autocomplete]
+          'autocomplete'
+        else
+          'search'
+        end
+        "https://api.geoapify.com/v1/geocode/#{method}?"
+      end
+
+      def results(query)
+        return [] unless (doc = fetch_data(query))
+
+        # The rest of the status codes should be already handled by the default
+        # functionality as the API returns correct HTTP response codes in most
+        # cases. There may be some unhandled cases still (such as over query
+        # limit reached) but there is not enough documentation to cover them.
+        case doc['statusCode']
+        when 500
+          raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, doc['message'])
+        end
+
+        return [] unless doc['type'] == 'FeatureCollection'
+        return [] unless doc['features'] || doc['features'].present?
+
+        doc['features']
+      end
+
+      def query_url_params(query)
+        lang = query.language || configuration.language
+        params = { apiKey: configuration.api_key, lang: lang, limit: query.options[:limit] }
+
+        if query.reverse_geocode?
+          params.merge!(query_url_params_reverse(query))
+        else
+          params.merge!(query_url_params_coordinates(query))
+        end
+
+        params.merge!(super)
+      end
+
+      def query_url_params_coordinates(query)
+        { text: query.sanitized_text }
+      end
+
+      def query_url_params_reverse(query)
+        {
+          lat: query.coordinates[0],
+          lon: query.coordinates[1]
+        }
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/geocoder_us.rb b/lib/geocoder/lookups/geocoder_us.rb
deleted file mode 100644
index cc9869c..0000000
--- a/lib/geocoder/lookups/geocoder_us.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'geocoder/lookups/base'
-require "geocoder/results/geocoder_us"
-
-module Geocoder::Lookup
-  class GeocoderUs < Base
-
-    def name
-      "Geocoder.us"
-    end
-
-    def supported_protocols
-      [:http]
-    end
-
-    private # ----------------------------------------------------------------
-
-    def base_query_url(query)
-      base_query_url_with_optional_key(configuration.api_key)
-    end
-
-    def cache_key(query)
-      base_query_url_with_optional_key(nil) + url_query_string(query)
-    end
-
-    def base_query_url_with_optional_key(key = nil)
-      base = "#{protocol}://"
-      if configuration.api_key
-        base << "#{configuration.api_key}@"
-      end
-      base + "geocoder.us/member/service/csv/geocode?"
-    end
-
-    def results(query)
-      return [] unless doc = fetch_data(query)
-      if doc[0].to_s =~ /^(\d+)\:/
-        return []
-      else
-        return [doc.size == 5 ? ((doc[0..1] << nil) + doc[2..4]) : doc]
-      end
-    end
-
-    def query_url_params(query)
-      (query.text =~ /^\d{5}(?:-\d{4})?$/ ? {:zip => query} : {:address => query.sanitized_text}).merge(super)
-    end
-
-    def parse_raw_data(raw_data)
-      raw_data.chomp.split(',')
-    end
-  end
-end
-
diff --git a/lib/geocoder/lookups/geocodio.rb b/lib/geocoder/lookups/geocodio.rb
index 14e2655..58a0629 100644
--- a/lib/geocoder/lookups/geocodio.rb
+++ b/lib/geocoder/lookups/geocodio.rb
@@ -29,7 +29,7 @@ module Geocoder::Lookup
 
     def base_query_url(query)
       path = query.reverse_geocode? ? "reverse" : "geocode"
-      "#{protocol}://api.geocod.io/v1.3/#{path}?"
+      "#{protocol}://api.geocod.io/v1.6/#{path}?"
     end
 
     def query_url_params(query)
diff --git a/lib/geocoder/lookups/geoip2.rb b/lib/geocoder/lookups/geoip2.rb
index de647ad..f198b25 100644
--- a/lib/geocoder/lookups/geoip2.rb
+++ b/lib/geocoder/lookups/geoip2.rb
@@ -37,6 +37,10 @@ module Geocoder
       def results(query)
         return [] unless configuration[:file]
 
+        if @mmdb.respond_to?(:local_ip_alias) && !configuration[:local_ip_alias].nil?
+          @mmdb.local_ip_alias = configuration[:local_ip_alias]
+        end
+
         result = @mmdb.lookup(query.to_s)
         result.nil? ? [] : [result]
       end
diff --git a/lib/geocoder/lookups/geoportail_lu.rb b/lib/geocoder/lookups/geoportail_lu.rb
index bb35a0e..6c4154a 100644
--- a/lib/geocoder/lookups/geoportail_lu.rb
+++ b/lib/geocoder/lookups/geoportail_lu.rb
@@ -56,7 +56,7 @@ module Geocoder
         else
           result = []
           raise_error(Geocoder::Error) ||
-              warn("Geportail.lu Geocoding API error")
+              Geocoder.log(:warn, "Geportail.lu Geocoding API error")
         end
         result
       end
diff --git a/lib/geocoder/lookups/google.rb b/lib/geocoder/lookups/google.rb
index b209f8d..81df164 100644
--- a/lib/geocoder/lookups/google.rb
+++ b/lib/geocoder/lookups/google.rb
@@ -44,10 +44,15 @@ module Geocoder::Lookup
       super(response) and ['OK', 'ZERO_RESULTS'].include?(status)
     end
 
+    def result_root_attr
+      'results'
+    end
+
     def results(query)
       return [] unless doc = fetch_data(query)
-      case doc['status']; when "OK" # OK status implies >0 results
-        return doc['results']
+      case doc['status']
+      when "OK" # OK status implies >0 results
+        return doc[result_root_attr]
       when "OVER_QUERY_LIMIT"
         raise_error(Geocoder::OverQueryLimitError) ||
           Geocoder.log(:warn, "#{name} API error: over query limit.")
diff --git a/lib/geocoder/lookups/google_places_details.rb b/lib/geocoder/lookups/google_places_details.rb
index 5bc1b17..c96a3c0 100644
--- a/lib/geocoder/lookups/google_places_details.rb
+++ b/lib/geocoder/lookups/google_places_details.rb
@@ -22,26 +22,40 @@ module Geocoder
         "#{protocol}://maps.googleapis.com/maps/api/place/details/json?"
       end
 
+      def result_root_attr
+        'result'
+      end
+
       def results(query)
-        return [] unless doc = fetch_data(query)
-
-        case doc["status"]
-        when "OK"
-          return [doc["result"]]
-        when "OVER_QUERY_LIMIT"
-          raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Google Places Details API error: over query limit.")
-        when "REQUEST_DENIED"
-          raise_error(Geocoder::RequestDenied) || Geocoder.log(:warn, "Google Places Details API error: request denied.")
-        when "INVALID_REQUEST"
-          raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, "Google Places Details API error: invalid request.")
+        result = super(query)
+        return [result] unless result.is_a? Array
+
+        result
+      end
+
+      def fields(query)
+        if query.options.has_key?(:fields)
+          return format_fields(query.options[:fields])
         end
 
-        []
+        if configuration.has_key?(:fields)
+          return format_fields(configuration[:fields])
+        end
+
+        nil  # use Google Places defaults
+      end
+
+      def format_fields(*fields)
+        flattened = fields.flatten.compact
+        return if flattened.empty?
+
+        flattened.join(',')
       end
 
       def query_url_google_params(query)
         {
           placeid: query.text,
+          fields: fields(query),
           language: query.language || configuration.language
         }
       end
diff --git a/lib/geocoder/lookups/google_places_search.rb b/lib/geocoder/lookups/google_places_search.rb
index 2461033..8b4e0ee 100644
--- a/lib/geocoder/lookups/google_places_search.rb
+++ b/lib/geocoder/lookups/google_places_search.rb
@@ -18,16 +18,59 @@ module Geocoder
 
       private
 
+      def result_root_attr
+        'candidates'
+      end
+
       def base_query_url(query)
-        "#{protocol}://maps.googleapis.com/maps/api/place/textsearch/json?"
+        "#{protocol}://maps.googleapis.com/maps/api/place/findplacefromtext/json?"
       end
 
       def query_url_google_params(query)
         {
-          query: query.text,
+          input: query.text,
+          inputtype: 'textquery',
+          fields: fields(query),
+          locationbias: locationbias(query),
           language: query.language || configuration.language
         }
       end
+
+      def fields(query)
+        if query.options.has_key?(:fields)
+          return format_fields(query.options[:fields])
+        end
+
+        if configuration.has_key?(:fields)
+          return format_fields(configuration[:fields])
+        end
+
+        default_fields
+      end
+
+      def default_fields
+        legacy = %w[id reference]
+        basic = %w[business_status formatted_address geometry icon name 
+          photos place_id plus_code types]
+        contact = %w[opening_hours]
+        atmosphere = %W[price_level rating user_ratings_total]
+        format_fields(legacy, basic, contact, atmosphere)
+      end
+
+      def format_fields(*fields)
+        flattened = fields.flatten.compact
+        return if flattened.empty?
+
+        flattened.join(',')
+      end
+
+      def locationbias(query)
+        if query.options.has_key?(:locationbias)
+          query.options[:locationbias]
+        else
+          configuration[:locationbias]
+        end
+      end
     end
   end
 end
diff --git a/lib/geocoder/lookups/google_premier.rb b/lib/geocoder/lookups/google_premier.rb
index c985bd8..d2e9642 100644
--- a/lib/geocoder/lookups/google_premier.rb
+++ b/lib/geocoder/lookups/google_premier.rb
@@ -21,6 +21,10 @@ module Geocoder::Lookup
 
     private # ---------------------------------------------------------------
 
+    def result_root_attr
+      'results'
+    end
+
     def cache_key(query)
       "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + hash_to_query(cache_key_params(query))
     end
diff --git a/lib/geocoder/lookups/here.rb b/lib/geocoder/lookups/here.rb
index e302ef6..0a830c5 100644
--- a/lib/geocoder/lookups/here.rb
+++ b/lib/geocoder/lookups/here.rb
@@ -9,69 +9,65 @@ module Geocoder::Lookup
     end
 
     def required_api_key_parts
-      ["app_id", "app_code"]
+      ['api_key']
+    end
+
+    def supported_protocols
+      [:https]
     end
 
     private # ---------------------------------------------------------------
 
     def base_query_url(query)
-      "#{protocol}://#{if query.reverse_geocode? then 'reverse.' end}geocoder.api.here.com/6.2/#{if query.reverse_geocode? then 'reverse' end}geocode.json?"
+      service = query.reverse_geocode? ? "revgeocode" : "geocode"
+
+      "#{protocol}://#{service}.search.hereapi.com/v1/#{service}?"
     end
 
     def results(query)
-      return [] unless doc = fetch_data(query)
-      return [] unless doc['Response'] && doc['Response']['View']
-      if r=doc['Response']['View']
-        return [] if r.nil? || !r.is_a?(Array) || r.empty?
-        return r.first['Result']
+      unless configuration.api_key.is_a?(String)
+        api_key_not_string!
+        return []
       end
-      []
+      return [] unless doc = fetch_data(query)
+      return [] if doc["items"].nil?
+
+      doc["items"]
     end
 
     def query_url_here_options(query, reverse_geocode)
       options = {
-        gen: 9,
-        app_id: api_key,
-        app_code: api_code,
-        language: (query.language || configuration.language)
+        apiKey: configuration.api_key,
+        lang: (query.language || configuration.language)
       }
-      if reverse_geocode
-        options[:mode] = :retrieveAddresses
-        return options
-      end
+      return options if reverse_geocode
 
       unless (country = query.options[:country]).nil?
-        options[:country] = country
+        options[:in] = "countryCode:#{country}"
       end
 
-      unless (mapview = query.options[:bounds]).nil?
-        options[:mapview] = mapview.map{ |point| "%f,%f" % point }.join(';')
-      end
       options
     end
 
     def query_url_params(query)
       if query.reverse_geocode?
         super.merge(query_url_here_options(query, true)).merge(
-          prox: query.sanitized_text
+          at: query.sanitized_text
         )
       else
         super.merge(query_url_here_options(query, false)).merge(
-          searchtext: query.sanitized_text
+          q: query.sanitized_text
         )
       end
     end
 
-    def api_key
-      if (a = configuration.api_key)
-        return a.first if a.is_a?(Array)
-      end
-    end
+    def api_key_not_string!
+      msg = <<~MSG
+        API key for HERE Geocoding and Search API should be a string.
+        For more info on how to obtain it, please see https://developer.here.com/documentation/identity-access-management/dev_guide/topics/plat-using-apikeys.html
+      MSG
 
-    def api_code
-      if (a = configuration.api_key)
-        return a.last if a.is_a?(Array)
-      end
+      raise_error(Geocoder::ConfigurationError, msg) || Geocoder.log(:warn, msg)
     end
   end
 end
diff --git a/lib/geocoder/lookups/ip2location.rb b/lib/geocoder/lookups/ip2location.rb
index 3e29a79..82de1e0 100644
--- a/lib/geocoder/lookups/ip2location.rb
+++ b/lib/geocoder/lookups/ip2location.rb
@@ -8,6 +8,10 @@ module Geocoder::Lookup
       "IP2LocationApi"
     end
 
+    def required_api_key_parts
+      ['key']
+    end
+
     def supported_protocols
       [:http, :https]
     end
@@ -15,15 +19,15 @@ module Geocoder::Lookup
     private # ----------------------------------------------------------------
 
     def base_query_url(query)
-      "#{protocol}://api.ip2location.com/?"
+      "#{protocol}://api.ip2location.com/v2/?"
     end
 
     def query_url_params(query)
-      {
-        key: configuration.api_key ? configuration.api_key : "demo",
-        format: "json",
-        ip: query.sanitized_text
-      }.merge(super)
+      super.merge(
+        key: configuration.api_key,
+        ip: query.sanitized_text,
+        package: configuration[:package],
+      )
     end
 
     def results(query)
@@ -63,13 +67,5 @@ module Geocoder::Lookup
       }
     end
 
-    def query_url_params(query)
-      params = super
-      if configuration.has_key?(:package)
-        params.merge!(package: configuration[:package])
-      end
-      params
-    end
-
   end
 end
diff --git a/lib/geocoder/lookups/ipbase.rb b/lib/geocoder/lookups/ipbase.rb
new file mode 100644
index 0000000..a6cfd95
--- /dev/null
+++ b/lib/geocoder/lookups/ipbase.rb
@@ -0,0 +1,49 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/ipbase'
+
+module Geocoder::Lookup
+  class Ipbase < Base
+
+    def name
+      "ipbase.com"
+    end
+
+    def supported_protocols
+      [:https]
+    end
+
+    private # ---------------------------------------------------------------
+
+    def base_query_url(query)
+      "https://api.ipbase.com/v2/info?"
+    end
+
+    def query_url_params(query)
+      {
+        :ip => query.sanitized_text,
+        :apikey => configuration.api_key
+      }
+    end
+
+    def results(query)
+      # don't look up a loopback or private address, just return the stored result
+      return [reserved_result(query.text)] if query.internal_ip_address?
+      doc = fetch_data(query) || {}
+      doc.fetch("data", {})["location"] ? [doc] : []
+    end
+
+    def reserved_result(ip)
+      {
+        "data" => {
+          "ip" => ip,
+          "location" => {
+            "city" => { "name" => "" },
+            "country" => { "alpha2" => "RD", "name" => "Reserved" },
+            "region" => { "alpha2" => "", "name" => "" },
+            "zip" => ""
+          }
+        }
+      }
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/ipdata_co.rb b/lib/geocoder/lookups/ipdata_co.rb
index d061696..de11b5a 100644
--- a/lib/geocoder/lookups/ipdata_co.rb
+++ b/lib/geocoder/lookups/ipdata_co.rb
@@ -47,7 +47,7 @@ module Geocoder::Lookup
     end
 
     def host
-      "api.ipdata.co"
+      configuration[:host] || "api.ipdata.co"
     end
 
     def check_response_for_errors!(response)
diff --git a/lib/geocoder/lookups/ipgeolocation.rb b/lib/geocoder/lookups/ipgeolocation.rb
new file mode 100644
index 0000000..adfc655
--- /dev/null
+++ b/lib/geocoder/lookups/ipgeolocation.rb
@@ -0,0 +1,51 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/ipgeolocation'
+
+
+module Geocoder::Lookup
+  class Ipgeolocation < Base
+
+    ERROR_CODES = {
+      400 => Geocoder::RequestDenied, # subscription is paused
+      401 => Geocoder::InvalidApiKey, # missing/invalid API key
+      403 => Geocoder::InvalidRequest, # invalid IP address
+      404 => Geocoder::InvalidRequest, # not found
+      423 => Geocoder::InvalidRequest # bogon/reserved IP address
+    }
+    ERROR_CODES.default = Geocoder::Error
+
+    def name
+      "Ipgeolocation"
+    end
+
+    def supported_protocols
+      [:https]
+    end
+
+    private # ----------------------------------------------------------------
+
+    def base_query_url(query)
+      "#{protocol}://api.ipgeolocation.io/ipgeo?"
+    end
+    def query_url_params(query)
+      {
+          ip: query.sanitized_text,
+          apiKey: configuration.api_key
+      }.merge(super)
+    end
+
+    def results(query)
+      # don't look up a loopback or private address, just return the stored result
+      return [reserved_result(query.text)] if query.internal_ip_address?
+      [fetch_data(query)]
+    end
+
+    def reserved_result(ip)
+      {
+          "ip"           => ip,
+          "country_name" => "Reserved",
+          "country_code2" => "RD"
+      }
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/ipqualityscore.rb b/lib/geocoder/lookups/ipqualityscore.rb
new file mode 100644
index 0000000..134b4d4
--- /dev/null
+++ b/lib/geocoder/lookups/ipqualityscore.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+
+require 'geocoder/lookups/base'
+require 'geocoder/results/ipqualityscore'
+
+module Geocoder::Lookup
+  class Ipqualityscore < Base
+
+    def name
+      "IPQualityScore"
+    end
+
+    def required_api_key_parts
+      ['api_key']
+    end
+
+    private # ---------------------------------------------------------------
+
+    def base_query_url(query)
+      "#{protocol}://ipqualityscore.com/api/json/ip/#{configuration.api_key}/#{query.sanitized_text}?"
+    end
+
+    def valid_response?(response)
+      if (json = parse_json(response.body))
+        success = json['success']
+      end
+      super && success == true
+    end
+
+    def results(query, reverse = false)
+      return [] unless doc = fetch_data(query)
+
+      return [doc] if doc['success']
+
+      case doc['message']
+      when /invalid (.*) key/i
+        raise_error Geocoder::InvalidApiKey ||
+                    Geocoder.log(:warn, "#{name} API error: invalid api key.")
+      when /insufficient credits/, /exceeded your request quota/
+        raise_error Geocoder::OverQueryLimitError ||
+                    Geocoder.log(:warn, "#{name} API error: query limit exceeded.")
+      when /invalid (.*) address/i
+        raise_error Geocoder::InvalidRequest ||
+                    Geocoder.log(:warn, "#{name} API error: invalid request.")
+      end
+
+      [doc]
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/ipregistry.rb b/lib/geocoder/lookups/ipregistry.rb
new file mode 100644
index 0000000..a0591a4
--- /dev/null
+++ b/lib/geocoder/lookups/ipregistry.rb
@@ -0,0 +1,68 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/ipregistry'
+
+module Geocoder::Lookup
+  class Ipregistry < Base
+
+    ERROR_CODES = {
+      400 => Geocoder::InvalidRequest,
+      401 => Geocoder::InvalidRequest,
+      402 => Geocoder::OverQueryLimitError,
+      403 => Geocoder::InvalidApiKey,
+      451 => Geocoder::RequestDenied,
+      500 => Geocoder::Error
+    }
+    ERROR_CODES.default = Geocoder::Error
+
+    def name
+      "Ipregistry"
+    end
+
+    def supported_protocols
+      [:https, :http]
+    end
+
+    private
+
+    def base_query_url(query)
+      "#{protocol}://#{host}/#{query.sanitized_text}?"
+    end
+
+    def cache_key(query)
+      query_url(query)
+    end
+
+    def host
+      configuration[:host] || "api.ipregistry.co"
+    end
+
+    def query_url_params(query)
+      {
+        key: configuration.api_key
+      }.merge(super)
+    end
+
+    def results(query)
+      # don't look up a loopback or private address, just return the stored result
+      return [reserved_result(query.text)] if query.internal_ip_address?
+
+      return [] unless (doc = fetch_data(query))
+
+      if (error = doc['error'])
+        code = error['code']
+        msg = error['message']
+        raise_error(ERROR_CODES[code], msg ) || Geocoder.log(:warn, "Ipregistry API error: #{msg}")
+        return []
+      end
+      [doc]
+    end
+
+    def reserved_result(ip)
+      {
+        "ip"           => ip,
+        "country_name" => "Reserved",
+        "country_code" => "RD"
+      }
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/latlon.rb b/lib/geocoder/lookups/latlon.rb
index e6a77f2..cdc47ae 100644
--- a/lib/geocoder/lookups/latlon.rb
+++ b/lib/geocoder/lookups/latlon.rb
@@ -25,8 +25,7 @@ module Geocoder::Lookup
       # The API returned a 404 response, which indicates no results found
       elsif doc['error']['type'] == 'api_error'
         []
-      elsif
-        doc['error']['type'] == 'authentication_error'
+      elsif doc['error']['type'] == 'authentication_error'
         raise_error(Geocoder::InvalidApiKey) ||
           Geocoder.log(:warn, "LatLon.io service error: invalid API key.")
       else
diff --git a/lib/geocoder/lookups/location_iq.rb b/lib/geocoder/lookups/location_iq.rb
index 3410557..8ed0387 100644
--- a/lib/geocoder/lookups/location_iq.rb
+++ b/lib/geocoder/lookups/location_iq.rb
@@ -11,6 +11,10 @@ module Geocoder::Lookup
       ["api_key"]
     end
 
+    def supported_protocols
+      [:https]
+    end
+
     private # ----------------------------------------------------------------
 
     def base_query_url(query)
@@ -25,7 +29,7 @@ module Geocoder::Lookup
     end
 
     def configured_host
-      configuration[:host] || "locationiq.org"
+      configuration[:host] || "us1.locationiq.com"
     end
 
     def results(query)
diff --git a/lib/geocoder/lookups/maxmind_local.rb b/lib/geocoder/lookups/maxmind_local.rb
index efce27b..b0d1eac 100644
--- a/lib/geocoder/lookups/maxmind_local.rb
+++ b/lib/geocoder/lookups/maxmind_local.rb
@@ -30,7 +30,13 @@ module Geocoder::Lookup
     def results(query)
       if configuration[:file]
         geoip_class = RUBY_PLATFORM == "java" ? JGeoIP : GeoIP
-        result = geoip_class.new(configuration[:file]).city(query.to_s)
+        geoip_instance = geoip_class.new(configuration[:file])
+        result =
+          if configuration[:package] == :country
+            geoip_instance.country(query.to_s)
+          else
+            geoip_instance.city(query.to_s)
+          end
         result.nil? ? [] : [encode_hash(result.to_hash)]
       elsif configuration[:package] == :city
         addr = IPAddr.new(query.text).to_i
diff --git a/lib/geocoder/lookups/melissa_street.rb b/lib/geocoder/lookups/melissa_street.rb
new file mode 100644
index 0000000..1da9684
--- /dev/null
+++ b/lib/geocoder/lookups/melissa_street.rb
@@ -0,0 +1,41 @@
+require 'geocoder/lookups/base'
+require "geocoder/results/melissa_street"
+
+module Geocoder::Lookup
+  class MelissaStreet < Base
+
+    def name
+      "MelissaStreet"
+    end
+
+    def results(query)
+      return [] unless doc = fetch_data(query)
+
+      if doc["TransmissionResults"] == "GE05"
+        raise_error(Geocoder::InvalidApiKey) ||
+          Geocoder.log(:warn, "Melissa service error: invalid API key.")
+      end
+
+      return doc["Records"]
+    end
+
+    private # ---------------------------------------------------------------
+
+    def base_query_url(query)
+      "#{protocol}://address.melissadata.net/v3/WEB/GlobalAddress/doGlobalAddress?"
+    end
+
+    def query_url_params(query)
+      params = {
+        id: configuration.api_key,
+        format: "JSON",
+        a1: query.sanitized_text,
+        loc: query.options[:city],
+        admarea: query.options[:state],
+        postal: query.options[:postal],
+        ctry: query.options[:country]
+      }
+      params.merge(super)
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/nationaal_georegister_nl.rb b/lib/geocoder/lookups/nationaal_georegister_nl.rb
new file mode 100644
index 0000000..af1256b
--- /dev/null
+++ b/lib/geocoder/lookups/nationaal_georegister_nl.rb
@@ -0,0 +1,38 @@
+require 'geocoder/lookups/base'
+require "geocoder/results/nationaal_georegister_nl"
+
+module Geocoder::Lookup
+  class NationaalGeoregisterNl < Base
+
+    def name
+      'Nationaal Georegister Nederland'
+    end
+
+    private # ---------------------------------------------------------------
+
+    def cache_key(query)
+      base_query_url(query) + hash_to_query(query_url_params(query))
+    end
+
+    def base_query_url(query)
+      "#{protocol}://geodata.nationaalgeoregister.nl/locatieserver/v3/free?"
+    end
+
+    def valid_response?(response)
+      json   = parse_json(response.body)
+      super(response) if json
+    end
+
+    def results(query)
+      return [] unless doc = fetch_data(query)
+      return doc['response']['docs']
+    end
+
+    def query_url_params(query)
+      {
+        fl: '*',
+        q:  query.text
+      }.merge(super)
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/osmnames.rb b/lib/geocoder/lookups/osmnames.rb
new file mode 100644
index 0000000..7a78198
--- /dev/null
+++ b/lib/geocoder/lookups/osmnames.rb
@@ -0,0 +1,57 @@
+require 'cgi'
+require 'geocoder/lookups/base'
+require 'geocoder/results/osmnames'
+
+module Geocoder::Lookup
+  class Osmnames < Base
+
+    def name
+      'OSM Names'
+    end
+
+    def required_api_key_parts
+      configuration[:host] ? [] : ['key']
+    end
+
+    def supported_protocols
+      [:https]
+    end
+
+    private
+
+    def base_query_url(query)
+      "#{base_url(query)}/#{params_url(query)}.js?"
+    end
+
+    def base_url(query)
+      host = configuration[:host] || 'geocoder.tilehosting.com'
+      "#{protocol}://#{host}"
+    end
+
+    def params_url(query)
+      method, args = 'q', CGI.escape(query.sanitized_text)
+      method, args = 'r', query.coordinates.join('/') if query.reverse_geocode?
+      "#{country_limited(query)}#{method}/#{args}"
+    end
+
+    def results(query)
+      return [] unless doc = fetch_data(query)
+      if (error = doc['message'])
+        raise_error(Geocoder::InvalidRequest, error) ||
+          Geocoder.log(:warn, "OSMNames Geocoding API error: #{error}")
+      else
+        return doc['results']
+      end
+    end
+
+    def query_url_params(query)
+      {
+        key: configuration.api_key
+      }.merge(super)
+    end
+
+    def country_limited(query)
+      "#{query.options[:country_code].downcase}/" if query.options[:country_code] && !query.reverse_geocode?
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/photon.rb b/lib/geocoder/lookups/photon.rb
new file mode 100644
index 0000000..29ad926
--- /dev/null
+++ b/lib/geocoder/lookups/photon.rb
@@ -0,0 +1,89 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/photon'
+
+module Geocoder::Lookup
+  class Photon < Base
+    def name
+      'Photon'
+    end
+
+    private # ---------------------------------------------------------------
+
+    def supported_protocols
+      [:https]
+    end
+
+    def base_query_url(query)
+      host = configuration[:host] || 'photon.komoot.io'
+      method = query.reverse_geocode? ? 'reverse' : 'api'
+      "#{protocol}://#{host}/#{method}?"
+    end
+
+    def results(query)
+      return [] unless (doc = fetch_data(query))
+      return [] unless doc['type'] == 'FeatureCollection'
+      return [] unless doc['features'] || doc['features'].present?
+
+      doc['features']
+    end
+
+    def query_url_params(query)
+      lang = query.language || configuration.language
+      params = { lang: lang, limit: query.options[:limit] }
+
+      if query.reverse_geocode?
+        params.merge!(query_url_params_reverse(query))
+      else
+        params.merge!(query_url_params_coordinates(query))
+      end
+
+      params.merge!(super)
+    end
+
+    def query_url_params_coordinates(query)
+      params = { q: query.sanitized_text }
+
+      if (bias = query.options[:bias])
+        params.merge!(lat: bias[:latitude], lon: bias[:longitude], location_bias_scale: bias[:scale])
+      end
+
+      if (filter = query_url_params_coordinates_filter(query))
+        params.merge!(filter)
+      end
+
+      params
+    end
+
+    def query_url_params_coordinates_filter(query)
+      filter = query.options[:filter]
+      return unless filter
+
+      bbox = filter[:bbox]
+      {
+        bbox: bbox.is_a?(Array) ? bbox.join(',') : bbox,
+        osm_tag: filter[:osm_tag]
+      }
+    end
+
+    def query_url_params_reverse(query)
+      params = { lat: query.coordinates[0], lon: query.coordinates[1], radius: query.options[:radius] }
+
+      if (dsort = query.options[:distance_sort])
+        params[:distance_sort] = dsort ? 'true' : 'false'
+      end
+
+      if (filter = query_url_params_reverse_filter(query))
+        params.merge!(filter)
+      end
+
+      params
+    end
+
+    def query_url_params_reverse_filter(query)
+      filter = query.options[:filter]
+      return unless filter
+
+      { query_string_filter: filter[:string] }
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/pickpoint.rb b/lib/geocoder/lookups/pickpoint.rb
index e298e89..b5863ee 100644
--- a/lib/geocoder/lookups/pickpoint.rb
+++ b/lib/geocoder/lookups/pickpoint.rb
@@ -23,7 +23,7 @@ module Geocoder::Lookup
     end
 
     def query_url_params(query)
-      params = {
+      {
         key: configuration.api_key
       }.merge(super)
     end
diff --git a/lib/geocoder/lookups/smarty_streets.rb b/lib/geocoder/lookups/smarty_streets.rb
index e4f56ec..7745a94 100644
--- a/lib/geocoder/lookups/smarty_streets.rb
+++ b/lib/geocoder/lookups/smarty_streets.rb
@@ -57,7 +57,12 @@ module Geocoder::Lookup
     end
 
     def results(query)
-      fetch_data(query) || []
+      doc = fetch_data(query) || []
+      if doc.is_a?(Hash) and doc.key?('status') # implies there's an error
+        return []
+      else
+        return doc
+      end
     end
   end
 end
diff --git a/lib/geocoder/lookups/telize.rb b/lib/geocoder/lookups/telize.rb
index 2dd8452..839d42e 100644
--- a/lib/geocoder/lookups/telize.rb
+++ b/lib/geocoder/lookups/telize.rb
@@ -16,7 +16,7 @@ module Geocoder::Lookup
       if configuration[:host]
         "#{protocol}://#{configuration[:host]}/location/#{query.sanitized_text}"
       else
-        "#{protocol}://telize-v1.p.mashape.com/location/#{query.sanitized_text}?mashape-key=#{api_key}"
+        "#{protocol}://telize-v1.p.rapidapi.com/location/#{query.sanitized_text}?rapidapi-key=#{api_key}"
       end
     end
 
diff --git a/lib/geocoder/lookups/tencent.rb b/lib/geocoder/lookups/tencent.rb
index 1fef32a..39dcdbf 100644
--- a/lib/geocoder/lookups/tencent.rb
+++ b/lib/geocoder/lookups/tencent.rb
@@ -31,18 +31,18 @@ module Geocoder::Lookup
       case doc['status']
       when 0
         return [doc[content_key]]
-      when 199
-        raise error(Geocoder::InvalidApiKey, "invalid api key") ||
-        Geocoder.log(:warn, "#{name} Geocoding API error: key is not enabled for web service usage.")
-      when 311
-        raise_error(Geocoder::RequestDenied, "request denied") ||
-          Geocoder.log(:warn, "#{name} Geocoding API error: request denied.")
-      when 310, 306
-        raise_error(Geocoder::InvalidRequest, "invalid request.") ||
-          Geocoder.log(:warn, "#{name} Geocoding API error: invalid request.")
       when 311
         raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
           Geocoder.log(:warn, "#{name} Geocoding API error: invalid api key.")
+      when 310
+        raise_error(Geocoder::InvalidRequest, "invalid request.") ||
+          Geocoder.log(:warn, "#{name} Geocoding API error: invalid request, invalid parameters.")
+      when 306
+        raise_error(Geocoder::InvalidRequest, "invalid request.") ||
+          Geocoder.log(:warn, "#{name} Geocoding API error: invalid request, check response for more info.")
+      when 110
+        raise_error(Geocoder::RequestDenied, "request denied.") ||
+          Geocoder.log(:warn, "#{name} Geocoding API error: request source is not authorized.")
       end
       return []
     end
diff --git a/lib/geocoder/lookups/test.rb b/lib/geocoder/lookups/test.rb
index 1a2d0cf..115a626 100644
--- a/lib/geocoder/lookups/test.rb
+++ b/lib/geocoder/lookups/test.rb
@@ -18,6 +18,7 @@ module Geocoder
       end
 
       def self.read_stub(query_text)
+        @default_stub ||= nil
         stubs.fetch(query_text) {
           return @default_stub unless @default_stub.nil?
           raise ArgumentError, "unknown stub request #{query_text}"
@@ -28,6 +29,10 @@ module Geocoder
         @stubs ||= {}
       end
 
+      def self.delete_stub(query_text)
+        stubs.delete(query_text)
+      end
+
       def self.reset
         @stubs = {}
         @default_stub = nil
diff --git a/lib/geocoder/lookups/twogis.rb b/lib/geocoder/lookups/twogis.rb
new file mode 100644
index 0000000..e0dafe5
--- /dev/null
+++ b/lib/geocoder/lookups/twogis.rb
@@ -0,0 +1,58 @@
+require 'geocoder/lookups/base'
+require "geocoder/results/twogis"
+
+module Geocoder::Lookup
+  class Twogis < Base
+
+    def name
+      "2gis"
+    end
+
+    def required_api_key_parts
+      ["key"]
+    end
+
+    def map_link_url(coordinates)
+      "https://2gis.ru/?m=#{coordinates.join(',')}"
+    end
+
+    def supported_protocols
+      [:https]
+    end
+
+    private # ---------------------------------------------------------------
+
+    def base_query_url(query)
+      "#{protocol}://catalog.api.2gis.com/3.0/items/geocode?"
+    end
+
+    def results(query)
+      return [] unless doc = fetch_data(query)
+      if doc['meta'] && doc['meta']['error']
+        Geocoder.log(:warn, "2gis Geocoding API error: #{doc['meta']["code"]} (#{doc['meta']['error']["message"]}).")
+        return []
+      end
+      if doc['result'] && doc = doc['result']['items']
+        return doc.to_a
+      else
+        Geocoder.log(:warn, "2gis Geocoding API error: unexpected response format.")
+        return []
+      end
+    end
+
+    def query_url_params(query)
+      if query.reverse_geocode?
+        q = query.coordinates.reverse.join(",")
+      else
+        q = query.sanitized_text
+      end
+      params = {
+        :q => q,
+        :lang => "#{query.language || configuration.language}",
+        :key => configuration.api_key,
+        :fields => 'items.street,items.adm_div,items.full_address_name,items.point,items.geometry.centroid'
+      }
+      params.merge(super)
+    end
+  end
+end
diff --git a/lib/geocoder/lookups/uk_ordnance_survey_names.rb b/lib/geocoder/lookups/uk_ordnance_survey_names.rb
new file mode 100644
index 0000000..82e68b6
--- /dev/null
+++ b/lib/geocoder/lookups/uk_ordnance_survey_names.rb
@@ -0,0 +1,59 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/uk_ordnance_survey_names'
+
+module Geocoder::Lookup
+  class UkOrdnanceSurveyNames < Base
+
+    def name
+      'Ordance Survey Names'
+    end
+
+    def supported_protocols
+      [:https]
+    end
+
+    def base_query_url(query)
+      "#{protocol}://api.os.uk/search/names/v1/find?"
+    end
+
+    def required_api_key_parts
+      ["key"]
+    end
+
+    def query_url(query)
+      base_query_url(query) + url_query_string(query)
+    end
+
+    private # -------------------------------------------------------------
+
+    def results(query)
+      return [] unless doc = fetch_data(query)
+      return [] if doc['header']['totalresults'].zero?
+      return doc['results'].map { |r| r['GAZETTEER_ENTRY'] }
+    end
+
+    def query_url_params(query)
+      {
+        query: query.sanitized_text,
+        key: configuration.api_key,
+        fq: filter
+      }.merge(super)
+    end
+
+    def local_types
+      %w[
+        City
+        Hamlet
+        Other_Settlement
+        Town
+        Village
+        Postcode
+      ]
+    end
+
+    def filter
+      local_types.map { |t| "local_type:#{t}" }.join(' ')
+    end
+
+  end
+end
diff --git a/lib/geocoder/lookups/yandex.rb b/lib/geocoder/lookups/yandex.rb
index 61a6afe..7f91e4d 100644
--- a/lib/geocoder/lookups/yandex.rb
+++ b/lib/geocoder/lookups/yandex.rb
@@ -24,17 +24,16 @@ module Geocoder::Lookup
 
     def results(query)
       return [] unless doc = fetch_data(query)
-      if err = doc['error']
-        if err["status"] == 401 and err["message"] == "invalid key"
+      if [400, 403].include? doc['statusCode']
+        if doc['statusCode'] == 403 and doc['message'] == 'Invalid key'
           raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid API key.")
         else
-          Geocoder.log(:warn, "Yandex Geocoding API error: #{err['status']} (#{err['message']}).")
+          Geocoder.log(:warn, "Yandex Geocoding API error: #{doc['statusCode']} (#{doc['message']}).")
         end
         return []
       end
       if doc = doc['response']['GeoObjectCollection']
-        meta = doc['metaDataProperty']['GeocoderResponseMetaData']
-        return meta['found'].to_i > 0 ? doc['featureMember'] : []
+        return doc['featureMember'].to_a
       else
         Geocoder.log(:warn, "Yandex Geocoding API error: unexpected response format.")
         return []
@@ -50,8 +49,8 @@ module Geocoder::Lookup
       params = {
         :geocode => q,
         :format => "json",
-        :plng => "#{query.language || configuration.language}", # supports ru, uk, be
-        :key => configuration.api_key
+        :lang => "#{query.language || configuration.language}", # supports ru, uk, be, default -> ru
+        :apikey => configuration.api_key
       }
       unless (bounds = query.options[:bounds]).nil?
         params[:bbox] = bounds.map{ |point| "%f,%f" % point }.join('~')
diff --git a/lib/geocoder/results/abstract_api.rb b/lib/geocoder/results/abstract_api.rb
new file mode 100644
index 0000000..b8e3068
--- /dev/null
+++ b/lib/geocoder/results/abstract_api.rb
@@ -0,0 +1,146 @@
+require 'geocoder/results/base'
+
+module Geocoder
+  module Result
+    class AbstractApi < Base
+
+      ##
+      # Geolocation
+
+      def state
+        @data['region']
+      end
+
+      def state_code
+        @data['region_iso_code']
+      end
+
+      def city
+        @data["city"]
+      end
+
+      def city_geoname_id
+        @data["city_geoname_id"]
+      end
+
+      def region_geoname_id
+        @data["region_geoname_id"]
+      end
+
+      def postal_code
+        @data["postal_code"]
+      end
+
+      def country
+        @data["country"]
+      end
+
+      def country_code
+        @data["country_code"]
+      end
+
+      def country_geoname_id
+        @data["country_geoname_id"]
+      end
+
+      def country_is_eu
+        @data["country_is_eu"]
+      end
+
+      def continent
+        @data["continent"]
+      end
+
+      def continent_code
+        @data["continent_code"]
+      end
+
+      def continent_geoname_id
+        @data["continent_geoname_id"]
+      end
+
+      ##
+      # Security
+
+      def is_vpn?
+        @data.dig "security", "is_vpn"
+      end
+
+      ##
+      # Timezone
+
+      def timezone_name
+        @data.dig "timezone", "name"
+      end
+
+      def timezone_abbreviation
+        @data.dig "timezone", "abbreviation"
+      end
+
+      def timezone_gmt_offset
+        @data.dig "timezone", "gmt_offset"
+      end
+
+      def timezone_current_time
+        @data.dig "timezone", "current_time"
+      end
+
+      def timezone_is_dst
+        @data.dig "timezone", "is_dst"
+      end
+
+      ##
+      # Flag
+
+      def flag_emoji
+        @data.dig "flag", "emoji"
+      end
+
+      def flag_unicode
+        @data.dig "flag", "unicode"
+      end
+
+      def flag_png
+        @data.dig "flag", "png"
+      end
+
+      def flag_svg
+        @data.dig "flag", "svg"
+      end
+
+      ##
+      # Currency
+
+      def currency_currency_name
+        @data.dig "currency", "currency_name"
+      end
+
+      def currency_currency_code
+        @data.dig "currency", "currency_code"
+      end
+
+      ##
+      # Connection
+
+      def connection_autonomous_system_number
+        @data.dig "connection", "autonomous_system_number"
+      end
+
+      def connection_autonomous_system_organization
+        @data.dig "connection", "autonomous_system_organization"
+      end
+
+      def connection_connection_type
+        @data.dig "connection", "connection_type"
+      end
+
+      def connection_isp_name
+        @data.dig "connection", "isp_name"
+      end
+
+      def connection_organization_name
+        @data.dig "connection", "organization_name"
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/geocoder/results/amazon_location_service.rb b/lib/geocoder/results/amazon_location_service.rb
new file mode 100644
index 0000000..6cc1753
--- /dev/null
+++ b/lib/geocoder/results/amazon_location_service.rb
@@ -0,0 +1,57 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class AmazonLocationService < Base
+    def initialize(result)
+      @place = result
+    end
+
+    def coordinates
+      [@place.geometry.point[1], @place.geometry.point[0]]
+    end
+
+    def address
+      @place.label
+    end
+
+    def neighborhood
+      @place.neighborhood
+    end
+
+    def route
+      @place.street
+    end
+
+    def city
+      @place.municipality || @place.sub_region
+    end
+
+    def state
+      @place.region
+    end
+
+    def state_code
+      @place.region
+    end
+
+    def province
+      @place.region
+    end
+
+    def province_code
+      @place.region
+    end
+
+    def postal_code
+      @place.postal_code
+    end
+
+    def country
+      @place.country
+    end
+
+    def country_code
+      @place.country
+    end
+  end
+end
diff --git a/lib/geocoder/results/baidu.rb b/lib/geocoder/results/baidu.rb
index b30265e..c6fd36e 100644
--- a/lib/geocoder/results/baidu.rb
+++ b/lib/geocoder/results/baidu.rb
@@ -7,10 +7,6 @@ module Geocoder::Result
       ['lat', 'lng'].map{ |i| @data['location'][i] }
     end
 
-    def address
-      @data['formatted_address']
-    end
-
     def province
       @data['addressComponent'] and @data['addressComponent']['province'] or ""
     end
diff --git a/lib/geocoder/results/ban_data_gouv_fr.rb b/lib/geocoder/results/ban_data_gouv_fr.rb
index 0b936b0..d039535 100644
--- a/lib/geocoder/results/ban_data_gouv_fr.rb
+++ b/lib/geocoder/results/ban_data_gouv_fr.rb
@@ -4,10 +4,31 @@ require 'geocoder/results/base'
 module Geocoder::Result
   class BanDataGouvFr < Base
 
+    STATE_CODE_MAPPINGS = {
+      "Guadeloupe" => "01",
+      "Martinique" => "02",
+      "Guyane" => "03",
+      "La Réunion" => "04",
+      "Mayotte" => "06",
+      "Île-de-France" => "11",
+      "Centre-Val de Loire" => "24",
+      "Bourgogne-Franche-Comté" => "27",
+      "Normandie" => "28",
+      "Hauts-de-France" => "32",
+      "Grand Est" => "44",
+      "Pays de la Loire" => "52",
+      "Bretagne" => "53",
+      "Nouvelle-Aquitaine" => "75",
+      "Occitanie" => "76",
+      "Auvergne-Rhône-Alpes" => "84",
+      "Provence-Alpes-Côte d'Azur" => "93",
+      "Corse" => "94"
+    }.freeze
+
     #### BASE METHODS ####
 
     def self.response_attributes
-      %w[limit attribution version licence type features]
+      %w[limit attribution version licence type features center]
     end
 
     response_attributes.each do |a|
@@ -209,6 +230,10 @@ module Geocoder::Result
       end
     end
 
+    def region_code
+      STATE_CODE_MAPPINGS[region_name]
+    end
+
     def country
       "France"
     end
@@ -235,7 +260,7 @@ module Geocoder::Result
     alias_method :street, :street_name
     alias_method :city, :city_name
     alias_method :state, :region_name
-    alias_method :state_code, :state
+    alias_method :state_code, :region_code
 
     #### CITIES' METHODS ####
 
diff --git a/lib/geocoder/results/db_ip_com.rb b/lib/geocoder/results/db_ip_com.rb
index f5291bf..c491d52 100644
--- a/lib/geocoder/results/db_ip_com.rb
+++ b/lib/geocoder/results/db_ip_com.rb
@@ -16,7 +16,7 @@ module Geocoder::Result
     end
 
     def state_code
-      @data['stateProv']
+      @data['stateProvCode']
     end
     alias_method :state, :state_code
 
diff --git a/lib/geocoder/results/esri.rb b/lib/geocoder/results/esri.rb
index cf9c779..b2e3021 100644
--- a/lib/geocoder/results/esri.rb
+++ b/lib/geocoder/results/esri.rb
@@ -16,11 +16,14 @@ module Geocoder::Result
       end
     end
 
-    def state_code
+    def state
       attributes['Region']
     end
 
-    alias_method :state, :state_code
+    def state_code
+      abbr = attributes['RegionAbbr']
+      abbr.to_s == "" ? state : abbr
+    end
 
     def country
       country_key = reverse_geocode? ? "CountryCode" : "Country"
diff --git a/lib/geocoder/results/geoapify.rb b/lib/geocoder/results/geoapify.rb
new file mode 100644
index 0000000..9a8fcdd
--- /dev/null
+++ b/lib/geocoder/results/geoapify.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+
+require 'geocoder/results/base'
+
+module Geocoder
+  module Result
+    # https://apidocs.geoapify.com/docs/geocoding/api
+    class Geoapify < Base
+      def address(_format = :full)
+        properties['formatted']
+      end
+
+      def address_line1
+        properties['address_line1']
+      end
+
+      def address_line2
+        properties['address_line2']
+      end
+
+      def house_number
+        properties['housenumber']
+      end
+
+      def street
+        properties['street']
+      end
+
+      def postal_code
+        properties['postcode']
+      end
+
+      def district
+        properties['district']
+      end
+
+      def suburb
+        properties['suburb']
+      end
+
+      def city
+        properties['city']
+      end
+
+      def county
+        properties['county']
+      end
+
+      def state
+        properties['state']
+      end
+
+      # Not currently available in the API
+      def state_code
+        ''
+      end
+
+      def country
+        properties['country']
+      end
+
+      def country_code
+        return unless properties['country_code']
+
+        properties['country_code'].upcase
+      end
+
+      def coordinates
+        return unless properties['lat']
+        return unless properties['lon']
+
+        [properties['lat'], properties['lon']]
+      end
+
+      # See: https://tools.ietf.org/html/rfc7946#section-3.1
+      #
+      # Each feature has a "Point" type in the Geoapify API.
+      def geometry
+        return unless data['geometry']
+
+        symbol_hash data['geometry']
+      end
+
+      # See: https://tools.ietf.org/html/rfc7946#section-5
+      def bounds
+        data['bbox']
+      end
+
+      # Type of the result, one of:
+      #
+      #   * :unknown
+      #   * :amenity
+      #   * :building
+      #   * :street
+      #   * :suburb
+      #   * :district
+      #   * :postcode
+      #   * :city
+      #   * :county
+      #   * :state
+      #   * :country
+      #
+      def type
+        return :unknown unless properties['result_type']
+
+        properties['result_type'].to_sym
+      end
+
+      # Distance in meters to given bias:proximity or to given coordinates for
+      # reverse geocoding
+      def distance
+        properties['distance']
+      end
+
+      # Calculated rank for the result, containing the following keys:
+      #
+      #   * `popularity` - The popularity score of the result
+      #   * `confidence` - The confidence value of the result (0-1)
+      #   * `match_type` - The result's match type, one of following:
+      #      * full_match
+      #      * inner_part
+      #      * match_by_building
+      #      * match_by_street
+      #      * match_by_postcode
+      #      * match_by_city_or_disrict
+      #      * match_by_country_or_state
+      #
+      # Example:
+      #   {
+      #     popularity: 8.615793062435909,
+      #     confidence: 0.88,
+      #     match_type: :full_match
+      #   }
+      def rank
+        return unless properties['rank']
+
+        r = symbol_hash(properties['rank'])
+        r[:match_type] = r[:match_type].to_sym if r[:match_type]
+        r
+      end
+
+      # Examples:
+      #
+      # Open
+      #   {
+      #     sourcename: 'openstreetmap',
+      #     wheelchair: 'limited',
+      #     wikidata: 'Q186125',
+      #     wikipedia: 'en:Madison Square Garden',
+      #     website: 'http://www.thegarden.com/',
+      #     phone: '12124656741',
+      #     osm_type: 'W',
+      #     osm_id: 138141251,
+      #     continent: 'North America',
+      #   }
+      def datasource
+        return unless properties['datasource']
+
+        symbol_hash properties['datasource']
+      end
+
+      private
+
+      def properties
+        @properties ||= data['properties'] || {}
+      end
+
+      def symbol_hash(orig_hash)
+        {}.tap do |result|
+          orig_hash.each_key do |key|
+            next unless orig_hash[key]
+
+            result[key.to_sym] = orig_hash[key]
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/results/geocoder_us.rb b/lib/geocoder/results/geocoder_us.rb
deleted file mode 100644
index ca20ad4..0000000
--- a/lib/geocoder/results/geocoder_us.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'geocoder/results/base'
-
-module Geocoder::Result
-  class GeocoderUs < Base
-    def coordinates
-      [@data[0].to_f, @data[1].to_f]
-    end
-
-    def address(format = :full)
-      "#{street_address}, #{city}, #{state} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
-    end
-
-    def street_address
-      @data[2]
-    end
-
-    def city
-      @data[3]
-    end
-
-    def state
-      @data[4]
-    end
-
-    alias_method :state_code, :state
-
-    def postal_code
-      @data[5]
-    end
-
-    def country
-      'United States'
-    end
-
-    def country_code
-      'US'
-    end
-  end
-end
diff --git a/lib/geocoder/results/here.rb b/lib/geocoder/results/here.rb
index 12f3ab8..2b32f39 100644
--- a/lib/geocoder/results/here.rb
+++ b/lib/geocoder/results/here.rb
@@ -7,73 +7,71 @@ module Geocoder::Result
     # A string in the given format.
     #
     def address(format = :full)
-      address_data['Label']
+      address_data["label"]
     end
 
     ##
     # A two-element array: [lat, lon].
     #
     def coordinates
-      fail unless d = @data['Location']['DisplayPosition']
-      [d['Latitude'].to_f, d['Longitude'].to_f]
+      fail unless d = @data["position"]
+      [d["lat"].to_f, d["lng"].to_f]
     end
     
     def route
-      address_data['Street']
+      address_data["street"]
     end
     
     def street_number
-      address_data['HouseNumber']
+      address_data["houseNumber"]
     end  
 
     def state
-      address_data['County']
+      address_data["state"]
     end
 
     def province
-      address_data['County']
+      address_data["county"]
     end
 
     def postal_code
-      address_data['PostalCode']
+      address_data["postalCode"]
     end
 
     def city
-      address_data['City']
+      address_data["city"]
     end
 
     def state_code
-      address_data['State']
+      address_data["stateCode"]
     end
 
     def province_code
-      address_data['State']
+      address_data["state"]
     end
 
     def country
-      fail unless d = address_data['AdditionalData']
-      if v = d.find{|ad| ad['key']=='CountryName'}
-        return v['value']
-      end
+      address_data["countryName"]
     end
 
     def country_code
-      address_data['Country']
+      address_data["countryCode"]
     end
 
     def viewport
-      map_view = data['Location']['MapView'] || fail
-      south = map_view['BottomRight']['Latitude']
-      west = map_view['TopLeft']['Longitude']
-      north = map_view['TopLeft']['Latitude']
-      east = map_view['BottomRight']['Longitude']
+      return [] if data["resultType"] == "place"
+      map_view = data["mapView"]
+      south = map_view["south"]
+      west = map_view["west"]
+      north = map_view["north"]
+      east = map_view["east"]
       [south, west, north, east]
     end
 
     private # ----------------------------------------------------------------
 
     def address_data
-      @data['Location']['Address'] || fail
+      @data["address"] || fail
     end
   end
 end
diff --git a/lib/geocoder/results/ipbase.rb b/lib/geocoder/results/ipbase.rb
new file mode 100644
index 0000000..00dc3b5
--- /dev/null
+++ b/lib/geocoder/results/ipbase.rb
@@ -0,0 +1,40 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Ipbase < Base
+    def ip
+      @data["data"]['ip']
+    end
+
+    def country_code
+      @data["data"]["location"]["country"]["alpha2"]
+    end
+
+    def country
+      @data["data"]["location"]["country"]["name"]
+    end
+
+    def state_code
+      @data["data"]["location"]["region"]["alpha2"]
+    end
+
+    def state
+      @data["data"]["location"]["region"]["name"]
+    end
+
+    def city
+      @data["data"]["location"]["city"]["name"]
+    end
+
+    def postal_code
+      @data["data"]["location"]["zip"]
+    end
+
+    def coordinates
+      [
+        @data["data"]["location"]["latitude"].to_f,
+        @data["data"]["location"]["longitude"].to_f
+      ]
+    end
+  end
+end
diff --git a/lib/geocoder/results/ipgeolocation.rb b/lib/geocoder/results/ipgeolocation.rb
new file mode 100644
index 0000000..15dd05b
--- /dev/null
+++ b/lib/geocoder/results/ipgeolocation.rb
@@ -0,0 +1,59 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Ipgeolocation < Base
+
+    def coordinates
+      [@data['latitude'].to_f, @data['longitude'].to_f]
+    end
+
+    def address(format = :full)
+      "#{city}, #{state} #{postal_code}, #{country_name}".sub(/^[ ,]*/, "")
+    end
+
+    def state
+      @data['state_prov']
+    end
+
+    def state_code
+      @data['state_prov']
+    end
+
+    def country
+      @data['country_name']
+    end
+
+    def country_code
+      @data['country_code2']
+    end
+
+    def postal_code
+      @data['zipcode']
+    end
+
+    def self.response_attributes
+      [
+          ['ip', ''],
+          ['hostname', ''],
+          ['continent_code', ''],
+          ['continent_name', ''],
+          ['country_code2', ''],
+          ['country_code3', ''],
+          ['country_name', ''],
+          ['country_capital',''],
+          ['district',''],
+          ['state_prov',''],
+          ['city', ''],
+          ['zipcode', ''],
+          ['time_zone', {}],
+          ['currency', {}]
+      ]
+    end
+
+    response_attributes.each do |attr, default|
+      define_method attr do
+        @data[attr] || default
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/results/ipqualityscore.rb b/lib/geocoder/results/ipqualityscore.rb
new file mode 100644
index 0000000..c2bf58d
--- /dev/null
+++ b/lib/geocoder/results/ipqualityscore.rb
@@ -0,0 +1,54 @@
+require 'geocoder/results/base'
+
+module Geocoder
+  module Result
+    class Ipqualityscore < Base
+
+      def self.key_method_mappings
+        {
+          'request_id' => :request_id,
+          'success' => :success?,
+          'message' => :message,
+          'city' => :city,
+          'region' => :state,
+          'country_code' => :country_code,
+          'mobile' => :mobile?,
+          'fraud_score' => :fraud_score,
+          'ISP' => :isp,
+          'ASN' => :asn,
+          'organization' => :organization,
+          'is_crawler' => :crawler?,
+          'host' => :host,
+          'proxy' => :proxy?,
+          'vpn' => :vpn?,
+          'tor' => :tor?,
+          'active_vpn' => :active_vpn?,
+          'active_tor' => :active_tor?,
+          'recent_abuse' => :recent_abuse?,
+          'bot_status' => :bot?,
+          'connection_type' => :connection_type,
+          'abuse_velocity' => :abuse_velocity,
+          'timezone' => :timezone,
+        }
+      end
+
+      key_method_mappings.each_pair do |key, meth|
+        define_method meth do
+          @data[key]
+        end
+      end
+
+      alias_method :state_code, :state
+      alias_method :country, :country_code
+
+      def postal_code
+        '' # No suitable fallback
+      end
+
+      def address
+        [city, state, country_code].compact.reject(&:empty?).join(', ')
+      end
+
+    end
+  end
+end
diff --git a/lib/geocoder/results/ipregistry.rb b/lib/geocoder/results/ipregistry.rb
new file mode 100644
index 0000000..7179559
--- /dev/null
+++ b/lib/geocoder/results/ipregistry.rb
@@ -0,0 +1,304 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Ipregistry < Base
+
+    def initialize(data)
+      super
+
+      @data = flatten_hash(data)
+    end
+
+    def coordinates
+      [@data['location_latitude'], @data['location_longitude']]
+    end
+
+    def flatten_hash(hash)
+      hash.each_with_object({}) do |(k, v), h|
+        if v.is_a? Hash
+          flatten_hash(v).map do |h_k, h_v|
+            h["#{k}_#{h_k}".to_s] = h_v
+          end
+        else
+          h[k] = v
+        end
+      end
+    end
+
+    private :flatten_hash
+
+    def city
+      @data['location_city']
+    end
+
+    def country
+      @data['location_country_name']
+    end
+
+    def country_code
+      @data['location_country_code']
+    end
+
+    def postal_code
+      @data['location_postal']
+    end
+
+    def state
+      @data['location_region_name']
+    end
+
+    def state_code
+      @data['location_region_code']
+    end
+
+    # methods for fields specific to Ipregistry
+
+    def ip
+      @data["ip"]
+    end
+
+    def type
+      @data["type"]
+    end
+
+    def hostname
+      @data["hostname"]
+    end
+
+    def carrier_name
+      @data["carrier_name"]
+    end
+
+    def carrier_mcc
+      @data["carrier_mcc"]
+    end
+
+    def carrier_mnc
+      @data["carrier_mnc"]
+    end
+
+    def connection_asn
+      @data["connection_asn"]
+    end
+
+    def connection_domain
+      @data["connection_domain"]
+    end
+
+    def connection_organization
+      @data["connection_organization"]
+    end
+
+    def connection_type
+      @data["connection_type"]
+    end
+
+    def currency_code
+      @data["currency_code"]
+    end
+
+    def currency_name
+      @data["currency_name"]
+    end
+
+    def currency_plural
+      @data["currency_plural"]
+    end
+
+    def currency_symbol
+      @data["currency_symbol"]
+    end
+
+    def currency_symbol_native
+      @data["currency_symbol_native"]
+    end
+
+    def currency_format_negative_prefix
+      @data["currency_format_negative_prefix"]
+    end
+
+    def currency_format_negative_suffix
+      @data["currency_format_negative_suffix"]
+    end
+
+    def currency_format_positive_prefix
+      @data["currency_format_positive_prefix"]
+    end
+
+    def currency_format_positive_suffix
+      @data["currency_format_positive_suffix"]
+    end
+
+    def location_continent_code
+      @data["location_continent_code"]
+    end
+
+    def location_continent_name
+      @data["location_continent_name"]
+    end
+
+    def location_country_area
+      @data["location_country_area"]
+    end
+
+    def location_country_borders
+      @data["location_country_borders"]
+    end
+
+    def location_country_calling_code
+      @data["location_country_calling_code"]
+    end
+
+    def location_country_capital
+      @data["location_country_capital"]
+    end
+
+    def location_country_code
+      @data["location_country_code"]
+    end
+
+    def location_country_name
+      @data["location_country_name"]
+    end
+
+    def location_country_population
+      @data["location_country_population"]
+    end
+
+    def location_country_population_density
+      @data["location_country_population_density"]
+    end
+
+    def location_country_flag_emoji
+      @data["location_country_flag_emoji"]
+    end
+
+    def location_country_flag_emoji_unicode
+      @data["location_country_flag_emoji_unicode"]
+    end
+
+    def location_country_flag_emojitwo
+      @data["location_country_flag_emojitwo"]
+    end
+
+    def location_country_flag_noto
+      @data["location_country_flag_noto"]
+    end
+
+    def location_country_flag_twemoji
+      @data["location_country_flag_twemoji"]
+    end
+
+    def location_country_flag_wikimedia
+      @data["location_country_flag_wikimedia"]
+    end
+
+    def location_country_languages
+      @data["location_country_languages"]
+    end
+
+    def location_country_tld
+      @data["location_country_tld"]
+    end
+
+    def location_region_code
+      @data["location_region_code"]
+    end
+
+    def location_region_name
+      @data["location_region_name"]
+    end
+
+    def location_city
+      @data["location_city"]
+    end
+
+    def location_postal
+      @data["location_postal"]
+    end
+
+    def location_latitude
+      @data["location_latitude"]
+    end
+
+    def location_longitude
+      @data["location_longitude"]
+    end
+
+    def location_language_code
+      @data["location_language_code"]
+    end
+
+    def location_language_name
+      @data["location_language_name"]
+    end
+
+    def location_language_native
+      @data["location_language_native"]
+    end
+
+    def location_in_eu
+      @data["location_in_eu"]
+    end
+
+    def security_is_bogon
+      @data["security_is_bogon"]
+    end
+
+    def security_is_cloud_provider
+      @data["security_is_cloud_provider"]
+    end
+
+    def security_is_tor
+      @data["security_is_tor"]
+    end
+
+    def security_is_tor_exit
+      @data["security_is_tor_exit"]
+    end
+
+    def security_is_proxy
+      @data["security_is_proxy"]
+    end
+
+    def security_is_anonymous
+      @data["security_is_anonymous"]
+    end
+
+    def security_is_abuser
+      @data["security_is_abuser"]
+    end
+
+    def security_is_attacker
+      @data["security_is_attacker"]
+    end
+
+    def security_is_threat
+      @data["security_is_threat"]
+    end
+
+    def time_zone_id
+      @data["time_zone_id"]
+    end
+
+    def time_zone_abbreviation
+      @data["time_zone_abbreviation"]
+    end
+
+    def time_zone_current_time
+      @data["time_zone_current_time"]
+    end
+
+    def time_zone_name
+      @data["time_zone_name"]
+    end
+
+    def time_zone_offset
+      @data["time_zone_offset"]
+    end
+
+    def time_zone_in_daylight_saving
+      @data["time_zone_in_daylight_saving"]
+    end
+  end
+end
diff --git a/lib/geocoder/results/mapbox.rb b/lib/geocoder/results/mapbox.rb
index e458731..a43e5e2 100644
--- a/lib/geocoder/results/mapbox.rb
+++ b/lib/geocoder/results/mapbox.rb
@@ -23,7 +23,10 @@ module Geocoder::Result
       context_part('region')
     end
 
-    alias_method :state_code, :state
+    def state_code
+      value = context_part('region', 'short_code')
+      value.split('-').last unless value.nil?
+    end
 
     def postal_code
       context_part('postcode')
@@ -33,7 +36,10 @@ module Geocoder::Result
       context_part('country')
     end
 
-    alias_method :country_code, :country
+    def country_code
+      value = context_part('country', 'short_code')
+      value.upcase unless value.nil?
+    end
 
     def neighborhood
       context_part('neighborhood')
@@ -45,8 +51,8 @@ module Geocoder::Result
 
     private
 
-    def context_part(name)
-      context.map { |c| c['text'] if c['id'] =~ Regexp.new(name) }.compact.first
+    def context_part(name, key = 'text')
+      (context.detect { |c| c['id'] =~ Regexp.new(name) } || {})[key]
     end
 
     def context
diff --git a/lib/geocoder/results/melissa_street.rb b/lib/geocoder/results/melissa_street.rb
new file mode 100644
index 0000000..8cf6079
--- /dev/null
+++ b/lib/geocoder/results/melissa_street.rb
@@ -0,0 +1,46 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class MelissaStreet < Base
+    def address(format = :full)
+      @data['FormattedAddress']
+    end
+
+    def street_address
+      @data['AddressLine1']
+    end
+
+    def suffix
+      @data['ThoroughfareTrailingType']
+    end
+
+    def number
+      @data['PremisesNumber']
+    end
+
+    def city
+      @data['Locality']
+    end
+
+    def state_code
+      @data['AdministrativeArea']
+    end
+    alias_method :state, :state_code
+
+    def country
+      @data['CountryName']
+    end
+
+    def country_code
+      @data['CountryISO3166_1_Alpha2']
+    end
+
+    def postal_code
+      @data['PostalCode']
+    end
+
+    def coordinates
+      [@data['Latitude'].to_f, @data['Longitude'].to_f]
+    end
+  end
+end
diff --git a/lib/geocoder/results/nationaal_georegister_nl.rb b/lib/geocoder/results/nationaal_georegister_nl.rb
new file mode 100644
index 0000000..429e506
--- /dev/null
+++ b/lib/geocoder/results/nationaal_georegister_nl.rb
@@ -0,0 +1,62 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class NationaalGeoregisterNl < Base
+
+    def response_attributes
+      @data
+    end
+
+    def coordinates
+      @data['centroide_ll'][6..-2].split(' ').map(&:to_f).reverse
+    end
+
+    def formatted_address
+      @data['weergavenaam']
+    end
+
+    alias_method :address, :formatted_address
+
+    def province
+      @data['provincienaam']
+    end
+
+    alias_method :state, :province
+
+    def city
+      @data['woonplaatsnaam']
+    end
+
+    def district
+      @data['gemeentenaam']
+    end
+
+    def street
+      @data['straatnaam']
+    end
+
+    def street_number
+      @data['huis_nlt']
+    end
+
+    def address_components
+      @data
+    end
+
+    def state_code
+      @data['provinciecode']
+    end
+
+    def postal_code
+      @data['postcode']
+    end
+
+    def country
+      "Netherlands"
+    end
+
+    def country_code
+      "NL"
+    end
+  end
+end
diff --git a/lib/geocoder/results/nominatim.rb b/lib/geocoder/results/nominatim.rb
index c993425..9f6149a 100644
--- a/lib/geocoder/results/nominatim.rb
+++ b/lib/geocoder/results/nominatim.rb
@@ -4,12 +4,12 @@ module Geocoder::Result
   class Nominatim < Base
 
     def poi
-      return @data['address'][place_type] if @data['address'].key?(place_type)
+      return address_data[place_type] if address_data.key?(place_type)
       return nil
     end
 
     def house_number
-      @data['address']['house_number']
+      address_data['house_number']
     end
 
     def address
@@ -18,65 +18,71 @@ module Geocoder::Result
 
     def street
       %w[road pedestrian highway].each do |key|
-        return @data['address'][key] if @data['address'].key?(key)
+        return address_data[key] if address_data.key?(key)
       end
       return nil
     end
 
     def city
       %w[city town village hamlet].each do |key|
-        return @data['address'][key] if @data['address'].key?(key)
+        return address_data[key] if address_data.key?(key)
       end
       return nil
     end
 
     def village
-      @data['address']['village']
+      address_data['village']
     end
 
     def town
-      @data['address']['town']
+      address_data['town']
     end
 
     def state
-      @data['address']['state']
+      address_data['state']
     end
 
     alias_method :state_code, :state
 
     def postal_code
-      @data['address']['postcode']
+      address_data['postcode']
     end
 
     def county
-      @data['address']['county']
+      address_data['county']
     end
 
     def country
-      @data['address']['country']
+      address_data['country']
     end
 
     def country_code
-      @data['address']['country_code']
+      address_data['country_code']
     end
 
     def suburb
-      @data['address']['suburb']
+      address_data['suburb']
     end
 
     def city_district
-      @data['address']['city_district']
+      address_data['city_district']
     end
 
     def state_district
-      @data['address']['state_district']
+      address_data['state_district']
     end
 
     def neighbourhood
-      @data['address']['neighbourhood']
+      address_data['neighbourhood']
+    end
+
+    def municipality
+      address_data['municipality']
     end
 
     def coordinates
+      return [] unless @data['lat'] && @data['lon']
+
       [@data['lat'].to_f, @data['lon'].to_f]
     end
 
@@ -105,5 +111,11 @@ module Geocoder::Result
         end
       end
     end
+
+    private
+
+    def address_data
+      @data['address'] || {}
+    end
   end
 end
diff --git a/lib/geocoder/results/osmnames.rb b/lib/geocoder/results/osmnames.rb
new file mode 100644
index 0000000..5954e75
--- /dev/null
+++ b/lib/geocoder/results/osmnames.rb
@@ -0,0 +1,56 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Osmnames < Base
+    def address
+      @data['display_name']
+    end
+
+    def coordinates
+      [@data['lat'].to_f, @data['lon'].to_f]
+    end
+
+    def viewport
+      west, south, east, north = @data['boundingbox'].map(&:to_f)
+      [south, west, north, east]
+    end
+
+    def state
+      @data['state']
+    end
+    alias_method :state_code, :state
+
+    def place_class
+      @data['class']
+    end
+
+    def place_type
+      @data['type']
+    end
+
+    def postal_code
+      ''
+    end
+
+    def country_code
+      @data['country_code']
+    end
+
+    def country
+      @data['country']
+    end
+
+    def self.response_attributes
+      %w[house_number street city name osm_id osm_type boundingbox place_rank
+      importance county rank name_suffix]
+    end
+
+    response_attributes.each do |a|
+      unless method_defined?(a)
+        define_method a do
+          @data[a]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/results/photon.rb b/lib/geocoder/results/photon.rb
new file mode 100644
index 0000000..534f0d0
--- /dev/null
+++ b/lib/geocoder/results/photon.rb
@@ -0,0 +1,119 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Photon < Base
+    def name
+      properties['name']
+    end
+
+    def address(_format = :full)
+      parts = []
+      parts << name if name
+      parts << street_address if street_address
+      parts << city
+      parts << state if state
+      parts << postal_code
+      parts << country
+
+      parts.join(', ')
+    end
+
+    def street_address
+      return unless street
+      return street unless house_number
+
+      "#{house_number} #{street}"
+    end
+
+    def house_number
+      properties['housenumber']
+    end
+
+    def street
+      properties['street']
+    end
+
+    def postal_code
+      properties['postcode']
+    end
+
+    def city
+      properties['city']
+    end
+
+    def state
+      properties['state']
+    end
+
+    def state_code
+      ''
+    end
+
+    def country
+      properties['country']
+    end
+
+    def country_code
+      ''
+    end
+
+    def coordinates
+      return unless geometry
+      return unless geometry[:coordinates]
+
+      geometry[:coordinates].reverse
+    end
+
+    def geometry
+      return unless data['geometry']
+
+      symbol_hash data['geometry']
+    end
+
+    def bounds
+      properties['extent']
+    end
+
+    # Type of the result (OSM object type), one of:
+    #
+    #   :node
+    #   :way
+    #   :relation
+    #
+    def type
+      {
+        'N' => :node,
+        'W' => :way,
+        'R' => :relation
+      }[properties['osm_type']]
+    end
+
+    def osm_id
+      properties['osm_id']
+    end
+
+    # See: https://wiki.openstreetmap.org/wiki/Tags
+    def osm_tag
+      return unless properties['osm_key']
+      return properties['osm_key'] unless properties['osm_value']
+
+      "#{properties['osm_key']}=#{properties['osm_value']}"
+    end
+
+    private
+
+    def properties
+      @properties ||= data['properties'] || {}
+    end
+
+    def symbol_hash(orig_hash)
+      {}.tap do |result|
+        orig_hash.each_key do |key|
+          next unless orig_hash[key]
+
+          result[key.to_sym] = orig_hash[key]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/results/twogis.rb b/lib/geocoder/results/twogis.rb
new file mode 100644
index 0000000..766be2d
--- /dev/null
+++ b/lib/geocoder/results/twogis.rb
@@ -0,0 +1,76 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Twogis < Base
+    def coordinates
+      ['lat', 'lon'].map{ |i| @data['point'][i] } if @data['point']
+    end
+
+    def address(_format = :full)
+      @data['full_address_name'] || ''
+    end
+
+    def city
+      return '' unless @data['adm_div']
+      @data['adm_div'].select{|u| u["type"] == "city"}.first.try(:[], 'name') || ''
+    end
+
+    def region
+      return '' unless @data['adm_div']
+      @data['adm_div'].select{|u| u["type"] == "region"}.first.try(:[], 'name') || ''
+    end
+
+    def country
+      return '' unless @data['adm_div']
+      @data['adm_div'].select{|u| u["type"] == "country"}.first.try(:[], 'name') || ''
+    end
+
+    def district
+      return '' unless @data['adm_div']
+      @data['adm_div'].select{|u| u["type"] == "district"}.first.try(:[], 'name') || ''
+    end
+
+    def district_area
+      return '' unless @data['adm_div']
+      @data['adm_div'].select{|u| u["type"] == "district_area"}.first.try(:[], 'name') || ''
+    end
+
+    def street_address
+      @data['address_name'] || ''
+    end
+
+    def street
+      return '' unless @data['address_name']
+      @data['address_name'].split(', ').first
+    end
+
+    def street_number
+      return '' unless @data['address_name']
+      @data['address_name'].split(', ')[1] || ''
+    end
+
+    def type
+      @data['type'] || ''
+    end
+
+    def purpose_name
+      @data['purpose_name'] || ''
+    end
+
+    def building_name
+      @data['building_name'] || ''
+    end
+
+    def subtype
+      @data['subtype'] || ''
+    end
+
+    def subtype_specification
+      @data['subtype_specification'] || ''
+    end
+
+    def name
+      @data['name'] || ''
+    end
+  end
+end
diff --git a/lib/geocoder/results/uk_ordnance_survey_names.rb b/lib/geocoder/results/uk_ordnance_survey_names.rb
new file mode 100644
index 0000000..c09d357
--- /dev/null
+++ b/lib/geocoder/results/uk_ordnance_survey_names.rb
@@ -0,0 +1,59 @@
+require 'geocoder/results/base'
+require 'easting_northing'
+
+module Geocoder::Result
+  class UkOrdnanceSurveyNames < Base
+
+    def coordinates
+      @coordinates ||= Geocoder::EastingNorthing.new(
+        easting: data['GEOMETRY_X'],
+        northing: data['GEOMETRY_Y'],
+      ).lat_lng
+    end
+
+    def city
+      is_postcode? ? data['DISTRICT_BOROUGH'] : data['NAME1']
+    end
+
+    def county
+      data['COUNTY_UNITARY']
+    end
+    alias state county
+
+    def county_code
+      code_from_uri data['COUNTY_UNITARY_URI']
+    end
+    alias state_code county_code
+
+    def province
+      data['REGION']
+    end
+
+    def province_code
+      code_from_uri data['REGION_URI']
+    end
+
+    def postal_code
+      is_postcode? ? data['NAME1'] : ''
+    end
+
+    def country
+      'United Kingdom'
+    end
+
+    def country_code
+      'UK'
+    end
+
+    private
+
+    def is_postcode?
+      data['LOCAL_TYPE'] == 'Postcode'
+    end
+
+    def code_from_uri(uri)
+      return '' if uri.nil?
+      uri.split('/').last
+    end
+  end
+end
diff --git a/lib/geocoder/results/yandex.rb b/lib/geocoder/results/yandex.rb
index 5243533..eced227 100644
--- a/lib/geocoder/results/yandex.rb
+++ b/lib/geocoder/results/yandex.rb
@@ -2,78 +2,223 @@ require 'geocoder/results/base'
 
 module Geocoder::Result
   class Yandex < Base
+    # Yandex result has difficult tree structure,
+    # and presence of some nodes depends on exact search case.
+
+    # Also Yandex lacks documentation about it.
+    # See https://tech.yandex.com/maps/doc/geocoder/desc/concepts/response_structure-docpage/
+
+    # Ultimatly, we need to find Locality and/or Thoroughfare data.
+
+    # It may resides on the top (ADDRESS_DETAILS) level.
+    # example: 'Baltic Sea'
+    # "AddressDetails": {
+    #   "Locality": {
+    #     "Premise": {
+    #       "PremiseName": "Baltic Sea"
+    #     }
+    #   }
+    # }
+
+    ADDRESS_DETAILS = %w[
+      GeoObject metaDataProperty GeocoderMetaData
+      AddressDetails
+    ].freeze
+
+    # On COUNTRY_LEVEL.
+    # example: 'Potomak'
+    # "AddressDetails": {
+    #   "Country": {
+    #     "AddressLine": "reka Potomak",
+    #     "CountryNameCode": "US",
+    #     "CountryName": "United States of America",
+    #     "Locality": {
+    #       "Premise": {
+    #         "PremiseName": "reka Potomak"
+    #       }
+    #     }
+    #   }
+    # }
+
+    COUNTRY_LEVEL = %w[
+      GeoObject metaDataProperty GeocoderMetaData
+      AddressDetails Country
+    ].freeze
+
+    # On ADMIN_LEVEL (usually state or city)
+    # example: 'Moscow, Tverskaya'
+    # "AddressDetails": {
+    #   "Country": {
+    #     "AddressLine": "Moscow, Tverskaya Street",
+    #     "CountryNameCode": "RU",
+    #     "CountryName": "Russia",
+    #     "AdministrativeArea": {
+    #       "AdministrativeAreaName": "Moscow",
+    #       "Locality": {
+    #         "LocalityName": "Moscow",
+    #         "Thoroughfare": {
+    #           "ThoroughfareName": "Tverskaya Street"
+    #         }
+    #       }
+    #     }
+    #   }
+    # }
+
+    ADMIN_LEVEL = %w[
+      GeoObject metaDataProperty GeocoderMetaData
+      AddressDetails Country
+      AdministrativeArea
+    ].freeze
+
+    # On SUBADMIN_LEVEL (may refer to urban district)
+    # example: 'Moscow Region, Krasnogorsk'
+    # "AddressDetails": {
+    #   "Country": {
+    #     "AddressLine": "Moscow Region, Krasnogorsk",
+    #     "CountryNameCode": "RU",
+    #     "CountryName": "Russia",
+    #     "AdministrativeArea": {
+    #       "AdministrativeAreaName": "Moscow Region",
+    #       "SubAdministrativeArea": {
+    #         "SubAdministrativeAreaName": "gorodskoy okrug Krasnogorsk",
+    #         "Locality": {
+    #           "LocalityName": "Krasnogorsk"
+    #         }
+    #       }
+    #     }
+    #   }
+    # }
+
+    SUBADMIN_LEVEL = %w[
+      GeoObject metaDataProperty GeocoderMetaData
+      AddressDetails Country
+      AdministrativeArea
+      SubAdministrativeArea
+    ].freeze
+
+    # On DEPENDENT_LOCALITY_1 (may refer to district of city)
+    # example: 'Paris, Etienne Marcel'
+    # "AddressDetails": {
+    #   "Country": {
+    #     "AddressLine": "Île-de-France, Paris, 1er Arrondissement, Rue Étienne Marcel",
+    #     "CountryNameCode": "FR",
+    #     "CountryName": "France",
+    #     "AdministrativeArea": {
+    #       "AdministrativeAreaName": "Île-de-France",
+    #       "Locality": {
+    #         "LocalityName": "Paris",
+    #         "DependentLocality": {
+    #           "DependentLocalityName": "1er Arrondissement",
+    #           "Thoroughfare": {
+    #             "ThoroughfareName": "Rue Étienne Marcel"
+    #           }
+    #         }
+    #       }
+    #     }
+    #   }
+    # }
+
+    DEPENDENT_LOCALITY_1 = %w[
+      GeoObject metaDataProperty GeocoderMetaData
+      AddressDetails Country
+      AdministrativeArea Locality
+      DependentLocality
+    ].freeze
+
+    # On DEPENDENT_LOCALITY_2 (for special cases like turkish "mahalle")
+    # https://en.wikipedia.org/wiki/Mahalle
+    # example: 'Istanbul Mabeyinci Yokuşu 17'
+
+    # "AddressDetails": {
+    #   "Country": {
+    #     "AddressLine": "İstanbul, Fatih, Saraç İshak Mah., Mabeyinci Yokuşu, 17",
+    #     "CountryNameCode": "TR",
+    #     "CountryName": "Turkey",
+    #     "AdministrativeArea": {
+    #       "AdministrativeAreaName": "İstanbul",
+    #       "SubAdministrativeArea": {
+    #         "SubAdministrativeAreaName": "Fatih",
+    #         "Locality": {
+    #           "DependentLocality": {
+    #             "DependentLocalityName": "Saraç İshak Mah.",
+    #             "Thoroughfare": {
+    #               "ThoroughfareName": "Mabeyinci Yokuşu",
+    #               "Premise": {
+    #                 "PremiseNumber": "17"
+    #               }
+    #             }
+    #           }
+    #         }
+    #       }
+    #     }
+    #   }
+    # }
+
+    DEPENDENT_LOCALITY_2 = %w[
+      GeoObject metaDataProperty GeocoderMetaData
+      AddressDetails Country
+      AdministrativeArea
+      SubAdministrativeArea Locality
+      DependentLocality
+    ].freeze
 
     def coordinates
       @data['GeoObject']['Point']['pos'].split(' ').reverse.map(&:to_f)
     end
 
-    def address(format = :full)
+    def address(_format = :full)
       @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['text']
     end
 
     def city
-      if state.empty? and address_details and address_details.has_key? 'Locality'
-        address_details['Locality']['LocalityName']
-      elsif sub_state.empty? and address_details and address_details.has_key? 'AdministrativeArea' and
-          address_details['AdministrativeArea'].has_key? 'Locality'
-        address_details['AdministrativeArea']['Locality']['LocalityName']
-      elsif not sub_state_city.empty?
-        sub_state_city
-      else
-        ""
-      end
+      result =
+        if state.empty?
+          find_in_hash(@data, *COUNTRY_LEVEL, 'Locality', 'LocalityName')
+        elsif sub_state.empty?
+          find_in_hash(@data, *ADMIN_LEVEL, 'Locality', 'LocalityName')
+        else
+          find_in_hash(@data, *SUBADMIN_LEVEL, 'Locality', 'LocalityName')
+        end
+
+      result || ""
     end
 
     def country
-      if address_details
-        address_details['CountryName']
-      else
-        ""
-      end
+      find_in_hash(@data, *COUNTRY_LEVEL, 'CountryName') || ""
     end
 
     def country_code
-      if address_details
-        address_details['CountryNameCode']
-      else
-        ""
-      end
+      find_in_hash(@data, *COUNTRY_LEVEL, 'CountryNameCode') || ""
     end
 
     def state
-      if address_details and address_details['AdministrativeArea']
-        address_details['AdministrativeArea']['AdministrativeAreaName']
-      else
-        ""
-      end
+      find_in_hash(@data, *ADMIN_LEVEL, 'AdministrativeAreaName') || ""
     end
 
     def sub_state
-      if !state.empty? and address_details and address_details['AdministrativeArea']['SubAdministrativeArea']
-        address_details['AdministrativeArea']['SubAdministrativeArea']['SubAdministrativeAreaName']
-      else
-        ""
-      end
+      return "" if state.empty?
+      find_in_hash(@data, *SUBADMIN_LEVEL, 'SubAdministrativeAreaName') || ""
     end
 
     def state_code
       ""
     end
 
-    def postal_code
-      ""
+    def street
+      thoroughfare_data.is_a?(Hash) ? thoroughfare_data['ThoroughfareName'] : ""
     end
 
-    def premise_name
-      address_details['Locality']['Premise']['PremiseName']
+    def street_number
+      premise.is_a?(Hash) ? premise.fetch('PremiseNumber', "") : ""
     end
 
-    def street
-      thoroughfare_data && thoroughfare_data['ThoroughfareName']
+    def premise_name
+      premise.is_a?(Hash) ? premise.fetch('PremiseName', "") : ""
     end
 
-    def street_number
-      thoroughfare_data && thoroughfare_data['Premise'] && thoroughfare_data['Premise']['PremiseNumber']
+    def postal_code
+      return "" unless premise.is_a?(Hash)
+      find_in_hash(premise, 'PostalCode', 'PostalCodeNumber') || ""
     end
 
     def kind
@@ -93,42 +238,55 @@ module Geocoder::Result
 
     private # ----------------------------------------------------------------
 
-    def thoroughfare_data
-      locality_data && locality_data['Thoroughfare']
+    def top_level_locality
+      find_in_hash(@data, *ADDRESS_DETAILS, 'Locality')
     end
 
-    def locality_data
-      dependent_locality && subadmin_locality && admin_locality
+    def country_level_locality
+      find_in_hash(@data, *COUNTRY_LEVEL, 'Locality')
     end
 
     def admin_locality
-      address_details && address_details['AdministrativeArea'] &&
-        address_details['AdministrativeArea']['Locality']
+      find_in_hash(@data, *ADMIN_LEVEL, 'Locality')
     end
 
     def subadmin_locality
-      address_details && address_details['AdministrativeArea'] &&
-        address_details['AdministrativeArea']['SubAdministrativeArea'] &&
-        address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']
+      find_in_hash(@data, *SUBADMIN_LEVEL, 'Locality')
     end
 
     def dependent_locality
-      address_details && address_details['AdministrativeArea'] &&
-        address_details['AdministrativeArea']['SubAdministrativeArea'] &&
-        address_details['AdministrativeArea']['SubAdministrativeArea']['Locality'] &&
-        address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']['DependentLocality']
+      find_in_hash(@data, *DEPENDENT_LOCALITY_1) ||
+        find_in_hash(@data, *DEPENDENT_LOCALITY_2)
+    end
+
+    def locality_data
+      dependent_locality || subadmin_locality || admin_locality ||
+        country_level_locality || top_level_locality
+    end
+
+    def thoroughfare_data
+      locality_data['Thoroughfare'] if locality_data.is_a?(Hash)
     end
 
-    def address_details
-      @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['AddressDetails']['Country']
+    def premise
+      if thoroughfare_data.is_a?(Hash)
+        thoroughfare_data['Premise']
+      elsif locality_data.is_a?(Hash)
+        locality_data['Premise']
+      end
     end
 
-    def sub_state_city
-      if !sub_state.empty? and address_details and address_details['AdministrativeArea']['SubAdministrativeArea'].has_key? 'Locality'
-        address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']['LocalityName'] || ""
-      else
-        ""
+    def find_in_hash(source, *keys)
+      key = keys.shift
+      result = source[key]
+
+      if keys.empty?
+        return result
+      elsif !result.is_a?(Hash)
+        return nil
       end
+
+      find_in_hash(result, *keys)
     end
   end
 end
diff --git a/lib/geocoder/sql.rb b/lib/geocoder/sql.rb
index 71ea96f..6bca8a6 100644
--- a/lib/geocoder/sql.rb
+++ b/lib/geocoder/sql.rb
@@ -44,13 +44,13 @@ module Geocoder
     end
 
     def within_bounding_box(sw_lat, sw_lng, ne_lat, ne_lng, lat_attr, lon_attr)
-      spans = "#{lat_attr} BETWEEN #{sw_lat} AND #{ne_lat} AND "
+      spans = "#{lat_attr} BETWEEN #{sw_lat.to_f} AND #{ne_lat.to_f} AND "
       # handle box that spans 180 longitude
       if sw_lng.to_f > ne_lng.to_f
-        spans + "(#{lon_attr} BETWEEN #{sw_lng} AND 180 OR " +
-        "#{lon_attr} BETWEEN -180 AND #{ne_lng})"
+        spans + "(#{lon_attr} BETWEEN #{sw_lng.to_f} AND 180 OR " +
+        "#{lon_attr} BETWEEN -180 AND #{ne_lng.to_f})"
       else
-        spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
+        spans + "#{lon_attr} BETWEEN #{sw_lng.to_f} AND #{ne_lng.to_f}"
       end
     end
 
diff --git a/lib/geocoder/util.rb b/lib/geocoder/util.rb
new file mode 100644
index 0000000..30ca5cd
--- /dev/null
+++ b/lib/geocoder/util.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Geocoder
+  module Util
+    #
+    # Recursive version of Hash#merge!
+    #
+    # Adds the contents of +h2+ to +h1+,
+    # merging entries in +h1+ with duplicate keys with those from +h2+.
+    #
+    # Compared with Hash#merge!, this method supports nested hashes.
+    # When both +h1+ and +h2+ contains an entry with the same key,
+    # it merges and returns the values from both hashes.
+    #
+    #    h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
+    #    h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
+    #    recursive_hash_merge(h1, h2)   #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
+    #
+    # Simply using Hash#merge! would return
+    #
+    #    h1.merge!(h2)    #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
+    #
+    def self.recursive_hash_merge(h1, h2)
+      h1.merge!(h2) do |_key, oldval, newval|
+        oldval.class == h1.class ? self.recursive_hash_merge(oldval, newval) : newval
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/version.rb b/lib/geocoder/version.rb
index 14ea8f9..418aab9 100644
--- a/lib/geocoder/version.rb
+++ b/lib/geocoder/version.rb
@@ -1,3 +1,3 @@
 module Geocoder
-  VERSION = "1.5.1"
+  VERSION = "1.8.1"
 end
diff --git a/lib/hash_recursive_merge.rb b/lib/hash_recursive_merge.rb
deleted file mode 100644
index 163566e..0000000
--- a/lib/hash_recursive_merge.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# 
-# = Hash Recursive Merge
-# 
-# Merges a Ruby Hash recursively, Also known as deep merge.
-# Recursive version of Hash#merge and Hash#merge!.
-# 
-# Category::    Ruby
-# Package::     Hash
-# Author::      Simone Carletti <weppos@weppos.net>
-# Copyright::   2007-2008 The Authors
-# License::     MIT License
-# Link::        http://www.simonecarletti.com/
-# Source::      http://gist.github.com/gists/6391/
-#
-module HashRecursiveMerge
-
-  #
-  # Recursive version of Hash#merge!
-  # 
-  # Adds the contents of +other_hash+ to +hsh+, 
-  # merging entries in +hsh+ with duplicate keys with those from +other_hash+.
-  # 
-  # Compared with Hash#merge!, this method supports nested hashes.
-  # When both +hsh+ and +other_hash+ contains an entry with the same key,
-  # it merges and returns the values from both arrays.
-  # 
-  #    h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
-  #    h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
-  #    h1.rmerge!(h2)   #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
-  #    
-  # Simply using Hash#merge! would return
-  # 
-  #    h1.merge!(h2)    #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
-  # 
-  def rmerge!(other_hash)
-    merge!(other_hash) do |key, oldval, newval|
-      oldval.class == self.class ? oldval.rmerge!(newval) : newval
-    end
-  end
-
-  #
-  # Recursive version of Hash#merge
-  # 
-  # Compared with Hash#merge!, this method supports nested hashes.
-  # When both +hsh+ and +other_hash+ contains an entry with the same key,
-  # it merges and returns the values from both arrays.
-  # 
-  # Compared with Hash#merge, this method provides a different approch
-  # for merging nasted hashes.
-  # If the value of a given key is an Hash and both +other_hash+ abd +hsh
-  # includes the same key, the value is merged instead replaced with
-  # +other_hash+ value.
-  # 
-  #    h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
-  #    h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
-  #    h1.rmerge(h2)    #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
-  #    
-  # Simply using Hash#merge would return
-  # 
-  #    h1.merge(h2)     #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
-  # 
-  def rmerge(other_hash)
-    r = {}
-    merge(other_hash) do |key, oldval, newval|
-      r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
-    end
-  end
-
-end
-
-
-class Hash
-  include HashRecursiveMerge
-end
diff --git a/lib/maxmind_database.rb b/lib/maxmind_database.rb
index 471195a..9330f03 100644
--- a/lib/maxmind_database.rb
+++ b/lib/maxmind_database.rb
@@ -96,9 +96,9 @@ module Geocoder
 
     def archive_url_path(package)
       {
-        geolite_country_csv: "GeoIPCountryCSV.zip",
-        geolite_city_csv: "GeoLiteCity_CSV/GeoLiteCity-latest.zip",
-        geolite_asn_csv: "asnum/GeoIPASNum2.zip"
+        geolite_country_csv: "GeoLite2-Country-CSV.zip",
+        geolite_city_csv: "GeoLite2-City-CSV.zip",
+        geolite_asn_csv: "GeoLite2-ASN-CSV.zip"
       }[package]
     end
 

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/easting_northing.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/cache_stores/base.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/cache_stores/generic.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/cache_stores/redis.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/abstract_api.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/amazon_location_service.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/geoapify.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/ipbase.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/ipgeolocation.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/ipqualityscore.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/ipregistry.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/melissa_street.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/nationaal_georegister_nl.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/osmnames.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/photon.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/twogis.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/uk_ordnance_survey_names.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/abstract_api.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/amazon_location_service.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/geoapify.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/ipbase.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/ipgeolocation.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/ipqualityscore.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/ipregistry.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/melissa_street.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/nationaal_georegister_nl.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/osmnames.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/photon.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/twogis.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/uk_ordnance_survey_names.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/util.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/geocoder-1.8.1.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/lookups/geocoder_us.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/geocoder/results/geocoder_us.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/hash_recursive_merge.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/geocoder-1.5.1.gemspec

No differences were encountered in the control files

More details

Full run details