geocoder-1.5.1/ 0000755 0000041 0000041 00000000000 13503210114 013323 5 ustar www-data www-data geocoder-1.5.1/README.md 0000644 0000041 0000041 00000104150 13503210114 014603 0 ustar www-data www-data Geocoder
========
**A complete geocoding solution for Ruby.**
[](http://badge.fury.io/rb/geocoder)
[](https://codeclimate.com/github/alexreisner/geocoder)
[](https://travis-ci.org/alexreisner/geocoder)
[](https://github.com/alexreisner/geocoder/issues)
[](https://opensource.org/licenses/MIT)
Key features:
* Forward and reverse geocoding, and 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.
Table of Contents
-----------------
Basic Features:
* [Basic Search](#basic-search)
* [Geocoding Objects](#geocoding-objects)
* [Geospatial Database Queries](#geospatial-database-queries)
* [Geocoding HTTP Requests](#geocoding-http-requests)
* [Geocoding Service ("Lookup") Configuration](#geocoding-service-lookup-configuration)
Advanced Features:
* [Performance and Optimization](#performance-and-optimization)
* [Advanced Model Configuration](#advanced-model-configuration)
* [Advanced Database Queries](#advanced-database-queries)
* [Geospatial Calculations](#geospatial-calculations)
* [Batch Geocoding](#batch-geocoding)
* [Testing](#testing)
* [Error Handling](#error-handling)
* [Command Line Interface](#command-line-interface)
The Rest:
* [Technical Discussions](#technical-discussions)
* [Troubleshooting](#troubleshooting)
* [Known Issues](#known-issues)
* [Reporting Issues](#reporting-issues)
* [Contributing](#contributing)
See Also:
* [Guide to Geocoding APIs](https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md) (formerly part of this README)
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
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"
You can also look up the location of an IP addresses:
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).
Geocoding Objects
-----------------
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
**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
This adds a `geocode` method which you can invoke via callback:
after_validation :geocode
Reverse geocoding (given lat/lon coordinates, find an address) is similar:
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
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.
### One More Thing for MongoDB!
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
### 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]
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]
So, be careful.
### Use Outside of Rails
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
Geospatial Database Queries
---------------------------
### For ActiveRecord models:
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
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
### For MongoDB-backed models:
Please do not use Geocoder's `near` method. Instead use MongoDB's built-in [geospatial query language](https://docs.mongodb.org/manual/reference/command/geoNear/), which is faster. Mongoid also provides [a DSL](http://mongoid.github.io/en/mongoid/docs/querying.html#geo_near) for geospatial queries.
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
**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).
Note that these methods will usually return `nil` in test and development environments because things like "localhost" and "0.0.0.0" are not geocodable IP addresses.
Geocoding Service ("Lookup") Configuration
------------------------------------------
Geocoder supports a variety of street and IP address geocoding services. The default lookups are `:nominatim` for street addresses and `:ipinfo_io` for IP addresses. Please see the [API Guide](https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md) for details on specific geocoding services (not all settings are supported by all services).
To create a Rails initializer with sample configuration:
rails generate geocoder:config
Some common options are:
# config/initializers/geocoder.rb
Geocoder.configure(
# street address geocoding service (default :nominatim)
lookup: :yandex,
# IP address geocoding service (default :ipinfo_io)
ip_lookup: :maxmind,
# to use an API key:
api_key: "...",
# geocoding service request timeout, in seconds (default 3):
timeout: 5,
# set default units to kilometers:
units: :km,
# caching (see [below](#caching) for details):
cache: Redis.new,
cache_prefix: "..."
)
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]])
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"})
# 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(
timeout: 2,
cache: Redis.new,
yandex: {
api_key: "...",
timeout: 5
},
baidu: {
api_key: "..."
},
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.
Performance and Optimization
----------------------------
### Database Indices
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]
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
### Avoiding Unnecessary API Requests
Geocoding only needs to be performed under certain conditions. To avoid unnecessary work (and quota usage) you will probably want to geocode an object only when:
* an address is present
* the address has been changed since last save (or it has never been saved)
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? }
### 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)
This example uses Redis, but the cache store can be any object that supports these methods:
* `store#[](key)` or `#get` or `#read` - retrieves a value
* `store#[]=(key, value)` or `#set` or `#write` - stores a value
* `store#del(url)` - deletes a value
* `store#keys` - (Optional) Returns array of keys. Used if you wish to expire the entire cache (see below).
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).
You can also set a custom prefix to be used for cache keys:
Geocoder.configure(cache_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)}
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.
For an example of a cache store with URL expiry, please see examples/autoexpire_cache.rb
_Before you implement caching in your app please be sure that doing so does not violate the Terms of Service for your geocoding service._
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
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
To specify geocoding parameters in your model:
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 }
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
Every `Geocoder::Result` object, `result`, provides the following data:
* `result.latitude` - float
* `result.longitude` - float
* `result.coordinates` - array of the above two in the form of `[lat,lon]`
* `result.address` - string
* `result.city` - string
* `result.state` - string
* `result.state_code` - string
* `result.postal_code` - string
* `result.country` - string
* `result.country_code` - string
Most APIs return other data in addition to these globally-supported attributes. To directly access the full response, call the `#data` method of any Geocoder::Result object. See the [API Guide](https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md) for links to documentation for all geocoding services.
### Forward and Reverse Geocoding in the Same Model
You can apply both forward and reverse geocoding to the same model (i.e. users can supply an address or coordinates and Geocoder fills in whatever's missing) but you'll need to provide two different address methods:
* one for storing the fetched address (when reverse geocoding)
* one for providing an address to use when fetching coordinates (forward geocoding)
For example:
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
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
Advanced Database Queries
-------------------------
*The following apply to ActiveRecord only. For MongoDB, please use the built-in geospatial features.*
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)
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)
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)
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)
### Distance and Bearing
When you run a geospatial query, the returned objects have two attributes added:
* `obj.distance` - number of miles from the search point to this object
* `obj.bearing` - direction from the search point to this object
Results are automatically sorted by distance from the search point, closest to farthest. Bearing is given as a number of degrees clockwise from due north, for example:
* `0` - due north
* `180` - due south
* `90` - due east
* `270` - due west
* `230.1` - southwest
* `359.9` - almost due north
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"
_Note: when running queries on SQLite, `distance` and `bearing` are provided for consistency only. They are not very accurate._
For more advanced geospatial querying, please see the [rgeo gem](https://github.com/rgeo/rgeo).
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)
# 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!
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
If you need reverse geocoding instead, call the task with REVERSE=true:
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
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
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)
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'
}
]
)
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'
}
]
)
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.
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])
You can also do this to raise all exceptions:
Geocoder.configure(always_raise: :all)
The raise-able exceptions are:
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.
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
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.
Technical Discussions
---------------------
### Distance Queries in SQLite
SQLite's lack of trigonometric functions requires an alternate implementation of the `near` scope. When using SQLite, Geocoder will automatically use a less accurate algorithm for finding objects near a given point. Results of this algorithm should not be trusted too much as it will return objects that are outside the given radius, along with inaccurate distance and bearing calculations.
There are few options for finding objects near a given point in SQLite without installing extensions:
1. Use a square instead of a circle for finding nearby points. For example, if you want to find points near 40.71, 100.23, search for objects with latitude between 39.71 and 41.71 and longitude between 99.23 and 101.23. One degree of latitude or longitude is at most 69 miles so divide your radius (in miles) by 69.0 to get the amount to add and subtract from your center coordinates to get the upper and lower bounds. The results will not be very accurate (you'll get points outside the desired radius), but you will get all the points within the required radius.
2. Load all objects into memory and compute distances between them using the `Geocoder::Calculations.distance_between` method. This will produce accurate results but will be very slow (and use a lot of memory) if you have a lot of objects in your database.
3. If you have a large number of objects (so you can't use approach #2) and you need accurate results (better than approach #1 will give), you can use a combination of the two. Get all the objects within a square around your center point, and then eliminate the ones that are too far away using `Geocoder::Calculations.distance_between`.
Because Geocoder needs to provide this functionality as a scope, we must go with option #1, but feel free to implement #2 or #3 if you need more accuracy.
### Numeric Data Types and Precision
Geocoder works with any numeric data type (e.g. float, double, decimal) on which trig (and other mathematical) functions can be performed.
A summary of the relationship between geographic precision and the number of decimal places in latitude and longitude degree values is available on [Wikipedia](http://en.wikipedia.org/wiki/Decimal_degrees#Accuracy). As an example: at the equator, latitude/longitude values with 4 decimal places give about 11 metres precision, whereas 5 decimal places gives roughly 1 metre precision.
Troubleshooting
---------------
### Mongoid
If you get one of these errors:
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.
### ActiveRecord
A lot of debugging time can be saved by understanding how Geocoder works with ActiveRecord. When you use the `near` scope or the `nearbys` method of a geocoded object, Geocoder creates an ActiveModel::Relation object which adds some attributes (eg: distance, bearing) to the SELECT clause. It also adds a condition to the WHERE clause to check that distance is within the given radius. Because the SELECT clause is modified, anything else that modifies the SELECT clause may produce strange results, for example:
* using the `pluck` method (selects only a single column)
* specifying another model through `includes` (selects columns from other tables)
### Geocoding is Slow
With most lookups, addresses are translated into coordinates via an API that must be accessed through the Internet. These requests are subject to the same bandwidth constraints as every other HTTP request, and will vary in speed depending on network conditions. Furthermore, many of the services supported by Geocoder are free and thus very popular. Often they cannot keep up with demand and their response times become quite bad.
If your application requires quick geocoding responses you will probably need to pay for a non-free service, or--if you're doing IP address geocoding--use a lookup that doesn't require an external (network-accessed) service.
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.
### 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("..."))
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("..."))
Known Issues
------------
### Using `count` with Rails 4.1+
Due to [a change in ActiveRecord's `count` method](https://github.com/rails/rails/pull/10710) you will need to use `count(:all)` to explicitly count all columns ("*") when using a `near` scope. Using `near` and calling `count` with no argument will cause exceptions in many cases.
### Using `near` with `includes`
You cannot use the `near` scope with another scope that provides an `includes` option because the `SELECT` clause generated by `near` will overwrite it (or vice versa).
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)
# 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.
### Using `near` with objects close to the 180th meridian
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.
geocoder-1.5.1/bin/ 0000755 0000041 0000041 00000000000 13503210114 014073 5 ustar www-data www-data geocoder-1.5.1/bin/geocode 0000755 0000041 0000041 00000000104 13503210114 015421 0 ustar www-data www-data #!/usr/bin/env ruby
require 'geocoder/cli'
Geocoder::Cli.run ARGV
geocoder-1.5.1/CHANGELOG.md 0000644 0000041 0000041 00000060363 13503210114 015144 0 ustar www-data www-data Changelog
=========
Major changes to Geocoder for each release. Please see the Git log for complete list of changes.
1.5.1 (2019 Jan 23)
-------------------
* Add support for :tencent lookup (thanks github.com/Anders-E).
* Add support for :smarty_streets international API (thanks github.com/ankane).
* Remove :mapzen lookup.
1.5.0 (2018 Jul 31)
-------------------
* Drop support for Ruby <2.0.
* Change default street address lookup from :google to :nominatim.
* Cache keys no longer include API credentials. This means many entries in existing cache implementations will be invalidated.
* Test lookup fixtures should now return `coordinates` and NOT `latitude`/`longitude` attributes (see #1258). This may break some people's tests.
* Add support for :ip2location lookup (thanks github.com/ip2location).
* Remove :ovi and :okf lookups.
1.4.9 (2018 May 27)
-------------------
* Fix regression in :geoip2 lookup.
* Add support for Postcodes.io lookup (thanks github.com/sledge909).
1.4.8 (2018 May 21)
-------------------
* Change default IP address lookup from :freegeoip to :ipinfo_io.
* Add support for :ipstack lookup (thanks github.com/Heath101).
* Fix incompatibility with redis-rb gem v4.0.
1.4.7 (2018 Mar 13)
-------------------
* Allow HTTP protocol for Nominatim.
1.4.6 (2018 Feb 28)
-------------------
* Add support for :ipdata_co lookup (thanks github.com/roschaefer).
* Update for Rails 5.2 compatibility (thanks github.com/stevenharman).
1.4.5 (2017 Nov 29)
-------------------
* Add support for :pickpoint lookup (thanks github.com/cylon-v).
* Add support for :db_ip_com lookup (thanks github.com/cv).
* Change FreeGeoIP host to freegeoip.net.
* Allow search radius to be a symbol representing a column in DB (thanks github.com/leonelgalan).
* Add support for new parameters and improved error handling for several lookups.
* Fix bug in SQL when searching for objects across 180th meridian.
1.4.4 (2017 May 17)
-------------------
* Use HTTPS by default for :freegeoip (thanks github.com/mehanoid).
* Add support for :amap lookup (thanks github.com/pzgz).
1.4.3 (2017 Feb 7)
-------------------
* Add :google_places_search lookup (thanks github.com/waruboy).
1.4.2 (2017 Jan 31)
-------------------
* Fix bug that caused Model.near to return an incorrect query in some cases.
1.4.1 (2016 Dec 2)
-------------------
* Add :location_iq lookup (thanks github.com/aleemuddin13 and glsignal).
* Add :ban_data_gouv_fr lookup (thanks github.com/JulianNacci).
* Fix :mapbox results when server returns no context (thanks github.com/jcmuller).
* Deprecate methods in Geocoder::Calculations: to_kilometers, to_miles, to_nautical_miles, mi_in_km, km_in_mi, km_in_nm, nm_in_km.
1.4.0 (2016 Sep 8)
-------------------
* Only consider an object geocoded if both lat and lon are present (previously was sufficient to have only one of the two) (thanks github.com/mltsy).
* Add support in :geocodio lookup for Canadian addresses (thanks github.com/bolandrm).
* Add support for SQLite extensions, if present (thanks github.com/stevecj).
* Always include Geocoder in Rack::Request, if defined (thanks github.com/wjordan).
1.3.7 (2016 Jun 9)
-------------------
* Fix Ruby 1.9, 2.0 incompatibility (thanks github.com/ebouchut).
* Update SmartyStreets zipcode API endpoint (thanks github.com/jeffects).
* Catch network errors (thanks github.com/sas1ni69).
1.3.6 (2016 May 31)
-------------------
* Fix Sinatra support broken in 1.3.5.
1.3.5 (2016 May 27)
-------------------
* Fix Rails 5 ActionDispatch include issue (thanks github.com/alepore).
* Fix bug that caused :esri lookup to ignore certain configured parameters (thanks github.com/aaronpk).
* Add reverse geocoding support to :pelias/:mapzen (thanks github.com/shinyaK14).
* Add support for custom host with :telize (thanks github.com/jwaldrip).
* Change the way :esri lookup generates cache keys for improved performance (thanks github.com/aaronpk).
* Improve HTTPS connections with :google (thanks github.com/jlhonora).
1.3.4 (2016 Apr 14)
-------------------
* Update :freegeoip host (old one is down).
* Add IP lookup :ipapi_com (thanks github.com/piotrgorecki).
1.3.3 (2016 Apr 4)
------------------
* Fix incorrect gem version number.
1.3.2 (2016 Apr 1)
------------------
* Remove :yahoo lookup (service was discontinued Mar 31) (thanks github.com/galiat).
* Add support for LatLon.io service (thanks github.com/evanmarks).
* Add support for IpInfo.io service (thanks github.com/rehan, akostyuk).
* Add support for Pelias/Mapzen service (thanks github.com/RealScout).
1.3.1 (2016 Feb 20)
-------------------
* Warn about upcoming discontinuation of :yahoo lookup (thanks github.com/galiat).
* Add #viewport method to results that return viewport data (thanks github.com/lime).
1.3.0 (2016 Jan 31)
-------------------
* Lazy load lookups to reduce memory footprint (thanks github.com/TrangPham).
* Add :geoportail_lu lookup (Luxembourg only) (thanks github.com/mdebo).
* Maxmind local query performance improvement (thanks github.com/vojtad).
* Remove deprecated Mongo near query methods (please use Mongo-native methods instead).
1.2.14 (2015 Dec 27)
--------------------
* Fix bug in :geoip2 lookup (thanks github.com/mromulus).
1.2.13 (2015 Dec 15)
--------------------
* Update :telize IP lookup to reflect new URL (thanks github.com/jfredrickson).
* Add reverse geocode rake task (thanks github.com/FanaHOVA).
* Fix reversed coordinates array with Mapbox (thanks github.com/marcusat).
* Fix missing city name in some cases with ESRI (thanks github.com/roybotnik).
* Prevent re-opening of DB file on every read with :geoip2 (thanks github.com/oogali).
1.2.12 (2015 Oct 29)
--------------------
* Fix Ruby 1.9.3 incompatibility (remove non-existent timeout classes) (thanks github.com/roychri).
1.2.11 (2015 Sep 10)
--------------------
* Fix load issue on Ruby 1.9.3.
1.2.10 (2015 Sep 7)
-------------------
* Force Yandex to use HTTPS (thanks github.com/donbobka).
* Force :google to use HTTPS if API key set.
* Fix out-of-the-box verbosity issues (GH #881).
* Improve timeout mechanism and add exception Geocoder::LookupTimeout (thanks github.com/ankane).
* Deprecate .near and #nearbys for MongoDB-backed models.
1.2.9 (2015 Jun 12)
-------------------
* Don't cache unsuccessful responses from Bing (thanks github.com/peteb).
* Show API response when not valid JSON.
* Log each API request.
* Force all SmartyStreets requests to use HTTPS.
1.2.8 (2015 Mar 21)
-------------------
* Add :maxmind_geoip2 lookup (thanks github.com/TrangPham).
* Add ability to force/specify query type (street or IP address) (thanks github.com/TrangPham).
* Add :basic_auth configuration (thanks github.com/TrangPham).
* Add `safe_location` method for Rails controllers (thanks github.com/edslocomb).
* Add :logger configuration (thanks github.com/TrangPham).
* Improve error condition handling with Bing (thanks github.com/TrangPham).
1.2.7 (2015 Jan 24)
-------------------
* DROP SUPPORT for Ruby 1.9.2.
* Use UTF-8 encoding for maxmind_local results (thanks github.com/ellmo).
* Update freegeoip response handling (thanks github.com/hosamaly).
* Update nominatim response handling (thanks github.com/jsantos).
* Update yandex response handling (thanks github.com/wfleming).
* Update geocodio response handling (thanks github.com/getsidewalk).
* Add ability to raise exception when response parsing fails (thanks github.com/spiderpug).
* Fix double-loading of Railtie (thanks github.com/wfleming and zhouguangming).
1.2.6 (2014 Nov 8)
------------------
* Add :geoip2 lookup (thanks github.com/ChristianHoj).
* Add :okf lookup (thanks github.com/kakoni).
* Add :postcode_anywhere_uk lookup (thanks github.com/rob-murray).
* Properly detect IPv6 addresses (thanks github.com/sethherr and github.com/ignatiusreza).
1.2.5 (2014 Sep 12)
-------------------
* Fix bugs in :opencagedata lookup (thanks github.com/duboff and kayakyakr).
* Allow language to be set in model configuration (thanks github.com/viniciusnz).
* Optimize lookup queries when using MaxMind Local (thanks github.com/gersmann).
1.2.4 (2014 Aug 12)
-------------------
* Add ability to specify lat/lon column names with .near scope (thanks github.com/switzersc).
* Add OpenCageData geocoder (thanks github.com/mtmail).
* Remove CloudMade geocoder.
1.2.3 (2014 Jul 11)
-------------------
* Add Telize IP address lookup (thanks github.com/lukeroberts1990).
* Fix bug in Bing reverse geocoding (thanks github.com/nickelser).
1.2.2 (2014 Jun 12)
-------------------
* Add ability to specify language per query (thanks github.com/mkristian).
* Handle Errno::ECONNREFUSED exceptions like TimeoutError exceptions.
* Switch to 'unstructured' query format for Bing API (thanks github.com/lukewendling).
1.2.1 (2014 May 12)
-------------------
* Fix: correctly handle encoding of MaxMind API responses (thanks github.com/hydrozen, gonzoyumo).
* Fixes to :maxmind_local database structure (thanks github.com/krakatoa).
1.2.0 (2014 Apr 16)
-------------------
* DROP SUPPORT for Ruby 1.8.x.
* Add :here lookup (thanks github.com/christoph-buente).
* Add :cloudmade lookup (thanks github.com/spoptchev).
* Add :smarty_streets lookup (thanks github.com/drinks).
* Add :maxmind_local IP lookup (thanks github.com/fernandomm).
* Add :baidu_ip lookup (thanks github.com/yonggu).
* Add :geocodio lookup (thanks github.com/dblock).
* Add :lookup option to `Geocoder.search` and `geocoded_by` (thanks github.com/Bonias).
* Add support for :maxmind_local on JRuby via jgeoip gem (thanks github.com/gxbe).
* Add support for using :maxmind_local with an SQL database, including Rake tasks for downloading CSV data and populating a local DB.
* Add support for character encodings based on Content-type header (thanks github.com/timaro).
* Add :min_radius option to `near` scope (thanks github.com/phallstrom).
* Fix: Yandex city attribute caused exception with certain responses (thanks github.com/dblock).
* Change name of MapQuest config option from :licensed to :open and revert default behavior to be MapQuest data (not OpenStreetMaps).
* Reduce number of Ruby warnings (thanks github.com/exviva).
1.1.9 (2013 Dec 11)
-------------------
* DEPRECATED support for Ruby 1.8.x. Will be dropped in a future version.
* Require API key for MapQuest (thanks github.com/robdiciuccio).
* Add support for geocoder.us and HTTP basic auth (thanks github.com/komba).
* Add support for Data Science Toolkit lookup (thanks github.com/ejhayes).
* Add support for Baidu (thanks github.com/mclee).
* Add Geocoder::Calculations.random_point_near method (thanks github.com/adambray).
* Fix: #nearbys method with Mongoid (thanks github.com/pascalbetz).
* Fix: bug in FreeGeoIp lookup that was preventing exception from being raised when configured cache was unavailable.
1.1.8 (2013 Apr 22)
-------------------
* Fix bug in ESRI lookup that caused an exception on load in environments without rack/utils.
1.1.7 (2013 Apr 21)
-------------------
* Add support for Ovi/Nokia API (thanks github.com/datenimperator).
* Add support for ESRI API (thanks github.com/rpepato).
* Add ability to omit distance and bearing from SQL select clause (thanks github.com/nicolasdespres).
* Add support for caches that use read/write methods (thanks github.com/eskil).
* Add support for nautical miles (thanks github.com/vanboom).
* Fix: bug in parsing of MaxMind responses.
* Fix: bugs in query regular expressions (thanks github.com/boone).
* Fix: various bugs in MaxMind implementation.
* Fix: don't require a key for MapQuest.
* Fix: bug in handling of HTTP_X_FORWARDED_FOR header (thanks github.com/robdimarco).
1.1.6 (2012 Dec 24)
-------------------
* Major changes to configuration syntax which allow for API-specific config options. Old config syntax is now DEPRECATED.
* Add support for MaxMind API (thanks github.com/gonzoyumo).
* Add optional Geocoder::InvalidApiKey exception for bad API credentials (Yahoo, Yandex, Bing, and Maxmind). Warn when bad key and exception not set in Geocoder.configure(:always_raise => [...]).
* Add support for X-Real-IP and X-Forwarded-For headers (thanks github.com/konsti).
* Add support for custom Nominatim host config: Geocoder.configure(:nominatim => {:host => "..."}).
* Raise exception when required API key is missing or incorrect format.
* Add support for Google's :region and :components parameters (thanks to github.com/tomlion).
* Fix: string escaping bug in OAuth lib (thanks github.com/m0thman).
* Fix: configured units were not always respected in SQL queries.
* Fix: in #nearbys, don't try to exclude self if not yet persisted.
* Fix: bug with cache stores that provided #delete but not #del.
* Change #nearbys so that it returns nil instead of [] when object is not geocoded.
1.1.5 (2012 Nov 9)
------------------
* Replace support for old Yahoo Placefinder with Yahoo BOSS (thanks github.com/pwoltman).
* Add support for actual Mapquest API (was previously just a proxy for Nominatim), including the paid service (thanks github.com/jedschneider).
* Add support for :select => :id_only option to near scope.
* Treat a given query as blank (don't do a lookup) if coordinates are given but latitude or longitude is nil.
* Speed up 'near' queries by adding bounding box condition (thanks github.com/mlandauer).
* Fix: don't redefine Object#hash in Yahoo result object (thanks github.com/m0thman).
1.1.4 (2012 Oct 2)
------------------
* Deprecate Geocoder::Result::Nominatim#class and #type methods. Use #place_class and #place_type instead.
* Add support for setting arbitrary parameters in geocoding request URL.
* Add support for Google's :bounds parameter (thanks to github.com/rosscooperman and github.com/peterjm for submitting suggestions).
* Add support for :select => :geo_only option to near scope (thanks github.com/gugl).
* Add ability to omit ORDER BY clause from .near scope (pass option :order => false).
* Fix: error on Yahoo lookup due to API change (thanks github.com/kynesun).
* Fix: problem with Mongoid field aliases not being respected.
* Fix: :exclude option to .near scope when primary key != :id (thanks github.com/smisml).
* Much code refactoring (added Geocoder::Query class and Geocoder::Sql module).
1.1.3 (2012 Aug 26)
-------------------
* Add support for Mapquest geocoding service (thanks github.com/razorinc).
* Add :test lookup for easy testing of apps using Geocoder (thanks github.com/mguterl).
* Add #precision method to Yandex results (thanks github.com/gemaker).
* Add support for raising :all exceptions (thanks github.com/andyvb).
* Add exceptions for certain Google geocoder responses (thanks github.com/andyvb).
* Add Travis-CI integration (thanks github.com/petergoldstein).
* Fix: unit config was not working with SQLite (thanks github.com/balvig).
* Fix: get tests to pass under Jruby (thanks github.com/petergoldstein).
* Fix: bug in distance_from_sql method (error occurred when coordinates not found).
* Fix: incompatibility with Mongoid 3.0.x (thanks github.com/petergoldstein).
1.1.2 (2012 May 24)
-------------------
* Add ability to specify default units and distance calculation method (thanks github.com/abravalheri).
* Add new (optional) configuration syntax (thanks github.com/abravalheri).
* Add support for cache stores that provide :get and :set methods.
* Add support for custom HTTP request headers (thanks github.com/robotmay).
* Add Result#cache_hit attribute (thanks github.com/s01ipsist).
* Fix: rake geocode:all wasn't properly loading namespaced classes.
* Fix: properly recognize IP addresses with ::ffff: prefix (thanks github.com/brian-ewell).
* Fix: avoid exception during calculations when coordinates not known (thanks github.com/flori).
1.1.1 (2012 Feb 16)
-------------------
* Add distance_from_sql class method to geocoded class (thanks github.com/dwilkie).
* Add OverQueryLimitError and raise when relevant for Google lookup.
* Fix: don't cache API data if response indicates an error.
* Fix: within_bounding_box now uses correct lat/lon DB columns (thanks github.com/kongo).
* Fix: error accessing city in some cases with Yandex result (thanks github.com/kor6n and sld).
1.1.0 (2011 Dec 3)
------------------
* A block passed to geocoded_by is now always executed, even if the geocoding service returns no results. This means you need to make sure you have results before trying to assign data to your object.
* Fix issues with joins and row counts (issues #49, 86, and 108) by not using GROUP BY clause with ActiveRecord scopes.
* Fix incorrect object ID when join used (fixes issue #140).
* Fix calculation of bounding box which spans 180th meridian (thanks github.com/hwuethrich).
* Add within_bounding_box scope for ActiveRecord-based models (thanks github.com/gavinhughes and dbloete).
* Add option to raise Geocoder::OverQueryLimitError for Google geocoding service.
* Add support for Nominatim geocoding service (thanks github.com/wranglerdriver).
* Add support for API key to Geocoder.ca geocoding service (thanks github.com/ryanLonac).
* Add support for state to Yandex results (thanks github.com/tipugin).
1.0.5 (2011 Oct 26)
-------------------
* Fix error with `rake assets:precompile` (thanks github.com/Sush).
* Fix HTTPS support (thanks github.com/rsanheim).
* Improve cache interface.
1.0.4 (2011 Sep 18)
-------------------
* Remove klass method from rake task, which could conflict with app methods (thanks github.com/mguterl).
1.0.3 (2011 Sep 17)
-------------------
* Add support for Google Premier geocoding service (thanks github.com/steveh).
* Update Google API URL (thanks github.com/soorajb).
* Allow rescue from timeout with FreeGeoIP (thanks github.com/lukeledet).
* Fix: rake assets:precompile (Rails 3.1) not working in some situations.
* Fix: stop double-adjusting units when using kilometers (thanks github.com/hairyheron).
1.0.2 (2011 June 25)
--------------------
* Add support for MongoMapper (thanks github.com/spagalloco).
* Fix: user-specified coordinates field wasn't working with Mongoid (thanks github.com/thisduck).
* Fix: invalid location given to near scope was returning all results (Active Record) or error (Mongoid) (thanks github.com/ogennadi).
1.0.1 (2011 May 17)
-------------------
* Add option to not rescue from certain exceptions (thanks github.com/ahmedrb).
* Fix STI child/parent geocoding bug (thanks github.com/ogennadi).
* Other bugfixes.
1.0.0 (2011 May 9)
------------------
* Add command line interface.
* Add support for local proxy (thanks github.com/Olivier).
* Add support for Yandex.ru geocoding service.
* Add support for Bing geocoding service (thanks github.com/astevens).
* Fix single table inheritance bug (reported by github.com/enrico).
* Fix bug when Google result supplies no city (thanks github.com/jkeen).
0.9.13 (2011 Apr 11)
--------------------
* Fix "can't find special index: 2d" error when using Mongoid with Ruby 1.8.
0.9.12 (2011 Apr 6)
-------------------
* Add support for Mongoid.
* Add bearing_to/from methods to geocoded objects.
* Improve SQLite's distance calculation heuristic.
* Fix: Geocoder::Calculations.geographic_center was modifying its argument in-place (reported by github.com/joelmats).
* Fix: sort 'near' query results by distance when using SQLite.
* Clean up input: search for coordinates as a string with space after comma yields zero results from Google. Now we get rid of any such space before sending the query.
* DEPRECATION: Geocoder.near should not take :limit or :offset options.
* DEPRECATION: Change argument format of all methods that take lat/lon as separate arguments. Now you must pass the coordinates as an array [lat,lon], but you may alternatively pass a address string (will look up coordinates) or a geocoded object (or any object that implements a to_coordinates method which returns a [lat,lon] array).
0.9.11 (2011 Mar 25)
--------------------
* Add support for result caching.
* Add support for Geocoder.ca geocoding service.
* Add +bearing+ attribute to objects returned by geo-aware queries (thanks github.com/matellis).
* Add config setting: language.
* Add config settings: +use_https+, +google_api_key+ (thanks github.com/svesely).
* DEPRECATION: Geocoder.search now returns an array instead of a single result.
* DEPRECATION: obj.nearbys second argument is now an options hash (instead of units). Please change obj.nearbys(20, :km) to: obj.nearbys(20, :units => :km).
0.9.10 (2011 Mar 9)
-------------------
* Fix broken scopes (github.com/mikepinde).
* Fix broken Ruby 1.9 and JRuby compatibility (don't require json gem).
0.9.9 (2011 Mar 9)
------------------
* Add support for IP address geocoding via FreeGeoIp.net.
* Add support for Yahoo PlaceFinder geocoding API.
* Add support for custom geocoder data handling by passing a block to geocoded_by or reverse_geocoded_by.
* Add Rack::Request#location method for geocoding user's IP address.
* Change gem name to geocoder (no more rails-geocoder).
* Gem now works outside of Rails.
* DEPRECATION: +fetch_coordinates+ no longer takes an argument.
* DEPRECATION: +fetch_address+ no longer takes an argument.
* DEPRECATION: Geocoder.search now returns a single result instead of an array.
* DEPRECATION: fetch_coordinates! has been superceded by +geocode+ (then save your object manually).
* DEPRECATION: fetch_address! has been superceded by +reverse_geocode+ (then save your object manually).
* Fix: don't die when trying to get coordinates with a nil address (github.com/zmack).
0.9.8 (2011 Feb 8)
------------------
* Include geocode:all Rake task in gem (was missing!).
* Add Geocoder.search for access to Google's full response.
* Add ability to configure Google connection timeout.
* Emit warnings on Google connection problems and errors.
* Refactor: insert Geocoder into ActiveRecord via Railtie.
0.9.7 (2011 Feb 1)
------------------
* Add reverse geocoding (+reverse_geocoded_by+).
* Prevent exception (uninitialized constant Geocoder::Net) when net/http not already required (github.com/sleepycat).
* Refactor: split monolithic Geocoder module into several smaller ones.
0.9.6 (2011 Jan 19)
-------------------
* Fix incompatibility with will_paginate gem.
* Include table names in GROUP BY clause of nearby scope to avoid ambiguity in joins (github.com/matchu).
0.9.5 (2010 Oct 15)
-------------------
* Fix broken PostgreSQL compatibility (now 100% compatible).
* Switch from Google's XML to JSON geocoding API.
* Separate Rails 2 and Rails 3-compatible branches.
* Don't allow :conditions hash in 'options' argument to 'nearbys' method (was deprecated in 0.9.3).
0.9.4 (2010 Aug 2)
------------------
* Google Maps API key no longer required (uses geocoder v3).
0.9.3 (2010 Aug 2)
------------------
* Fix incompatibility with Rails 3 RC 1.
* Deprecate 'options' argument to 'nearbys' method.
* Allow inclusion of 'nearbys' in Arel method chains.
0.9.2 (2010 Jun 3)
------------------
* Fix LIMIT clause bug in PostgreSQL (reported by github.com/kenzie).
0.9.1 (2010 May 4)
------------------
* Use scope instead of named_scope in Rails 3.
0.9.0 (2010 Apr 2)
------------------
* Fix bug in PostgreSQL support (caused "PGError: ERROR: column "distance" does not exist"), reported by github.com/developish.
0.8.9 (2010 Feb 11)
-------------------
* Add Rails 3 compatibility.
* Avoid querying Google when query would be an empty string.
0.8.8 (2009 Dec 7)
------------------
* Automatically select a less accurate but compatible distance algorithm when SQLite database detected (fixes SQLite incompatibility).
0.8.7 (2009 Nov 4)
------------------
* Added Geocoder.geographic_center method.
* Replaced _get_coordinates class method with read_coordinates instance method.
0.8.6 (2009 Oct 27)
-------------------
* The fetch_coordinates method now assigns coordinates to attributes (behaves like fetch_coordinates! used to) and fetch_coordinates! both assigns and saves the attributes.
* Added geocode:all rake task.
0.8.5 (2009 Oct 26)
-------------------
* Avoid calling deprecated method from within Geocoder itself.
0.8.4 (2009 Oct 23)
-------------------
* Deprecate find_near class method in favor of +near+ named scope.
0.8.3 (2009 Oct 23)
-------------------
* Update Google URL query string parameter to reflect recent changes in Google's API.
0.8.2 (2009 Oct 12)
-------------------
* Allow a model's geocoder search string method to be something other than an ActiveRecord attribute.
* Clean up documentation.
0.8.1 (2009 Oct 8)
------------------
* Extract XML-fetching code from Geocoder.search and place in Geocoder._fetch_xml (for ease of mocking).
* Add tests for coordinate-fetching instance methods.
0.8.0 (2009 Oct 1)
------------------
First release.
geocoder-1.5.1/LICENSE 0000644 0000041 0000041 00000002043 13503210114 014327 0 ustar www-data www-data Copyright (c) 2009-11 Alex Reisner
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
geocoder-1.5.1/examples/ 0000755 0000041 0000041 00000000000 13503210114 015141 5 ustar www-data www-data geocoder-1.5.1/examples/autoexpire_cache_redis.rb 0000644 0000041 0000041 00000001132 13503210114 022161 0 ustar www-data www-data # 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))
geocoder-1.5.1/examples/autoexpire_cache_dalli.rb 0000644 0000041 0000041 00000002462 13503210114 022147 0 ustar www-data www-data # 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))
geocoder-1.5.1/examples/cache_bypass.rb 0000644 0000041 0000041 00000001750 13503210114 020115 0 ustar www-data www-data # This class allows you to configure how Geocoder should treat errors that occur when
# the cache is not available.
# Configure it like this
# config/initializers/geocoder.rb
# Geocoder.configure(
# :cache => Geocoder::CacheBypass.new(Redis.new)
# )
#
# Depending on the value of @bypass this will either
# raise the exception (true) or swallow it and pretend the cache did not return a hit (false)
#
class Geocoder::CacheBypass
def initialize(target, bypass = true)
@target = target
@bypass = bypass
end
def [](key)
with_bypass { @target[key] }
end
def []=(key, value)
with_bypass(value) { @target[key] = value }
end
def keys
with_bypass([]) { @target.keys }
end
def del(key)
with_bypass { @target.del(key) }
end
private
def with_bypass(return_value_if_exception = nil, &block)
begin
yield
rescue
if @bypass
return_value_if_exception
else
raise # reraise original exception
end
end
end
end geocoder-1.5.1/examples/reverse_geocode_job.rb 0000644 0000041 0000041 00000001750 13503210114 021463 0 ustar www-data www-data # This class implements an ActiveJob job for performing reverse-geocoding
# asynchronously. Example usage:
# if @location.save && @location.address.blank?
# ReverseGeocodeJob.perform_later(@location)
# end
# Be sure to configure the queue adapter in config/application.rb:
# config.active_job.queue_adapter = :sidekiq
# You can read the Rails docs for more information on configuring ActiveJob:
# http://edgeguides.rubyonrails.org/active_job_basics.html
class ReverseGeocodeJob < ActiveJob::Base
queue_as :high
def perform(location)
address = address(location)
if address.present?
location.update(address: address)
end
end
private
def address(location)
Geocoder.address(location.coordinates)
rescue => exception
MonitoringService.notify(exception, location: { id: location.id })
if retryable?(exception)
raise exception
end
end
def retryable?(exception)
exception.is_a?(Timeout::Error) || exception.is_a?(SocketError)
end
end
geocoder-1.5.1/lib/ 0000755 0000041 0000041 00000000000 13503210114 014071 5 ustar www-data www-data geocoder-1.5.1/lib/geocoder.rb 0000644 0000041 0000041 00000002377 13503210114 016216 0 ustar www-data www-data require "geocoder/configuration"
require "geocoder/logger"
require "geocoder/kernel_logger"
require "geocoder/query"
require "geocoder/calculations"
require "geocoder/exceptions"
require "geocoder/cache"
require "geocoder/request"
require "geocoder/lookup"
require "geocoder/ip_address"
require "geocoder/models/active_record" if defined?(::ActiveRecord)
require "geocoder/models/mongoid" if defined?(::Mongoid)
require "geocoder/models/mongo_mapper" if defined?(::MongoMapper)
module Geocoder
##
# Search for information about an address or a set of coordinates.
#
def self.search(query, options = {})
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
query.blank? ? [] : query.execute
end
##
# Look up the coordinates of the given street or IP address.
#
def self.coordinates(address, options = {})
if (results = search(address, options)).size > 0
results.first.coordinates
end
end
##
# Look up the address of the given coordinates ([lat,lon])
# or IP address (string).
#
def self.address(query, options = {})
if (results = search(query, options)).size > 0
results.first.address
end
end
end
# load Railtie if Rails exists
if defined?(Rails)
require "geocoder/railtie"
end
geocoder-1.5.1/lib/tasks/ 0000755 0000041 0000041 00000000000 13503210114 015216 5 ustar www-data www-data geocoder-1.5.1/lib/tasks/geocoder.rake 0000644 0000041 0000041 00000003044 13503210114 017652 0 ustar www-data www-data require "geocoder/models/mongoid"
namespace :geocode do
desc "Geocode all objects without coordinates."
task :all => :environment do
class_name = ENV['CLASS'] || ENV['class']
sleep_timer = ENV['SLEEP'] || ENV['sleep']
batch = ENV['BATCH'] || ENV['batch']
reverse = ENV['REVERSE'] || ENV['reverse']
limit = ENV['LIMIT'] || ENV['limit']
raise "Please specify a CLASS (model)" unless class_name
klass = class_from_string(class_name)
batch = (batch.to_i unless batch.nil?) || 1000
orm = (klass < Geocoder::Model::Mongoid) ? 'mongoid' : 'active_record'
reverse = false unless reverse.to_s.downcase == 'true'
geocode_record = lambda { |obj|
reverse ? obj.reverse_geocode : obj.geocode
obj.save
sleep(sleep_timer.to_f) unless sleep_timer.nil?
}
scope = reverse ? klass.not_reverse_geocoded : klass.not_geocoded
scope = scope.limit(limit) if limit
if orm == 'mongoid'
scope.each do |obj|
geocode_record.call(obj)
end
elsif orm == 'active_record'
if limit
scope.each do |obj|
geocode_record.call(obj)
end
else
scope.find_each(batch_size: batch) do |obj|
geocode_record.call(obj)
end
end
end
end
end
##
# Get a class object from the string given in the shell environment.
# Similar to ActiveSupport's +constantize+ method.
#
def class_from_string(class_name)
parts = class_name.split("::")
constant = Object
parts.each do |part|
constant = constant.const_get(part)
end
constant
end
geocoder-1.5.1/lib/tasks/maxmind.rake 0000644 0000041 0000041 00000003642 13503210114 017524 0 ustar www-data www-data require 'maxmind_database'
namespace :geocoder do
namespace :maxmind do
namespace :geolite do
desc "Download and load/refresh MaxMind GeoLite City data"
task load: [:download, :extract, :insert]
desc "Download MaxMind GeoLite City data"
task :download do
p = MaxmindTask.check_for_package!
MaxmindTask.download!(p, dir: ENV['DIR'] || "tmp/")
end
desc "Extract (unzip) MaxMind GeoLite City data"
task :extract do
p = MaxmindTask.check_for_package!
MaxmindTask.extract!(p, dir: ENV['DIR'] || "tmp/")
end
desc "Load/refresh MaxMind GeoLite City data"
task insert: [:environment] do
p = MaxmindTask.check_for_package!
MaxmindTask.insert!(p, dir: ENV['DIR'] || "tmp/")
end
end
end
end
module MaxmindTask
extend self
def check_for_package!
if %w[city country].include?(p = ENV['PACKAGE'])
return p
else
puts "Please specify PACKAGE=city or PACKAGE=country"
exit
end
end
def download!(package, options = {})
p = "geolite_#{package}_csv".intern
Geocoder::MaxmindDatabase.download(p, options[:dir])
end
def extract!(package, options = {})
begin
require 'zip'
rescue LoadError
puts "Please install gem: rubyzip (>= 1.0.0)"
exit
end
require 'fileutils'
p = "geolite_#{package}_csv".intern
archive_filename = Geocoder::MaxmindDatabase.archive_filename(p)
Zip::File.open(File.join(options[:dir], archive_filename)).each do |entry|
filepath = File.join(options[:dir], entry.name)
if File.exist? filepath
warn "File already exists (#{entry.name}), skipping"
else
FileUtils.mkdir_p(File.dirname(filepath))
entry.extract(filepath)
end
end
end
def insert!(package, options = {})
p = "geolite_#{package}_csv".intern
Geocoder::MaxmindDatabase.insert(p, options[:dir])
end
end
geocoder-1.5.1/lib/geocoder/ 0000755 0000041 0000041 00000000000 13503210114 015660 5 ustar www-data www-data geocoder-1.5.1/lib/geocoder/ip_address.rb 0000644 0000041 0000041 00000001040 13503210114 020315 0 ustar www-data www-data require 'resolv'
module Geocoder
class IpAddress < String
PRIVATE_IPS = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
].map { |ip| IPAddr.new(ip) }.freeze
def internal?
loopback? || private?
end
def loopback?
valid? and !!(self == "0.0.0.0" or self.match(/\A127\./) or self == "::1")
end
def private?
valid? && PRIVATE_IPS.any? { |ip| ip.include?(self) }
end
def valid?
!!((self =~ Resolv::IPv4::Regex) || (self =~ Resolv::IPv6::Regex))
end
end
end
geocoder-1.5.1/lib/geocoder/version.rb 0000644 0000041 0000041 00000000050 13503210114 017665 0 ustar www-data www-data module Geocoder
VERSION = "1.5.1"
end
geocoder-1.5.1/lib/geocoder/cache.rb 0000644 0000041 0000041 00000004007 13503210114 017251 0 ustar www-data www-data module Geocoder
class Cache
def initialize(store, prefix)
@store = store
@prefix = prefix
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
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
end
##
# Delete cache entry for given URL,
# or pass :all to clear all URLs.
#
def expire(url)
if url == :all
if store.respond_to?(:keys)
urls.each{ |u| expire(u) }
else
raise(NoMethodError, "The Geocoder cache store must implement `#keys` for `expire(:all)` to work")
end
else
expire_single_url(url)
end
end
private # ----------------------------------------------------------------
def prefix; @prefix; end
def store; @store; end
##
# Cache key for a given URL.
#
def key_for(url)
[prefix, url].join
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.map{ |k| k[/^#{prefix}(.*)/, 1] }
end
##
# Clean up value before returning. Namely, convert empty string to nil.
# (Some key/value stores return empty string instead of nil.)
#
def interpret(value)
value == "" ? nil : value
end
def expire_single_url(url)
key = key_for(url)
store.respond_to?(:del) ? store.del(key) : store.delete(key)
end
end
end
geocoder-1.5.1/lib/geocoder/query.rb 0000644 0000041 0000041 00000005403 13503210114 017354 0 ustar www-data www-data module Geocoder
class Query
attr_accessor :text, :options
def initialize(text, options = {})
self.text = text
self.options = options
end
def execute
lookup.search(text, options)
end
def to_s
text
end
def sanitized_text
if coordinates?
if text.is_a?(Array)
text.join(',')
else
text.split(/\s*,\s*/).join(',')
end
else
text
end
end
##
# Get a Lookup object (which communicates with the remote geocoding API)
# appropriate to the Query text.
#
def lookup
if !options[:street_address] and (options[:ip_address] or ip_address?)
name = options[:ip_lookup] || Configuration.ip_lookup || Geocoder::Lookup.ip_services.first
else
name = options[:lookup] || Configuration.lookup || Geocoder::Lookup.street_services.first
end
Lookup.get(name)
end
def url
lookup.query_url(self)
end
##
# Is the Query blank? (ie, should we not bother searching?)
# A query is considered blank if its text is nil or empty string AND
# no URL parameters are specified.
#
def blank?
!params_given? and (
(text.is_a?(Array) and text.compact.size < 2) or
text.to_s.match(/\A\s*\z/)
)
end
##
# Does the Query text look like an IP address?
#
# Does not check for actual validity, just the appearance of four
# dot-delimited numbers.
#
def ip_address?
IpAddress.new(text).valid? rescue false
end
##
# Is the Query text a loopback or private IP address?
#
def internal_ip_address?
ip_address? && IpAddress.new(text).internal?
end
##
# Is the Query text a loopback IP address?
#
def loopback_ip_address?
ip_address? && IpAddress.new(text).loopback?
end
##
# Is the Query text a private IP address?
#
def private_ip_address?
ip_address? && IpAddress.new(text).private?
end
##
# Does the given string look like latitude/longitude coordinates?
#
def coordinates?
text.is_a?(Array) or (
text.is_a?(String) and
!!text.to_s.match(/\A-?[0-9\.]+, *-?[0-9\.]+\z/)
)
end
##
# Return the latitude/longitude coordinates specified in the query,
# or nil if none.
#
def coordinates
sanitized_text.split(',') if coordinates?
end
##
# Should reverse geocoding be performed for this query?
#
def reverse_geocode?
coordinates?
end
def language
options[:language]
end
private # ----------------------------------------------------------------
def params_given?
!!(options[:params].is_a?(Hash) and options[:params].keys.size > 0)
end
end
end
geocoder-1.5.1/lib/geocoder/sql.rb 0000644 0000041 0000041 00000010671 13503210114 017011 0 ustar www-data www-data module Geocoder
module Sql
extend self
##
# Distance calculation for use with a database that supports POWER(),
# SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
# ATAN2().
#
# Based on the excellent tutorial at:
# http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
#
def full_distance(latitude, longitude, lat_attr, lon_attr, options = {})
units = options[:units] || Geocoder.config.units
earth = Geocoder::Calculations.earth_radius(units)
"#{earth} * 2 * ASIN(SQRT(" +
"POWER(SIN((#{latitude.to_f} - #{lat_attr}) * PI() / 180 / 2), 2) + " +
"COS(#{latitude.to_f} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " +
"POWER(SIN((#{longitude.to_f} - #{lon_attr}) * PI() / 180 / 2), 2)" +
"))"
end
##
# Distance calculation for use with a database without trigonometric
# functions, like SQLite. Approach is to find objects within a square
# rather than a circle, so results are very approximate (will include
# objects outside the given radius).
#
# Distance and bearing calculations are *extremely inaccurate*. To be
# clear: this only exists to provide interface consistency. Results
# are not intended for use in production!
#
def approx_distance(latitude, longitude, lat_attr, lon_attr, options = {})
units = options[:units] || Geocoder.config.units
dx = Geocoder::Calculations.longitude_degree_distance(30, units)
dy = Geocoder::Calculations.latitude_degree_distance(units)
# sin of 45 degrees = average x or y component of vector
factor = Math.sin(Math::PI / 4)
"(#{dy} * ABS(#{lat_attr} - #{latitude.to_f}) * #{factor}) + " +
"(#{dx} * ABS(#{lon_attr} - #{longitude.to_f}) * #{factor})"
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 "
# 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})"
else
spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
end
end
##
# Fairly accurate bearing calculation. Takes a latitude, longitude,
# and an options hash which must include a :bearing value
# (:linear or :spherical).
#
# For use with a database that supports MOD() and trigonometric functions
# SIN(), COS(), ASIN(), ATAN2().
#
# Based on:
# http://www.beginningspatial.com/calculating_bearing_one_point_another
#
def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
degrees_per_radian = Geocoder::Calculations::DEGREES_PER_RADIAN
case options[:bearing] || Geocoder.config.distances
when :linear
"MOD(CAST(" +
"(ATAN2( " +
"((#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian}), " +
"((#{lat_attr} - #{latitude.to_f}) / #{degrees_per_radian})" +
") * #{degrees_per_radian}) + 360 " +
"AS decimal), 360)"
when :spherical
"MOD(CAST(" +
"(ATAN2( " +
"SIN( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian} ) * " +
"COS( (#{lat_attr}) / #{degrees_per_radian} ), (" +
"COS( (#{latitude.to_f}) / #{degrees_per_radian} ) * SIN( (#{lat_attr}) / #{degrees_per_radian})" +
") - (" +
"SIN( (#{latitude.to_f}) / #{degrees_per_radian}) * COS((#{lat_attr}) / #{degrees_per_radian}) * " +
"COS( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian})" +
")" +
") * #{degrees_per_radian}) + 360 " +
"AS decimal), 360)"
end
end
##
# Totally lame bearing calculation. Basically useless except that it
# returns *something* in databases without trig functions.
#
def approx_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
"CASE " +
"WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
"#{lon_attr} >= #{longitude.to_f}) THEN 45.0 " +
"WHEN (#{lat_attr} < #{latitude.to_f} AND " +
"#{lon_attr} >= #{longitude.to_f}) THEN 135.0 " +
"WHEN (#{lat_attr} < #{latitude.to_f} AND " +
"#{lon_attr} < #{longitude.to_f}) THEN 225.0 " +
"WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
"#{lon_attr} < #{longitude.to_f}) THEN 315.0 " +
"END"
end
end
end
geocoder-1.5.1/lib/geocoder/lookup.rb 0000644 0000041 0000041 00000005065 13503210114 017524 0 ustar www-data www-data require "geocoder/lookups/test"
module Geocoder
module Lookup
extend self
##
# Array of valid Lookup service names.
#
def all_services
street_services + ip_services
end
##
# Array of valid Lookup service names, excluding :test.
#
def all_services_except_test
all_services - [:test]
end
##
# All street address lookup services, default first.
#
def street_services
@street_services ||= [
:location_iq,
:dstk,
:esri,
:google,
:google_premier,
:google_places_details,
:google_places_search,
:bing,
:geocoder_ca,
:geocoder_us,
:yandex,
:nominatim,
:mapbox,
:mapquest,
:opencagedata,
:pelias,
:pickpoint,
:here,
:baidu,
:tencent,
:geocodio,
:smarty_streets,
:postcode_anywhere_uk,
:postcodes_io,
:geoportail_lu,
:ban_data_gouv_fr,
:test,
:latlon,
:amap
]
end
##
# All IP address lookup services, default first.
#
def ip_services
@ip_services ||= [
:baidu_ip,
:freegeoip,
:geoip2,
:maxmind,
:maxmind_local,
:telize,
:pointpin,
:maxmind_geoip2,
:ipinfo_io,
:ipapi_com,
:ipdata_co,
:db_ip_com,
:ipstack,
:ip2location
]
end
attr_writer :street_services, :ip_services
##
# Retrieve a Lookup object from the store.
# Use this instead of Geocoder::Lookup::X.new to get an
# already-configured Lookup object.
#
def get(name)
@services = {} unless defined?(@services)
@services[name] = spawn(name) unless @services.include?(name)
@services[name]
end
private # -----------------------------------------------------------------
##
# Spawn a Lookup of the given name.
#
def spawn(name)
if all_services.include?(name)
name = name.to_s
require "geocoder/lookups/#{name}"
Geocoder::Lookup.const_get(classify_name(name)).new
else
valids = all_services.map(&:inspect).join(", ")
raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
"(#{name.inspect} is not one of: #{valids})."
end
end
##
# Convert an "underscore" version of a name into a "class" version.
#
def classify_name(filename)
filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
end
end
end
geocoder-1.5.1/lib/geocoder/exceptions.rb 0000644 0000041 0000041 00000001025 13503210114 020364 0 ustar www-data www-data require 'timeout'
module Geocoder
class Error < StandardError
end
class ConfigurationError < Error
end
class OverQueryLimitError < Error
end
class ResponseParseError < Error
attr_reader :response
def initialize(response)
@response = response
end
end
class RequestDenied < Error
end
class InvalidRequest < Error
end
class InvalidApiKey < Error
end
class ServiceUnavailable < Error
end
class LookupTimeout < ::Timeout::Error
end
class NetworkError < Error
end
end
geocoder-1.5.1/lib/geocoder/railtie.rb 0000644 0000041 0000041 00000001140 13503210114 017632 0 ustar www-data www-data require 'geocoder/models/active_record'
module Geocoder
if defined? Rails::Railtie
require 'rails'
class Railtie < Rails::Railtie
initializer 'geocoder.insert_into_active_record', before: :load_config_initializers do
ActiveSupport.on_load :active_record do
Geocoder::Railtie.insert
end
end
rake_tasks do
load "tasks/geocoder.rake"
load "tasks/maxmind.rake"
end
end
end
class Railtie
def self.insert
if defined?(::ActiveRecord)
::ActiveRecord::Base.extend(Model::ActiveRecord)
end
end
end
end
geocoder-1.5.1/lib/geocoder/stores/ 0000755 0000041 0000041 00000000000 13503210114 017177 5 ustar www-data www-data geocoder-1.5.1/lib/geocoder/stores/active_record.rb 0000644 0000041 0000041 00000030077 13503210114 022344 0 ustar www-data www-data # -*- coding: utf-8 -*-
require 'geocoder/sql'
require 'geocoder/stores/base'
##
# Add geocoding functionality to any ActiveRecord object.
#
module Geocoder::Store
module ActiveRecord
include Base
##
# Implementation of 'included' hook method.
#
def self.included(base)
base.extend ClassMethods
base.class_eval do
# scope: geocoded objects
scope :geocoded, lambda {
where("#{table_name}.#{geocoder_options[:latitude]} IS NOT NULL " +
"AND #{table_name}.#{geocoder_options[:longitude]} IS NOT NULL")
}
# scope: not-geocoded objects
scope :not_geocoded, lambda {
where("#{table_name}.#{geocoder_options[:latitude]} IS NULL " +
"OR #{table_name}.#{geocoder_options[:longitude]} IS NULL")
}
# scope: not-reverse geocoded objects
scope :not_reverse_geocoded, lambda {
where("#{table_name}.#{geocoder_options[:fetched_address]} IS NULL")
}
##
# Find all objects within a radius of the given location.
# Location may be either a string to geocode or an array of
# coordinates ([lat,lon]). Also takes an options hash
# (see Geocoder::Store::ActiveRecord::ClassMethods.near_scope_options
# for details).
#
scope :near, lambda{ |location, *args|
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
options = near_scope_options(latitude, longitude, *args)
select(options[:select]).where(options[:conditions]).
order(options[:order])
else
# If no lat/lon given we don't want any results, but we still
# need distance and bearing columns so you can add, for example:
# .order("distance")
select(select_clause(nil, null_value, null_value)).where(false_condition)
end
}
##
# Find all objects within the area of a given bounding box.
# Bounds must be an array of locations specifying the southwest
# corner followed by the northeast corner of the box
# ([[sw_lat, sw_lon], [ne_lat, ne_lon]]).
#
scope :within_bounding_box, lambda{ |*bounds|
sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
if sw_lat && sw_lng && ne_lat && ne_lng
where(Geocoder::Sql.within_bounding_box(
sw_lat, sw_lng, ne_lat, ne_lng,
full_column_name(geocoder_options[:latitude]),
full_column_name(geocoder_options[:longitude])
))
else
select(select_clause(nil, null_value, null_value)).where(false_condition)
end
}
end
end
##
# Methods which will be class methods of the including class.
#
module ClassMethods
def distance_from_sql(location, *args)
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
distance_sql(latitude, longitude, *args)
end
end
##
# Get options hash suitable for passing to ActiveRecord.find to get
# records within a radius (in kilometers) of the given point.
# Options hash may include:
#
# * +:units+ - :mi or :km; to be used.
# for interpreting radius as well as the +distance+ attribute which
# is added to each found nearby object.
# Use Geocoder.configure[:units] to configure default units.
# * +:bearing+ - :linear or :spherical.
# the method to be used for calculating the bearing (direction)
# between the given point and each found nearby point;
# set to false for no bearing calculation. Use
# Geocoder.configure[:distances] to configure default calculation method.
# * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
# * +:select_distance+ - whether to include the distance alias in the
# SELECT SQL fragment (e.g. AS distance)
# * +:select_bearing+ - like +:select_distance+ but for bearing.
# * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
# set to false or nil to omit the ORDER BY clause
# * +:exclude+ - an object to exclude (used by the +nearbys+ method)
# * +:distance_column+ - used to set the column name of the calculated distance.
# * +:bearing_column+ - used to set the column name of the calculated bearing.
# * +:min_radius+ - the value to use as the minimum radius.
# ignored if database is sqlite.
# default is 0.0
#
def near_scope_options(latitude, longitude, radius = 20, options = {})
if options[:units]
options[:units] = options[:units].to_sym
end
latitude_attribute = options[:latitude] || geocoder_options[:latitude]
longitude_attribute = options[:longitude] || geocoder_options[:longitude]
options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
select_distance = options.fetch(:select_distance) { true }
options[:order] = "" if !select_distance && !options.include?(:order)
select_bearing = options.fetch(:select_bearing) { true }
bearing = bearing_sql(latitude, longitude, options)
distance = distance_sql(latitude, longitude, options)
distance_column = options.fetch(:distance_column) { 'distance' }
bearing_column = options.fetch(:bearing_column) { 'bearing' }
# If radius is a DB column name, bounding box should include
# all rows within the maximum radius appearing in that column.
# Note: performance is dependent on variability of radii.
bb_radius = radius.is_a?(Symbol) ? maximum(radius) : radius
b = Geocoder::Calculations.bounding_box([latitude, longitude], bb_radius, options)
args = b + [
full_column_name(latitude_attribute),
full_column_name(longitude_attribute)
]
bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
if using_unextended_sqlite?
conditions = bounding_box_conditions
else
min_radius = options.fetch(:min_radius, 0).to_f
# if radius is a DB column name,
# find rows between min_radius and value in column
if radius.is_a?(Symbol)
c = "BETWEEN ? AND #{radius}"
a = [min_radius]
else
c = "BETWEEN ? AND ?"
a = [min_radius, radius]
end
conditions = [bounding_box_conditions + " AND (#{distance}) " + c] + a
end
{
:select => select_clause(options[:select],
select_distance ? distance : nil,
select_bearing ? bearing : nil,
distance_column,
bearing_column),
:conditions => add_exclude_condition(conditions, options[:exclude]),
:order => options.include?(:order) ? options[:order] : "#{distance_column} ASC"
}
end
##
# SQL for calculating distance based on the current database's
# capabilities (trig functions?).
#
def distance_sql(latitude, longitude, options = {})
method_prefix = using_unextended_sqlite? ? "approx" : "full"
Geocoder::Sql.send(
method_prefix + "_distance",
latitude, longitude,
full_column_name(options[:latitude] || geocoder_options[:latitude]),
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
options
)
end
##
# SQL for calculating bearing based on the current database's
# capabilities (trig functions?).
#
def bearing_sql(latitude, longitude, options = {})
if !options.include?(:bearing)
options[:bearing] = Geocoder.config.distances
end
if options[:bearing]
method_prefix = using_unextended_sqlite? ? "approx" : "full"
Geocoder::Sql.send(
method_prefix + "_bearing",
latitude, longitude,
full_column_name(options[:latitude] || geocoder_options[:latitude]),
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
options
)
end
end
##
# Generate the SELECT clause.
#
def select_clause(columns, distance = nil, bearing = nil, distance_column = 'distance', bearing_column = 'bearing')
if columns == :id_only
return full_column_name(primary_key)
elsif columns == :geo_only
clause = ""
else
clause = (columns || full_column_name("*"))
end
if distance
clause += ", " unless clause.empty?
clause += "#{distance} AS #{distance_column}"
end
if bearing
clause += ", " unless clause.empty?
clause += "#{bearing} AS #{bearing_column}"
end
clause
end
##
# Adds a condition to exclude a given object by ID.
# Expects conditions as an array or string. Returns array.
#
def add_exclude_condition(conditions, exclude)
conditions = [conditions] if conditions.is_a?(String)
if exclude
conditions[0] << " AND #{full_column_name(primary_key)} != ?"
conditions << exclude.id
end
conditions
end
def using_unextended_sqlite?
using_sqlite? && !using_sqlite_with_extensions?
end
def using_sqlite?
!!connection.adapter_name.match(/sqlite/i)
end
def using_sqlite_with_extensions?
connection.adapter_name.match(/sqlite/i) &&
defined?(::SqliteExt) &&
%W(MOD POWER SQRT PI SIN COS ASIN ATAN2).all?{ |fn_name|
connection.raw_connection.function_created?(fn_name)
}
end
def using_postgres?
connection.adapter_name.match(/postgres/i)
end
##
# Use OID type when running in PosgreSQL
#
def null_value
using_postgres? ? 'NULL::text' : 'NULL'
end
##
# Value which can be passed to where() to produce no results.
#
def false_condition
using_unextended_sqlite? ? 0 : "false"
end
##
# Prepend table name if column name doesn't already contain one.
#
def full_column_name(column)
column = column.to_s
column.include?(".") ? column : [table_name, column].join(".")
end
end
##
# Get nearby geocoded objects.
# Takes the same options hash as the near class method (scope).
# Returns nil if the object is not geocoded.
#
def nearbys(radius = 20, options = {})
return nil unless geocoded?
options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
self.class.near(self, radius, options)
end
##
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
# (or other as specified in +geocoded_by+). Returns coordinates (array).
#
def geocode
do_lookup(false) do |o,rs|
if r = rs.first
unless r.latitude.nil? or r.longitude.nil?
o.__send__ "#{self.class.geocoder_options[:latitude]}=", r.latitude
o.__send__ "#{self.class.geocoder_options[:longitude]}=", r.longitude
end
r.coordinates
end
end
end
alias_method :fetch_coordinates, :geocode
##
# Look up address and assign to +address+ attribute (or other as specified
# in +reverse_geocoded_by+). Returns address (string).
#
def reverse_geocode
do_lookup(true) do |o,rs|
if r = rs.first
unless r.address.nil?
o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
end
r.address
end
end
end
alias_method :fetch_address, :reverse_geocode
end
end
geocoder-1.5.1/lib/geocoder/stores/mongo_base.rb 0000644 0000041 0000041 00000002765 13503210114 021647 0 ustar www-data www-data module Geocoder::Store
module MongoBase
def self.included_by_model(base)
base.class_eval do
scope :geocoded, lambda {
where(geocoder_options[:coordinates].ne => nil)
}
scope :not_geocoded, lambda {
where(geocoder_options[:coordinates] => nil)
}
end
end
##
# Coordinates [lat,lon] of the object.
# This method always returns coordinates in lat,lon order,
# even though internally they are stored in the opposite order.
#
def to_coordinates
coords = send(self.class.geocoder_options[:coordinates])
coords.is_a?(Array) ? coords.reverse : []
end
##
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
# (or other as specified in +geocoded_by+). Returns coordinates (array).
#
def geocode
do_lookup(false) do |o,rs|
if r = rs.first
unless r.coordinates.nil?
o.__send__ "#{self.class.geocoder_options[:coordinates]}=", r.coordinates.reverse
end
r.coordinates
end
end
end
##
# Look up address and assign to +address+ attribute (or other as specified
# in +reverse_geocoded_by+). Returns address (string).
#
def reverse_geocode
do_lookup(true) do |o,rs|
if r = rs.first
unless r.address.nil?
o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
end
r.address
end
end
end
end
end
geocoder-1.5.1/lib/geocoder/stores/mongoid.rb 0000644 0000041 0000041 00000000353 13503210114 021161 0 ustar www-data www-data require 'geocoder/stores/base'
require 'geocoder/stores/mongo_base'
module Geocoder::Store
module Mongoid
include Base
include MongoBase
def self.included(base)
MongoBase.included_by_model(base)
end
end
end
geocoder-1.5.1/lib/geocoder/stores/base.rb 0000644 0000041 0000041 00000007201 13503210114 020436 0 ustar www-data www-data module Geocoder
module Store
module Base
##
# Is this object geocoded? (Does it have latitude and longitude?)
#
def geocoded?
to_coordinates.compact.size == 2
end
##
# Coordinates [lat,lon] of the object.
#
def to_coordinates
[:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
end
##
# Calculate the distance from the object to an arbitrary point.
# See Geocoder::Calculations.distance_between for ways of specifying
# the point. Also takes a symbol specifying the units
# (:mi or :km; can be specified in Geocoder configuration).
#
def distance_to(point, units = nil)
units ||= self.class.geocoder_options[:units]
return nil unless geocoded?
Geocoder::Calculations.distance_between(
to_coordinates, point, :units => units)
end
alias_method :distance_from, :distance_to
##
# Calculate the bearing from the object to another point.
# See Geocoder::Calculations.distance_between for
# ways of specifying the point.
#
def bearing_to(point, options = {})
options[:method] ||= self.class.geocoder_options[:method]
return nil unless geocoded?
Geocoder::Calculations.bearing_between(
to_coordinates, point, options)
end
##
# Calculate the bearing from another point to the object.
# See Geocoder::Calculations.distance_between for
# ways of specifying the point.
#
def bearing_from(point, options = {})
options[:method] ||= self.class.geocoder_options[:method]
return nil unless geocoded?
Geocoder::Calculations.bearing_between(
point, to_coordinates, options)
end
##
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
# (or other as specified in +geocoded_by+). Returns coordinates (array).
#
def geocode
fail
end
##
# Look up address and assign to +address+ attribute (or other as specified
# in +reverse_geocoded_by+). Returns address (string).
#
def reverse_geocode
fail
end
private # --------------------------------------------------------------
##
# Look up geographic data based on object attributes (configured in
# geocoded_by or reverse_geocoded_by) and handle the results with the
# block (given to geocoded_by or reverse_geocoded_by). The block is
# given two-arguments: the object being geocoded and an array of
# Geocoder::Result objects).
#
def do_lookup(reverse = false)
options = self.class.geocoder_options
if reverse and options[:reverse_geocode]
query = to_coordinates
elsif !reverse and options[:geocode]
query = send(options[:user_address])
else
return
end
query_options = [:lookup, :ip_lookup, :language, :params].inject({}) do |hash, key|
if options.has_key?(key)
val = options[key]
hash[key] = val.respond_to?(:call) ? val.call(self) : val
end
hash
end
results = Geocoder.search(query, query_options)
# execute custom block, if specified in configuration
block_key = reverse ? :reverse_block : :geocode_block
if custom_block = options[block_key]
custom_block.call(self, results)
# else execute block passed directly to this method,
# which generally performs the "auto-assigns"
elsif block_given?
yield(self, results)
end
end
end
end
end
geocoder-1.5.1/lib/geocoder/stores/mongo_mapper.rb 0000644 0000041 0000041 00000000357 13503210114 022214 0 ustar www-data www-data require 'geocoder/stores/base'
require 'geocoder/stores/mongo_base'
module Geocoder::Store
module MongoMapper
include Base
include MongoBase
def self.included(base)
MongoBase.included_by_model(base)
end
end
end
geocoder-1.5.1/lib/geocoder/calculations.rb 0000644 0000041 0000041 00000033061 13503210114 020671 0 ustar www-data www-data module Geocoder
module Calculations
extend self
##
# Compass point names, listed clockwise starting at North.
#
# If you want bearings named using more, fewer, or different points
# override Geocoder::Calculations.COMPASS_POINTS with your own array.
#
COMPASS_POINTS = %w[N NE E SE S SW W NW]
##
# Conversion factor: multiply by kilometers to get miles.
#
KM_IN_MI = 0.621371192
##
# Conversion factor: multiply by nautical miles to get miles.
#
KM_IN_NM = 0.539957
##
# Conversion factor: multiply by radians to get degrees.
#
DEGREES_PER_RADIAN = 57.2957795
##
# Radius of the Earth, in kilometers.
# Value taken from: http://en.wikipedia.org/wiki/Earth_radius
#
EARTH_RADII = {km: 6371.0}
EARTH_RADII[:mi] = EARTH_RADII[:km] * KM_IN_MI
EARTH_RADII[:nm] = EARTH_RADII[:km] * KM_IN_NM
EARTH_RADIUS = EARTH_RADII[:km] # TODO: deprecate this constant (use `EARTH_RADII[:km]`)
# Not a number constant
NAN = defined?(::Float::NAN) ? ::Float::NAN : 0 / 0.0
##
# Returns true if all given arguments are valid latitude/longitude values.
#
def coordinates_present?(*args)
args.each do |a|
# note that Float::NAN != Float::NAN
# still, this could probably be improved:
return false if (!a.is_a?(Numeric) or a.to_s == "NaN")
end
true
end
##
# Distance spanned by one degree of latitude in the given units.
#
def latitude_degree_distance(units = nil)
2 * Math::PI * earth_radius(units) / 360
end
##
# Distance spanned by one degree of longitude at the given latitude.
# This ranges from around 69 miles at the equator to zero at the poles.
#
def longitude_degree_distance(latitude, units = nil)
latitude_degree_distance(units) * Math.cos(to_radians(latitude))
end
##
# Distance between two points on Earth (Haversine formula).
# Takes two points and an options hash.
# The points are given in the same way that points are given to all
# Geocoder methods that accept points as arguments. They can be:
#
# * an array of coordinates ([lat,lon])
# * a geocodable address (string)
# * a geocoded object (one which implements a +to_coordinates+ method
# which returns a [lat,lon] array
#
# The options hash supports:
#
# * :units - :mi or :km
# Use Geocoder.configure(:units => ...) to configure default units.
#
def distance_between(point1, point2, options = {})
# convert to coordinate arrays
point1 = extract_coordinates(point1)
point2 = extract_coordinates(point2)
# convert degrees to radians
point1 = to_radians(point1)
point2 = to_radians(point2)
# compute deltas
dlat = point2[0] - point1[0]
dlon = point2[1] - point1[1]
a = (Math.sin(dlat / 2))**2 + Math.cos(point1[0]) *
(Math.sin(dlon / 2))**2 * Math.cos(point2[0])
c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
c * earth_radius(options[:units])
end
##
# Bearing between two points on Earth.
# Returns a number of degrees from due north (clockwise).
#
# See Geocoder::Calculations.distance_between for
# ways of specifying the points. Also accepts an options hash:
#
# * :method - :linear or :spherical;
# the spherical method is "correct" in that it returns the shortest path
# (one along a great circle) but the linear method is less confusing
# (returns due east or west when given two points with the same latitude).
# Use Geocoder.configure(:distances => ...) to configure calculation method.
#
# Based on: http://www.movable-type.co.uk/scripts/latlong.html
#
def bearing_between(point1, point2, options = {})
# set default options
options[:method] ||= Geocoder.config.distances
options[:method] = :linear unless options[:method] == :spherical
# convert to coordinate arrays
point1 = extract_coordinates(point1)
point2 = extract_coordinates(point2)
# convert degrees to radians
point1 = to_radians(point1)
point2 = to_radians(point2)
# compute deltas
dlat = point2[0] - point1[0]
dlon = point2[1] - point1[1]
case options[:method]
when :linear
y = dlon
x = dlat
when :spherical
y = Math.sin(dlon) * Math.cos(point2[0])
x = Math.cos(point1[0]) * Math.sin(point2[0]) -
Math.sin(point1[0]) * Math.cos(point2[0]) * Math.cos(dlon)
end
bearing = Math.atan2(x,y)
# Answer is in radians counterclockwise from due east.
# Convert to degrees clockwise from due north:
(90 - to_degrees(bearing) + 360) % 360
end
##
# Translate a bearing (float) into a compass direction (string, eg "North").
#
def compass_point(bearing, points = COMPASS_POINTS)
seg_size = 360.0 / points.size
points[((bearing + (seg_size / 2)) % 360) / seg_size]
end
##
# Compute the geographic center (aka geographic midpoint, center of
# gravity) for an array of geocoded objects and/or [lat,lon] arrays
# (can be mixed). Any objects missing coordinates are ignored. Follows
# the procedure documented at http://www.geomidpoint.com/calculation.html.
#
def geographic_center(points)
# convert objects to [lat,lon] arrays and convert degrees to radians
coords = points.map{ |p| to_radians(extract_coordinates(p)) }
# convert to Cartesian coordinates
x = []; y = []; z = []
coords.each do |p|
x << Math.cos(p[0]) * Math.cos(p[1])
y << Math.cos(p[0]) * Math.sin(p[1])
z << Math.sin(p[0])
end
# compute average coordinate values
xa, ya, za = [x,y,z].map do |c|
c.inject(0){ |tot,i| tot += i } / c.size.to_f
end
# convert back to latitude/longitude
lon = Math.atan2(ya, xa)
hyp = Math.sqrt(xa**2 + ya**2)
lat = Math.atan2(za, hyp)
# return answer in degrees
to_degrees [lat, lon]
end
##
# Returns coordinates of the southwest and northeast corners of a box
# with the given point at its center. The radius is the shortest distance
# from the center point to any side of the box (the length of each side
# is twice the radius).
#
# This is useful for finding corner points of a map viewport, or for
# roughly limiting the possible solutions in a geo-spatial search
# (ActiveRecord queries use it thusly).
#
# See Geocoder::Calculations.distance_between for
# ways of specifying the point. Also accepts an options hash:
#
# * :units - :mi or :km.
# Use Geocoder.configure(:units => ...) to configure default units.
#
def bounding_box(point, radius, options = {})
lat,lon = extract_coordinates(point)
radius = radius.to_f
[
lat - (radius / latitude_degree_distance(options[:units])),
lon - (radius / longitude_degree_distance(lat, options[:units])),
lat + (radius / latitude_degree_distance(options[:units])),
lon + (radius / longitude_degree_distance(lat, options[:units]))
]
end
##
# Random point within a circle of provided radius centered
# around the provided point
# Takes one point, one radius, and an options hash.
# The points are given in the same way that points are given to all
# Geocoder methods that accept points as arguments. They can be:
#
# * an array of coordinates ([lat,lon])
# * a geocodable address (string)
# * a geocoded object (one which implements a +to_coordinates+ method
# which returns a [lat,lon] array
#
# The options hash supports:
#
# * :units - :mi or :km
# Use Geocoder.configure(:units => ...) to configure default units.
# * :seed - The seed for the random number generator
def random_point_near(center, radius, options = {})
random = Random.new(options[:seed] || Random.new_seed)
# convert to coordinate arrays
center = extract_coordinates(center)
earth_circumference = 2 * Math::PI * earth_radius(options[:units])
max_degree_delta = 360.0 * (radius / earth_circumference)
# random bearing in radians
theta = 2 * Math::PI * random.rand
# random radius, use the square root to ensure a uniform
# distribution of points over the circle
r = Math.sqrt(random.rand) * max_degree_delta
delta_lat, delta_long = [r * Math.cos(theta), r * Math.sin(theta)]
[center[0] + delta_lat, center[1] + delta_long]
end
##
# Given a start point, heading (in degrees), and distance, provides
# an endpoint.
# The starting point is given in the same way that points are given to all
# Geocoder methods that accept points as arguments. It can be:
#
# * an array of coordinates ([lat,lon])
# * a geocodable address (string)
# * a geocoded object (one which implements a +to_coordinates+ method
# which returns a [lat,lon] array
#
def endpoint(start, heading, distance, options = {})
radius = earth_radius(options[:units])
start = extract_coordinates(start)
# convert degrees to radians
start = to_radians(start)
lat = start[0]
lon = start[1]
heading = to_radians(heading)
distance = distance.to_f
end_lat = Math.asin(Math.sin(lat)*Math.cos(distance/radius) +
Math.cos(lat)*Math.sin(distance/radius)*Math.cos(heading))
end_lon = lon+Math.atan2(Math.sin(heading)*Math.sin(distance/radius)*Math.cos(lat),
Math.cos(distance/radius)-Math.sin(lat)*Math.sin(end_lat))
to_degrees [end_lat, end_lon]
end
##
# Convert degrees to radians.
# If an array (or multiple arguments) is passed,
# converts each value and returns array.
#
def to_radians(*args)
args = args.first if args.first.is_a?(Array)
if args.size == 1
args.first * (Math::PI / 180)
else
args.map{ |i| to_radians(i) }
end
end
##
# Convert radians to degrees.
# If an array (or multiple arguments) is passed,
# converts each value and returns array.
#
def to_degrees(*args)
args = args.first if args.first.is_a?(Array)
if args.size == 1
(args.first * 180.0) / Math::PI
else
args.map{ |i| to_degrees(i) }
end
end
def distance_to_radians(distance, units = nil)
distance.to_f / earth_radius(units)
end
def radians_to_distance(radians, units = nil)
radians * earth_radius(units)
end
##
# Convert miles to kilometers.
#
def to_kilometers(mi)
Geocoder.log(:warn, "DEPRECATION WARNING: Geocoder::Calculations.to_kilometers is deprecated and will be removed in Geocoder 1.5.0. Please multiply by MI_IN_KM instead.")
mi * mi_in_km
end
##
# Convert kilometers to miles.
#
def to_miles(km)
Geocoder.log(:warn, "DEPRECATION WARNING: Geocoder::Calculations.to_miles is deprecated and will be removed in Geocoder 1.5.0. Please multiply by KM_IN_MI instead.")
km * KM_IN_MI
end
##
# Convert kilometers to nautical miles.
#
def to_nautical_miles(km)
Geocoder.log(:warn, "DEPRECATION WARNING: Geocoder::Calculations.to_nautical_miles is deprecated and will be removed in Geocoder 1.5.0. Please multiply by KM_IN_NM instead.")
km * KM_IN_NM
end
##
# Radius of the Earth in the given units (:mi or :km).
# Use Geocoder.configure(:units => ...) to configure default units.
#
def earth_radius(units = nil)
EARTH_RADII[units || Geocoder.config.units]
end
##
# Conversion factor: km to mi.
#
def km_in_mi
Geocoder.log(:warn, "DEPRECATION WARNING: Geocoder::Calculations.km_in_mi is deprecated and will be removed in Geocoder 1.5.0. Please use the constant KM_IN_MI instead.")
KM_IN_MI
end
##
# Conversion factor: km to nm.
#
def km_in_nm
Geocoder.log(:warn, "DEPRECATION WARNING: Geocoder::Calculations.km_in_nm is deprecated and will be removed in Geocoder 1.5.0. Please use the constant KM_IN_NM instead.")
KM_IN_NM
end
##
# Conversion factor: mi to km.
#
def mi_in_km
Geocoder.log(:warn, "DEPRECATION WARNING: Geocoder::Calculations.mi_in_km is deprecated and will be removed in Geocoder 1.5.0. Please use 1.0 / KM_IN_MI instead.")
1.0 / KM_IN_MI
end
##
# Conversion factor: nm to km.
#
def nm_in_km
Geocoder.log(:warn, "DEPRECATION WARNING: Geocoder::Calculations.nm_in_km is deprecated and will be removed in Geocoder 1.5.0. Please use 1.0 / KM_IN_NM instead.")
1.0 / KM_IN_NM
end
##
# Takes an object which is a [lat,lon] array, a geocodable string,
# or an object that implements +to_coordinates+ and returns a
# [lat,lon] array. Note that if a string is passed this may be a slow-
# running method and may return nil.
#
def extract_coordinates(point)
case point
when Array
if point.size == 2
lat, lon = point
if !lat.nil? && lat.respond_to?(:to_f) and
!lon.nil? && lon.respond_to?(:to_f)
then
return [ lat.to_f, lon.to_f ]
end
end
when String
point = Geocoder.coordinates(point) and return point
else
if point.respond_to?(:to_coordinates)
if Array === array = point.to_coordinates
return extract_coordinates(array)
end
end
end
[ NAN, NAN ]
end
end
end
geocoder-1.5.1/lib/geocoder/models/ 0000755 0000041 0000041 00000000000 13503210114 017143 5 ustar www-data www-data geocoder-1.5.1/lib/geocoder/models/active_record.rb 0000644 0000041 0000041 00000003133 13503210114 022301 0 ustar www-data www-data require 'geocoder/models/base'
module Geocoder
module Model
module ActiveRecord
include Base
##
# Set attribute names and include the Geocoder module.
#
def geocoded_by(address_attr, options = {}, &block)
geocoder_init(
:geocode => true,
:user_address => address_attr,
:latitude => options[:latitude] || :latitude,
:longitude => options[:longitude] || :longitude,
:geocode_block => block,
:units => options[:units],
:method => options[:method],
:lookup => options[:lookup],
:language => options[:language],
:params => options[:params]
)
end
##
# Set attribute names and include the Geocoder module.
#
def reverse_geocoded_by(latitude_attr, longitude_attr, options = {}, &block)
geocoder_init(
:reverse_geocode => true,
:fetched_address => options[:address] || :address,
:latitude => latitude_attr,
:longitude => longitude_attr,
:reverse_block => block,
:units => options[:units],
:method => options[:method],
:lookup => options[:lookup],
:language => options[:language],
:params => options[:params]
)
end
private # --------------------------------------------------------------
def geocoder_file_name; "active_record"; end
def geocoder_module_name; "ActiveRecord"; end
end
end
end
geocoder-1.5.1/lib/geocoder/models/mongo_base.rb 0000644 0000041 0000041 00000003543 13503210114 021606 0 ustar www-data www-data module Geocoder
##
# Methods for invoking Geocoder in a model.
#
module Model
module MongoBase
##
# Set attribute names and include the Geocoder module.
#
def geocoded_by(address_attr, options = {}, &block)
geocoder_init(
:geocode => true,
:user_address => address_attr,
:coordinates => options[:coordinates] || :coordinates,
:geocode_block => block,
:units => options[:units],
:method => options[:method],
:skip_index => options[:skip_index] || false,
:lookup => options[:lookup],
:language => options[:language]
)
end
##
# Set attribute names and include the Geocoder module.
#
def reverse_geocoded_by(coordinates_attr, options = {}, &block)
geocoder_init(
:reverse_geocode => true,
:fetched_address => options[:address] || :address,
:coordinates => coordinates_attr,
:reverse_block => block,
:units => options[:units],
:method => options[:method],
:skip_index => options[:skip_index] || false,
:lookup => options[:lookup],
:language => options[:language]
)
end
private # ----------------------------------------------------------------
def geocoder_init(options)
unless geocoder_initialized?
@geocoder_options = { }
require "geocoder/stores/#{geocoder_file_name}"
include Geocoder::Store.const_get(geocoder_module_name)
end
@geocoder_options.merge! options
end
def geocoder_initialized?
included_modules.include? Geocoder::Store.const_get(geocoder_module_name)
rescue NameError
false
end
end
end
end
geocoder-1.5.1/lib/geocoder/models/mongoid.rb 0000644 0000041 0000041 00000001555 13503210114 021132 0 ustar www-data www-data require 'geocoder/models/base'
require 'geocoder/models/mongo_base'
module Geocoder
module Model
module Mongoid
include Base
include MongoBase
def self.included(base); base.extend(self); end
private # --------------------------------------------------------------
def geocoder_file_name; "mongoid"; end
def geocoder_module_name; "Mongoid"; end
def geocoder_init(options)
super(options)
if options[:skip_index] == false
# create 2d index
if defined?(::Mongoid::VERSION) && ::Mongoid::VERSION >= "3"
index({ geocoder_options[:coordinates].to_sym => '2d' },
{:min => -180, :max => 180})
else
index [[ geocoder_options[:coordinates], '2d' ]],
:min => -180, :max => 180
end
end
end
end
end
end
geocoder-1.5.1/lib/geocoder/models/base.rb 0000644 0000041 0000041 00000001515 13503210114 020404 0 ustar www-data www-data module Geocoder
##
# Methods for invoking Geocoder in a model.
#
module Model
module Base
def geocoder_options
if defined?(@geocoder_options)
@geocoder_options
elsif superclass.respond_to?(:geocoder_options)
superclass.geocoder_options || { }
else
{ }
end
end
def geocoded_by
fail
end
def reverse_geocoded_by
fail
end
private # ----------------------------------------------------------------
def geocoder_init(options)
unless defined?(@geocoder_options)
@geocoder_options = {}
require "geocoder/stores/#{geocoder_file_name}"
include Geocoder::Store.const_get(geocoder_module_name)
end
@geocoder_options.merge! options
end
end
end
end
geocoder-1.5.1/lib/geocoder/models/mongo_mapper.rb 0000644 0000041 0000041 00000001242 13503210114 022152 0 ustar www-data www-data require 'geocoder/models/base'
require 'geocoder/models/mongo_base'
module Geocoder
module Model
module MongoMapper
include Base
include MongoBase
def self.included(base); base.extend(self); end
private # --------------------------------------------------------------
def geocoder_file_name; "mongo_mapper"; end
def geocoder_module_name; "MongoMapper"; end
def geocoder_init(options)
super(options)
if options[:skip_index] == false
ensure_index [[ geocoder_options[:coordinates], Mongo::GEO2D ]],
:min => -180, :max => 180 # create 2d index
end
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/ 0000755 0000041 0000041 00000000000 13503210114 017354 5 ustar www-data www-data geocoder-1.5.1/lib/geocoder/lookups/opencagedata.rb 0000644 0000041 0000041 00000003600 13503210114 022313 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/opencagedata'
module Geocoder::Lookup
class Opencagedata < Base
def name
"OpenCageData"
end
def required_api_key_parts
["key"]
end
private # ----------------------------------------------------------------
def base_query_url(query)
"#{protocol}://api.opencagedata.com/geocode/v1/json?"
end
def results(query)
return [] unless doc = fetch_data(query)
# return doc["results"]
messages = doc['status']['message']
case doc['status']['code']
when 400 # Error with input
raise_error(Geocoder::InvalidRequest, messages) ||
Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
when 403 # Key related error
raise_error(Geocoder::InvalidApiKey, messages) ||
Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
when 402 # Quata Exceeded
raise_error(Geocoder::OverQueryLimitError, messages) ||
Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
when 500 # Unknown error
raise_error(Geocoder::Error, messages) ||
Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
end
return doc["results"]
end
def query_url_params(query)
params = {
:q => query.sanitized_text,
:key => configuration.api_key,
:language => (query.language || configuration.language)
}.merge(super)
[:abbrv, :countrycode, :min_confidence, :no_dedupe, :no_annotations, :no_record, :limit].each do |option|
unless (option_value = query.options[option]).nil?
params[option] = option_value
end
end
unless (bounds = query.options[:bounds]).nil?
params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join(',')
end
params
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/here.rb 0000644 0000041 0000041 00000003574 13503210114 020635 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/here'
module Geocoder::Lookup
class Here < Base
def name
"Here"
end
def required_api_key_parts
["app_id", "app_code"]
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?"
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']
end
[]
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)
}
if reverse_geocode
options[:mode] = :retrieveAddresses
return options
end
unless (country = query.options[:country]).nil?
options[:country] = 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
)
else
super.merge(query_url_here_options(query, false)).merge(
searchtext: 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_code
if (a = configuration.api_key)
return a.last if a.is_a?(Array)
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/amap.rb 0000644 0000041 0000041 00000003160 13503210114 020617 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/amap"
module Geocoder::Lookup
class Amap < Base
def name
"AMap"
end
def required_api_key_parts
["key"]
end
def supported_protocols
[:http]
end
private # ---------------------------------------------------------------
def base_query_url(query)
path = query.reverse_geocode? ? 'regeo' : 'geo'
"http://restapi.amap.com/v3/geocode/#{path}?"
end
def results(query, reverse = false)
return [] unless doc = fetch_data(query)
case [doc['status'], doc['info']]
when ['1', 'OK']
return doc['regeocodes'] unless doc['regeocodes'].blank?
return [doc['regeocode']] unless doc['regeocode'].blank?
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.")
else
raise_error(Geocoder::Error, "server error.") ||
warn("#{self.name} Geocoding API error: server error - [#{doc['info']}]")
end
return []
end
def query_url_params(query)
params = {
:key => configuration.api_key,
:output => "json"
}
if query.reverse_geocode?
params[:location] = revert_coordinates(query.text)
params[:extensions] = "all"
params[:coordsys] = "gps"
else
params[:address] = query.sanitized_text
end
params.merge(super)
end
def revert_coordinates(text)
[text[1],text[0]].join(",")
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/ipdata_co.rb 0000644 0000041 0000041 00000003041 13503210114 021622 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/ipdata_co'
module Geocoder::Lookup
class IpdataCo < Base
def name
"ipdata.co"
end
def supported_protocols
[:https]
end
def query_url(query)
# Ipdata.co requires that the API key be sent as a query parameter.
# It no longer supports API keys sent as headers.
"#{protocol}://#{host}/#{query.sanitized_text}?api-key=#{configuration.api_key}"
end
private # ---------------------------------------------------------------
def cache_key(query)
query_url(query)
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?
# note: Ipdata.co returns plain text on bad request
(doc = fetch_data(query)) ? [doc] : []
end
def reserved_result(ip)
{
"ip" => ip,
"city" => "",
"region_code" => "",
"region_name" => "",
"metrocode" => "",
"zipcode" => "",
"latitude" => "0",
"longitude" => "0",
"country_name" => "Reserved",
"country_code" => "RD"
}
end
def host
"api.ipdata.co"
end
def check_response_for_errors!(response)
if response.code.to_i == 403
raise_error(Geocoder::RequestDenied) ||
Geocoder.log(:warn, "Geocoding API error: 403 API key does not exist")
else
super(response)
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/geoportail_lu.rb 0000644 0000041 0000041 00000002753 13503210114 022555 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/geoportail_lu"
module Geocoder
module Lookup
class GeoportailLu < Base
def name
"Geoportail.lu"
end
private # ---------------------------------------------------------------
def base_query_url(query)
if query.reverse_geocode?
reverse_geocode_url_base_path
else
search_url_base_path
end
end
def search_url_base_path
"#{protocol}://api.geoportail.lu/geocoder/search?"
end
def reverse_geocode_url_base_path
"#{protocol}://api.geoportail.lu/geocoder/reverseGeocode?"
end
def query_url_geoportail_lu_params(query)
query.reverse_geocode? ? reverse_geocode_params(query) : search_params(query)
end
def search_params(query)
{
queryString: query.sanitized_text
}
end
def reverse_geocode_params(query)
lat_lon = query.coordinates
{
lat: lat_lon.first,
lon: lat_lon.last
}
end
def query_url_params(query)
query_url_geoportail_lu_params(query).merge(super)
end
def results(query)
return [] unless doc = fetch_data(query)
if doc['success'] == true
result = doc['results']
else
result = []
raise_error(Geocoder::Error) ||
warn("Geportail.lu Geocoding API error")
end
result
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/baidu_ip.rb 0000644 0000041 0000041 00000001053 13503210114 021454 0 ustar www-data www-data require 'geocoder/lookups/baidu'
require 'geocoder/results/baidu_ip'
module Geocoder::Lookup
class BaiduIp < Baidu
def name
"Baidu IP"
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://api.map.baidu.com/location/ip?"
end
def content_key
'content'
end
def query_url_params(query)
{
:ip => query.sanitized_text,
:ak => configuration.api_key,
:coor => "bd09ll"
}.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/yandex.rb 0000644 0000041 0000041 00000003333 13503210114 021173 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/yandex"
module Geocoder::Lookup
class Yandex < Base
def name
"Yandex"
end
def map_link_url(coordinates)
"http://maps.yandex.ru/?ll=#{coordinates.reverse.join(',')}"
end
def supported_protocols
[:https]
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://geocode-maps.yandex.ru/1.x/?"
end
def results(query)
return [] unless doc = fetch_data(query)
if err = doc['error']
if err["status"] == 401 and err["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']}).")
end
return []
end
if doc = doc['response']['GeoObjectCollection']
meta = doc['metaDataProperty']['GeocoderResponseMetaData']
return meta['found'].to_i > 0 ? doc['featureMember'] : []
else
Geocoder.log(:warn, "Yandex 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 = {
:geocode => q,
:format => "json",
:plng => "#{query.language || configuration.language}", # supports ru, uk, be
:key => configuration.api_key
}
unless (bounds = query.options[:bounds]).nil?
params[:bbox] = bounds.map{ |point| "%f,%f" % point }.join('~')
end
params.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/tencent.rb 0000644 0000041 0000041 00000003041 13503210114 021337 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/tencent"
module Geocoder::Lookup
class Tencent < Base
def name
"Tencent"
end
def required_api_key_parts
["key"]
end
def supported_protocols
[:https]
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://apis.map.qq.com/ws/geocoder/v1/?"
end
def content_key
'result'
end
def results(query, reverse = false)
return [] unless doc = fetch_data(query)
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.")
end
return []
end
def query_url_params(query)
{
(query.reverse_geocode? ? :location : :address) => query.sanitized_text,
:key => configuration.api_key,
:output => "json"
}.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/postcodes_io.rb 0000644 0000041 0000041 00000001227 13503210114 022375 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/postcodes_io'
module Geocoder::Lookup
class PostcodesIo < Base
def name
'Postcodes.io'
end
def query_url(query)
"#{protocol}://api.postcodes.io/postcodes/#{query.sanitized_text.gsub(/\s/, '')}"
end
def supported_protocols
[:https]
end
private # ----------------------------------------------------------------
def cache_key(query)
query_url(query)
end
def results(query)
response = fetch_data(query)
return [] if response.nil? || response['status'] != 200 || response.empty?
[response['result']]
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/telize.rb 0000644 0000041 0000041 00000003246 13503210114 021202 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/telize'
module Geocoder::Lookup
class Telize < Base
def name
"Telize"
end
def required_api_key_parts
configuration[:host] ? [] : ["key"]
end
def query_url(query)
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}"
end
end
def supported_protocols
[].tap do |array|
array << :https
array << :http if configuration[:host]
end
end
private # ---------------------------------------------------------------
def cache_key(query)
query_url(query)[/(.*)\?.*/, 1]
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?
if (doc = fetch_data(query)).nil? or doc['code'] == 401 or empty_result?(doc)
[]
else
[doc]
end
end
def empty_result?(doc)
!doc.is_a?(Hash) or doc.keys == ["ip"]
end
def reserved_result(ip)
{
"ip" => ip,
"latitude" => 0,
"longitude" => 0,
"city" => "",
"timezone" => "",
"asn" => 0,
"region" => "",
"offset" => 0,
"organization" => "",
"country_code" => "",
"country_code3" => "",
"postal_code" => "",
"continent_code" => "",
"country" => "",
"region_code" => ""
}
end
def api_key
configuration.api_key
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/ban_data_gouv_fr.rb 0000644 0000041 0000041 00000007366 13503210114 023175 0 ustar www-data www-data # encoding: utf-8
require 'geocoder/lookups/base'
require 'geocoder/results/ban_data_gouv_fr'
module Geocoder::Lookup
class BanDataGouvFr < Base
def name
"Base Adresse Nationale Française"
end
def map_link_url(coordinates)
"https://www.openstreetmap.org/#map=19/#{coordinates.join('/')}"
end
private # ---------------------------------------------------------------
def base_query_url(query)
method = query.reverse_geocode? ? "reverse" : "search"
"#{protocol}://api-adresse.data.gouv.fr/#{method}/?"
end
def any_result?(doc)
doc['features'].any?
end
def results(query)
if doc = fetch_data(query) and any_result?(doc)
[doc]
else
[]
end
end
#### PARAMS ####
def query_url_params(query)
query_ban_datagouv_fr_params(query).merge(super)
end
def query_ban_datagouv_fr_params(query)
query.reverse_geocode? ? reverse_geocode_ban_fr_params(query) : search_geocode_ban_fr_params(query)
end
#### SEARCH GEOCODING PARAMS ####
#
# :q => required, full text search param)
# :limit => force limit number of results returned by raw API
# (default = 5) note : only first result is taken
# in account in geocoder
#
# :autocomplete => pass 0 to disable autocomplete treatment of :q
# (default = 1)
#
# :lat => force filter results around specific lat/lon
#
# :lon => force filter results around specific lat/lon
#
# :type => force filter the returned result type
# (check results for a list of accepted types)
#
# :postcode => force filter results on a specific city post code
#
# :citycode => force filter results on a specific city UUID INSEE code
#
# For up to date doc (in french only) : https://adresse.data.gouv.fr/api/
#
def search_geocode_ban_fr_params(query)
params = {
q: query.sanitized_text
}
unless (limit = query.options[:limit]).nil? || !limit_param_is_valid?(limit)
params[:limit] = limit.to_i
end
unless (autocomplete = query.options[:autocomplete]).nil? || !autocomplete_param_is_valid?(autocomplete)
params[:autocomplete] = autocomplete.to_s
end
unless (type = query.options[:type]).nil? || !type_param_is_valid?(type)
params[:type] = type.downcase
end
unless (postcode = query.options[:postcode]).nil? || !code_param_is_valid?(postcode)
params[:postcode] = postcode.to_s
end
unless (citycode = query.options[:citycode]).nil? || !code_param_is_valid?(citycode)
params[:citycode] = citycode.to_s
end
params
end
#### REVERSE GEOCODING PARAMS ####
#
# :lat => required
#
# :lon => required
#
# :type => force returned results type
# (check results for a list of accepted types)
#
def reverse_geocode_ban_fr_params(query)
lat_lon = query.coordinates
params = {
lat: lat_lon.first,
lon: lat_lon.last
}
unless (type = query.options[:type]).nil? || !type_param_is_valid?(type)
params[:type] = type.downcase
end
params
end
def limit_param_is_valid?(param)
param.to_i.positive?
end
def autocomplete_param_is_valid?(param)
[0,1].include?(param.to_i)
end
def type_param_is_valid?(param)
%w(housenumber street locality village town city).include?(param.downcase)
end
def code_param_is_valid?(param)
(1..99999).include?(param.to_i)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/baidu.rb 0000644 0000041 0000041 00000003334 13503210114 020770 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/baidu"
module Geocoder::Lookup
class Baidu < Base
def name
"Baidu"
end
def required_api_key_parts
["key"]
end
# HTTP only
def supported_protocols
[:http]
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://api.map.baidu.com/geocoder/v2/?"
end
def content_key
'result'
end
def results(query, reverse = false)
return [] unless doc = fetch_data(query)
case doc['status']
when 0
return [doc[content_key]] unless doc[content_key].blank?
when 1, 3, 4
raise_error(Geocoder::Error, "server error.") ||
Geocoder.log(:warn, "#{name} Geocoding API error: server error.")
when 2
raise_error(Geocoder::InvalidRequest, "invalid request.") ||
Geocoder.log(:warn, "#{name} Geocoding API error: invalid request.")
when 5
raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
Geocoder.log(:warn, "#{name} Geocoding API error: invalid api key.")
when 101, 102, 200..299
raise_error(Geocoder::RequestDenied, "request denied") ||
Geocoder.log(:warn, "#{name} Geocoding API error: request denied.")
when 300..399
raise_error(Geocoder::OverQueryLimitError, "over query limit.") ||
Geocoder.log(:warn, "#{name} Geocoding API error: over query limit.")
end
return []
end
def query_url_params(query)
{
(query.reverse_geocode? ? :location : :address) => query.sanitized_text,
:ak => configuration.api_key,
:output => "json"
}.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/ipapi_com.rb 0000644 0000041 0000041 00000003744 13503210114 021651 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/ipapi_com'
module Geocoder::Lookup
class IpapiCom < Base
def name
"ip-api.com"
end
def supported_protocols
if configuration.api_key
[:http, :https]
else
[:http]
end
end
private # ----------------------------------------------------------------
def base_query_url(query)
domain = configuration.api_key ? "pro.ip-api.com" : "ip-api.com"
url = "#{protocol}://#{domain}/json/#{query.sanitized_text}"
url << "?" if not url_query_string(query).empty?
url
end
def parse_raw_data(raw_data)
if raw_data.chomp == "invalid key"
invalid_key_result
else
super(raw_data)
end
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 doc["message"] == "invalid key"
raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid API key.")
return []
else
return [doc]
end
end
def reserved_result(query)
{
"message" => "reserved range",
"query" => query,
"status" => "fail",
"ip" => query,
"city" => "",
"region_code" => "",
"region_name" => "",
"metrocode" => "",
"zipcode" => "",
"latitude" => "0",
"longitude" => "0",
"country_name" => "Reserved",
"country_code" => "RD"
}
end
def invalid_key_result
{
"message" => "invalid key"
}
end
def query_url_params(query)
params = {}
params.merge!(fields: configuration[:fields]) if configuration.has_key?(:fields)
params.merge!(key: configuration.api_key) if configuration.api_key
params.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/ipinfo_io.rb 0000644 0000041 0000041 00000001614 13503210114 021656 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/ipinfo_io'
module Geocoder::Lookup
class IpinfoIo < Base
def name
"Ipinfo.io"
end
private # ---------------------------------------------------------------
def base_query_url(query)
url = "#{protocol}://ipinfo.io/#{query.sanitized_text}/geo"
url << "?" if configuration.api_key
url
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?
if !(doc = fetch_data(query)).is_a?(Hash) or doc['error']
[]
else
[doc]
end
end
def reserved_result(ip)
{
"ip" => ip,
"bogon" => true
}
end
def query_url_params(query)
{
token: configuration.api_key
}.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/base.rb 0000644 0000041 0000041 00000024652 13503210114 020624 0 ustar www-data www-data require 'net/http'
require 'net/https'
require 'uri'
unless defined?(ActiveSupport::JSON)
begin
require 'json'
rescue LoadError
raise LoadError, "Please install the 'json' or 'json_pure' gem to parse geocoder results."
end
end
module Geocoder
module Lookup
class Base
def initialize
@cache = nil
end
##
# Human-readable name of the geocoding API.
#
def name
fail
end
##
# Symbol which is used in configuration to refer to this Lookup.
#
def handle
str = self.class.to_s
str[str.rindex(':')+1..-1].gsub(/([a-z\d]+)([A-Z])/,'\1_\2').downcase.to_sym
end
##
# Query the geocoding API and return a Geocoder::Result object.
# Returns +nil+ on timeout or error.
#
# Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
# "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
# for reverse geocoding. Returns an array of Geocoder::Results.
#
def search(query, options = {})
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
results(query).map{ |r|
result = result_class.new(r)
result.cache_hit = @cache_hit if cache
result
}
end
##
# Return the URL for a map of the given coordinates.
#
# Not necessarily implemented by all subclasses as only some lookups
# also provide maps.
#
def map_link_url(coordinates)
nil
end
##
# Array containing string descriptions of keys required by the API.
# Empty array if keys are optional or not required.
#
def required_api_key_parts
[]
end
##
# URL to use for querying the geocoding engine.
#
# Subclasses should not modify this method. Instead they should define
# base_query_url and url_query_string. If absolutely necessary to
# subclss this method, they must also subclass #cache_key.
#
def query_url(query)
base_query_url(query) + url_query_string(query)
end
##
# The working Cache object.
#
def cache
if @cache.nil? and store = configuration.cache
@cache = Cache.new(store, configuration.cache_prefix)
end
@cache
end
##
# Array containing the protocols supported by the api.
# Should be set to [:http] if only HTTP is supported
# or [:https] if only HTTPS is supported.
#
def supported_protocols
[:http, :https]
end
private # -------------------------------------------------------------
##
# String which, when concatenated with url_query_string(query)
# produces the full query URL. Should include the "?" a the end.
#
def base_query_url(query)
fail
end
##
# An object with configuration data for this particular lookup.
#
def configuration
Geocoder.config_for_lookup(handle)
end
##
# Object used to make HTTP requests.
#
def http_client
proxy_name = "#{protocol}_proxy"
if proxy = configuration.send(proxy_name)
proxy_url = !!(proxy =~ /^#{protocol}/) ? proxy : protocol + '://' + proxy
begin
uri = URI.parse(proxy_url)
rescue URI::InvalidURIError
raise ConfigurationError,
"Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'"
end
Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
else
Net::HTTP
end
end
##
# Geocoder::Result object or nil on timeout or other error.
#
def results(query)
fail
end
def query_url_params(query)
query.options[:params] || {}
end
def url_query_string(query)
hash_to_query(
query_url_params(query).reject{ |key,value| value.nil? }
)
end
##
# Key to use for caching a geocoding result. Usually this will be the
# request URL, but in cases where OAuth is used and the nonce,
# timestamp, etc varies from one request to another, we need to use
# something else (like the URL before OAuth encoding).
#
def cache_key(query)
base_query_url(query) + hash_to_query(cache_key_params(query))
end
def cache_key_params(query)
# omit api_key and token because they may vary among requests
query_url_params(query).reject do |key,value|
key.to_s.match(/(key|token)/)
end
end
##
# Class of the result objects
#
def result_class
Geocoder::Result.const_get(self.class.to_s.split(":").last)
end
##
# Raise exception if configuration specifies it should be raised.
# Return false if exception not raised.
#
def raise_error(error, message = nil)
exceptions = configuration.always_raise
if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
raise error, message
else
false
end
end
##
# Returns a parsed search result (Ruby hash).
#
def fetch_data(query)
parse_raw_data fetch_raw_data(query)
rescue SocketError => err
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 Timeout::Error => err
raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " +
"(use Geocoder.configure(:timeout => ...) to set limit).")
end
def parse_json(data)
if defined?(ActiveSupport::JSON)
ActiveSupport::JSON.decode(data)
else
JSON.parse(data)
end
rescue
unless raise_error(ResponseParseError.new(data))
Geocoder.log(:warn, "Geocoding API's response was not valid JSON")
Geocoder.log(:debug, "Raw response: #{data}")
end
end
##
# Parses a raw search result (returns hash or array).
#
def parse_raw_data(raw_data)
parse_json(raw_data)
end
##
# Protocol to use for communication with geocoding services.
# Set in configuration but not available for every service.
#
def protocol
"http" + (use_ssl? ? "s" : "")
end
def valid_response?(response)
(200..399).include?(response.code.to_i)
end
##
# Fetch a raw geocoding result (JSON string).
# The result might or might not be cached.
#
def fetch_raw_data(query)
key = cache_key(query)
if cache and body = cache[key]
@cache_hit = true
else
check_api_key_configuration!(query)
response = make_api_request(query)
check_response_for_errors!(response)
body = response.body
# apply the charset from the Content-Type header, if possible
ct = response['content-type']
if ct && ct['charset']
charset = ct.split(';').select do |s|
s['charset']
end.first.to_s.split('=')
if charset.length == 2
body.force_encoding(charset.last) rescue ArgumentError
end
end
if cache and valid_response?(response)
cache[key] = body
end
@cache_hit = false
end
body
end
def check_response_for_errors!(response)
if response.code.to_i == 400
raise_error(Geocoder::InvalidRequest) ||
Geocoder.log(:warn, "Geocoding API error: 400 Bad Request")
elsif response.code.to_i == 401
raise_error(Geocoder::RequestDenied) ||
Geocoder.log(:warn, "Geocoding API error: 401 Unauthorized")
elsif response.code.to_i == 402
raise_error(Geocoder::OverQueryLimitError) ||
Geocoder.log(:warn, "Geocoding API error: 402 Payment Required")
elsif response.code.to_i == 429
raise_error(Geocoder::OverQueryLimitError) ||
Geocoder.log(:warn, "Geocoding API error: 429 Too Many Requests")
elsif response.code.to_i == 503
raise_error(Geocoder::ServiceUnavailable) ||
Geocoder.log(:warn, "Geocoding API error: 503 Service Unavailable")
end
end
##
# Make an HTTP(S) request to a geocoding API and
# return the response object.
#
def make_api_request(query)
uri = URI.parse(query_url(query))
Geocoder.log(:debug, "Geocoder: HTTP request being made for #{uri.to_s}")
http_client.start(uri.host, uri.port, use_ssl: use_ssl?, open_timeout: configuration.timeout, read_timeout: configuration.timeout) do |client|
configure_ssl!(client) if use_ssl?
req = Net::HTTP::Get.new(uri.request_uri, configuration.http_headers)
if configuration.basic_auth[:user] and configuration.basic_auth[:password]
req.basic_auth(
configuration.basic_auth[:user],
configuration.basic_auth[:password]
)
end
client.request(req)
end
rescue Timeout::Error
raise Geocoder::LookupTimeout
rescue Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::ECONNRESET
raise Geocoder::NetworkError
end
def use_ssl?
if supported_protocols == [:https]
true
elsif supported_protocols == [:http]
false
else
configuration.use_https
end
end
def configure_ssl!(client); end
def check_api_key_configuration!(query)
key_parts = query.lookup.required_api_key_parts
if key_parts.size > Array(configuration.api_key).size
parts_string = key_parts.size == 1 ? key_parts.first : key_parts
raise Geocoder::ConfigurationError,
"The #{query.lookup.name} API requires a key to be configured: " +
parts_string.inspect
end
end
##
# Simulate ActiveSupport's Object#to_query.
# Removes any keys with nil value.
#
def hash_to_query(hash)
require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
hash.collect{ |p|
p[1].nil? ? nil : p.map{ |i| CGI.escape i.to_s } * '='
}.compact.sort * '&'
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/location_iq.rb 0000644 0000041 0000041 00000002423 13503210114 022203 0 ustar www-data www-data require 'geocoder/lookups/nominatim'
require "geocoder/results/location_iq"
module Geocoder::Lookup
class LocationIq < Nominatim
def name
"LocationIq"
end
def required_api_key_parts
["api_key"]
end
private # ----------------------------------------------------------------
def base_query_url(query)
method = query.reverse_geocode? ? "reverse" : "search"
"#{protocol}://#{configured_host}/v1/#{method}.php?"
end
def query_url_params(query)
{
key: configuration.api_key
}.merge(super)
end
def configured_host
configuration[:host] || "locationiq.org"
end
def results(query)
return [] unless doc = fetch_data(query)
if !doc.is_a?(Array)
case doc['error']
when "Invalid key"
raise_error(Geocoder::InvalidApiKey, doc['error'])
when "Key not active - Please write to contact@unwiredlabs.com"
raise_error(Geocoder::RequestDenied, doc['error'])
when "Rate Limited"
raise_error(Geocoder::OverQueryLimitError, doc['error'])
when "Unknown error - Please try again after some time"
raise_error(Geocoder::InvalidRequest, doc['error'])
end
end
doc.is_a?(Array) ? doc : [doc]
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/maxmind.rb 0000644 0000041 0000041 00000004500 13503210114 021335 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/maxmind'
require 'csv'
module Geocoder::Lookup
class Maxmind < Base
def name
"MaxMind"
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://geoip.maxmind.com/#{service_code}?"
end
##
# Return the name of the configured service, or raise an exception.
#
def configured_service!
if s = configuration[:service] and services.keys.include?(s)
return s
else
raise(
Geocoder::ConfigurationError,
"When using MaxMind you MUST specify a service name: " +
"Geocoder.configure(:maxmind => {:service => ...}), " +
"where '...' is one of: #{services.keys.inspect}"
)
end
end
def service_code
services[configured_service!]
end
def service_response_fields_count
Geocoder::Result::Maxmind.field_names[configured_service!].size
end
def data_contains_error?(parsed_data)
# if all fields given then there is an error
parsed_data.size == service_response_fields_count and !parsed_data.last.nil?
end
##
# Service names mapped to code used in URL.
#
def services
{
:country => "a",
:city => "b",
:city_isp_org => "f",
:omni => "e"
}
end
def results(query)
# don't look up a loopback or private address, just return the stored result
return [reserved_result] if query.internal_ip_address?
doc = fetch_data(query)
if doc and doc.is_a?(Array)
if !data_contains_error?(doc)
return [doc]
elsif doc.last == "INVALID_LICENSE_KEY"
raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid MaxMind API key.")
end
end
return []
end
def parse_raw_data(raw_data)
# Maxmind just returns text/plain as csv format but according to documentation,
# we get ISO-8859-1 encoded string. We need to convert it.
CSV.parse_line raw_data.force_encoding("ISO-8859-1").encode("UTF-8")
end
def reserved_result
",,,,0,0,0,0,,,".split(",")
end
def query_url_params(query)
{
:l => configuration.api_key,
:i => query.sanitized_text
}.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/latlon.rb 0000644 0000041 0000041 00000002571 13503210114 021177 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/latlon'
module Geocoder::Lookup
class Latlon < Base
def name
"LatLon.io"
end
def required_api_key_parts
["api_key"]
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://latlon.io/api/v1/#{'reverse_' if query.reverse_geocode?}geocode?"
end
def results(query)
return [] unless doc = fetch_data(query)
if doc['error'].nil?
[doc]
# The API returned a 404 response, which indicates no results found
elsif doc['error']['type'] == 'api_error'
[]
elsif
doc['error']['type'] == 'authentication_error'
raise_error(Geocoder::InvalidApiKey) ||
Geocoder.log(:warn, "LatLon.io service error: invalid API key.")
else
[]
end
end
def supported_protocols
[:https]
end
private # ---------------------------------------------------------------
def query_url_params(query)
if query.reverse_geocode?
{
:token => configuration.api_key,
:lat => query.coordinates[0],
:lon => query.coordinates[1]
}.merge(super)
else
{
:token => configuration.api_key,
:address => query.sanitized_text
}.merge(super)
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/test.rb 0000644 0000041 0000041 00000001452 13503210114 020662 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/test'
module Geocoder
module Lookup
class Test < Base
def name
"Test"
end
def self.add_stub(query_text, results)
stubs[query_text] = results
end
def self.set_default_stub(results)
@default_stub = results
end
def self.read_stub(query_text)
stubs.fetch(query_text) {
return @default_stub unless @default_stub.nil?
raise ArgumentError, "unknown stub request #{query_text}"
}
end
def self.stubs
@stubs ||= {}
end
def self.reset
@stubs = {}
@default_stub = nil
end
private
def results(query)
Geocoder::Lookup::Test.read_stub(query.text)
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/mapbox.rb 0000644 0000041 0000041 00000003057 13503210114 021174 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/mapbox"
module Geocoder::Lookup
class Mapbox < Base
def name
"Mapbox"
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://api.mapbox.com/geocoding/v5/#{dataset}/#{mapbox_search_term(query)}.json?"
end
def results(query)
return [] unless data = fetch_data(query)
if data['features']
sort_relevant_feature(data['features'])
elsif data['message'] =~ /Invalid\sToken/
raise_error(Geocoder::InvalidApiKey, data['message'])
else
[]
end
end
def query_url_params(query)
{access_token: configuration.api_key}.merge(super(query))
end
def mapbox_search_term(query)
require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
if query.reverse_geocode?
lat,lon = query.coordinates
"#{CGI.escape lon},#{CGI.escape lat}"
else
# truncate at first semicolon so Mapbox doesn't go into batch mode
# (see Github issue #1299)
CGI.escape query.text.to_s.split(';').first.to_s
end
end
def dataset
configuration[:dataset] || "mapbox.places"
end
def supported_protocols
[:https]
end
def sort_relevant_feature(features)
# Sort by descending relevance; Favor original order for equal relevance (eg occurs for reverse geocoding)
features.sort_by do |feature|
[feature["relevance"],-features.index(feature)]
end.reverse
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/esri.rb 0000644 0000041 0000041 00000004476 13503210114 020656 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/esri"
require 'geocoder/esri_token'
module Geocoder::Lookup
class Esri < Base
def name
"Esri"
end
private # ---------------------------------------------------------------
def base_query_url(query)
action = query.reverse_geocode? ? "reverseGeocode" : "find"
"#{protocol}://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/#{action}?"
end
def results(query)
return [] unless doc = fetch_data(query)
if (!query.reverse_geocode?)
return [] if !doc['locations'] || doc['locations'].empty?
end
if (doc['error'].nil?)
return [ doc ]
else
return []
end
end
def query_url_params(query)
params = {
:f => "pjson",
:outFields => "*"
}
if query.reverse_geocode?
params[:location] = query.coordinates.reverse.join(',')
else
params[:text] = query.sanitized_text
end
params[:token] = token(query)
if for_storage_value = for_storage(query)
params[:forStorage] = for_storage_value
end
params[:sourceCountry] = configuration[:source_country] if configuration[:source_country]
params.merge(super)
end
def for_storage(query)
if query.options.has_key?(:for_storage)
query.options[:for_storage]
else
configuration[:for_storage]
end
end
def token(query)
token_instance = if query.options[:token]
query.options[:token]
else
configuration[:token]
end
if !valid_token_configured?(token_instance) && configuration.api_key
token_instance = create_and_save_token!(query)
end
token_instance.to_s unless token_instance.nil?
end
def valid_token_configured?(token_instance)
!token_instance.nil? && token_instance.active?
end
def create_and_save_token!(query)
token_instance = create_token
if query.options[:token]
query.options[:token] = token_instance
else
Geocoder.merge_into_lookup_config(:esri, token: token_instance)
end
token_instance
end
def create_token
Geocoder::EsriToken.generate_token(*configuration.api_key)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/google.rb 0000644 0000041 0000041 00000005706 13503210114 021165 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/google"
module Geocoder::Lookup
class Google < Base
def name
"Google"
end
def map_link_url(coordinates)
"http://maps.google.com/maps?q=#{coordinates.join(',')}"
end
def supported_protocols
# Google requires HTTPS if an API key is used.
if configuration.api_key
[:https]
else
[:http, :https]
end
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://maps.googleapis.com/maps/api/geocode/json?"
end
def configure_ssl!(client)
client.instance_eval {
@ssl_context = OpenSSL::SSL::SSLContext.new
options = OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3
if OpenSSL::SSL.const_defined?('OP_NO_COMPRESSION')
options |= OpenSSL::SSL::OP_NO_COMPRESSION
end
@ssl_context.set_params({options: options})
}
end
def valid_response?(response)
json = parse_json(response.body)
status = json["status"] if json
super(response) and ['OK', 'ZERO_RESULTS'].include?(status)
end
def results(query)
return [] unless doc = fetch_data(query)
case doc['status']; when "OK" # OK status implies >0 results
return doc['results']
when "OVER_QUERY_LIMIT"
raise_error(Geocoder::OverQueryLimitError) ||
Geocoder.log(:warn, "#{name} API error: over query limit.")
when "REQUEST_DENIED"
raise_error(Geocoder::RequestDenied, doc['error_message']) ||
Geocoder.log(:warn, "#{name} API error: request denied (#{doc['error_message']}).")
when "INVALID_REQUEST"
raise_error(Geocoder::InvalidRequest, doc['error_message']) ||
Geocoder.log(:warn, "#{name} API error: invalid request (#{doc['error_message']}).")
end
return []
end
def query_url_google_params(query)
params = {
:sensor => "false",
:language => (query.language || configuration.language)
}
if query.options[:google_place_id]
params[:place_id] = query.sanitized_text
else
params[(query.reverse_geocode? ? :latlng : :address)] = query.sanitized_text
end
unless (bounds = query.options[:bounds]).nil?
params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join('|')
end
unless (region = query.options[:region]).nil?
params[:region] = region
end
unless (components = query.options[:components]).nil?
params[:components] = components.is_a?(Array) ? components.join("|") : components
end
unless (result_type = query.options[:result_type]).nil?
params[:result_type] = result_type.is_a?(Array) ? result_type.join("|") : result_type
end
params
end
def query_url_params(query)
query_url_google_params(query).merge(
:key => configuration.api_key
).merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/mapquest.rb 0000644 0000041 0000041 00000003233 13503210114 021541 0 ustar www-data www-data require 'cgi'
require 'geocoder/lookups/base'
require "geocoder/results/mapquest"
module Geocoder::Lookup
class Mapquest < Base
def name
"Mapquest"
end
def required_api_key_parts
["key"]
end
private # ---------------------------------------------------------------
def base_query_url(query)
domain = configuration[:open] ? "open" : "www"
"#{protocol}://#{domain}.mapquestapi.com/geocoding/v1/#{search_type(query)}?"
end
def search_type(query)
query.reverse_geocode? ? "reverse" : "address"
end
def query_url_params(query)
params = { :location => query.sanitized_text }
if key = configuration.api_key
params[:key] = CGI.unescape(key)
end
params.merge(super)
end
# http://www.mapquestapi.com/geocoding/status_codes.html
# http://open.mapquestapi.com/geocoding/status_codes.html
def results(query)
return [] unless doc = fetch_data(query)
return doc["results"][0]['locations'] if doc['info']['statuscode'] == 0 # A successful geocode call
messages = doc['info']['messages'].join
case doc['info']['statuscode']
when 400 # Error with input
raise_error(Geocoder::InvalidRequest, messages) ||
Geocoder.log(:warn, "Mapquest Geocoding API error: #{messages}")
when 403 # Key related error
raise_error(Geocoder::InvalidApiKey, messages) ||
Geocoder.log(:warn, "Mapquest Geocoding API error: #{messages}")
when 500 # Unknown error
raise_error(Geocoder::Error, messages) ||
Geocoder.log(:warn, "Mapquest Geocoding API error: #{messages}")
end
[]
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/dstk.rb 0000644 0000041 0000041 00000001072 13503210114 020646 0 ustar www-data www-data # More information about the Data Science Toolkit can be found at:
# http://www.datasciencetoolkit.org/. The provided APIs mimic the
# Google geocoding api.
require 'geocoder/lookups/google'
require 'geocoder/results/dstk'
module Geocoder::Lookup
class Dstk < Google
def name
"Data Science Toolkit"
end
private # ----------------------------------------------------------------
def base_query_url(query)
host = configuration[:host] || "www.datasciencetoolkit.org"
"#{protocol}://#{host}/maps/api/geocode/json?"
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/google_places_search.rb 0000644 0000041 0000041 00000001201 13503210114 024023 0 ustar www-data www-data require "geocoder/lookups/google"
require "geocoder/results/google_places_search"
module Geocoder
module Lookup
class GooglePlacesSearch < Google
def name
"Google Places Search"
end
def required_api_key_parts
["key"]
end
def supported_protocols
[:https]
end
private
def base_query_url(query)
"#{protocol}://maps.googleapis.com/maps/api/place/textsearch/json?"
end
def query_url_google_params(query)
{
query: query.text,
language: query.language || configuration.language
}
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/geocodio.rb 0000644 0000041 0000041 00000002305 13503210114 021471 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/geocodio"
module Geocoder::Lookup
class Geocodio < Base
def name
"Geocodio"
end
def results(query)
return [] unless doc = fetch_data(query)
return doc["results"] if doc['error'].nil?
if doc['error'] == 'Invalid API key'
raise_error(Geocoder::InvalidApiKey) ||
Geocoder.log(:warn, "Geocodio service error: invalid API key.")
elsif doc['error'].match(/You have reached your daily maximum/)
raise_error(Geocoder::OverQueryLimitError, doc['error']) ||
Geocoder.log(:warn, "Geocodio service error: #{doc['error']}.")
else
raise_error(Geocoder::InvalidRequest, doc['error']) ||
Geocoder.log(:warn, "Geocodio service error: #{doc['error']}.")
end
[]
end
private # ---------------------------------------------------------------
def base_query_url(query)
path = query.reverse_geocode? ? "reverse" : "geocode"
"#{protocol}://api.geocod.io/v1.3/#{path}?"
end
def query_url_params(query)
{
:api_key => configuration.api_key,
:q => query.sanitized_text
}.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/ipstack.rb 0000644 0000041 0000041 00000003023 13503210114 021335 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/ipstack'
module Geocoder::Lookup
class Ipstack < Base
ERROR_CODES = {
404 => Geocoder::InvalidRequest,
101 => Geocoder::InvalidApiKey,
102 => Geocoder::Error,
103 => Geocoder::InvalidRequest,
104 => Geocoder::OverQueryLimitError,
105 => Geocoder::RequestDenied,
301 => Geocoder::InvalidRequest,
302 => Geocoder::InvalidRequest,
303 => Geocoder::RequestDenied,
}
ERROR_CODES.default = Geocoder::Error
def name
"Ipstack"
end
private # ----------------------------------------------------------------
def base_query_url(query)
"#{protocol}://#{host}/#{query.sanitized_text}?"
end
def query_url_params(query)
{
access_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['info']
raise_error(ERROR_CODES[code], msg ) || Geocoder.log(:warn, "Ipstack Geocoding API error: #{msg}")
return []
end
[doc]
end
def reserved_result(ip)
{
"ip" => ip,
"country_name" => "Reserved",
"country_code" => "RD"
}
end
def host
configuration[:host] || "api.ipstack.com"
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/google_places_details.rb 0000644 0000041 0000041 00000002424 13503210114 024213 0 ustar www-data www-data require "geocoder/lookups/google"
require "geocoder/results/google_places_details"
module Geocoder
module Lookup
class GooglePlacesDetails < Google
def name
"Google Places Details"
end
def required_api_key_parts
["key"]
end
def supported_protocols
[:https]
end
private
def base_query_url(query)
"#{protocol}://maps.googleapis.com/maps/api/place/details/json?"
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.")
end
[]
end
def query_url_google_params(query)
{
placeid: query.text,
language: query.language || configuration.language
}
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/pickpoint.rb 0000644 0000041 0000041 00000001570 13503210114 021704 0 ustar www-data www-data require "geocoder/lookups/nominatim"
require "geocoder/results/pickpoint"
module Geocoder::Lookup
class Pickpoint < Nominatim
def name
"Pickpoint"
end
def supported_protocols
[:https]
end
def required_api_key_parts
["api_key"]
end
private # ----------------------------------------------------------------
def base_query_url(query)
method = query.reverse_geocode? ? "reverse" : "forward"
"#{protocol}://api.pickpoint.io/v1/#{method}?"
end
def query_url_params(query)
params = {
key: configuration.api_key
}.merge(super)
end
def results(query)
return [] unless doc = fetch_data(query)
if !doc.is_a?(Array) && doc['message'] == 'Unauthorized'
raise_error(Geocoder::InvalidApiKey, 'Unauthorized')
end
doc.is_a?(Array) ? doc : [doc]
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/nominatim.rb 0000644 0000041 0000041 00000003103 13503210114 021671 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/nominatim"
module Geocoder::Lookup
class Nominatim < Base
def name
"Nominatim"
end
def map_link_url(coordinates)
"https://www.openstreetmap.org/?lat=#{coordinates[0]}&lon=#{coordinates[1]}&zoom=15&layers=M"
end
private # ---------------------------------------------------------------
def base_query_url(query)
method = query.reverse_geocode? ? "reverse" : "search"
"#{protocol}://#{configured_host}/#{method}?"
end
def configured_host
configuration[:host] || "nominatim.openstreetmap.org"
end
def use_ssl?
# nominatim.openstreetmap.org redirects HTTP requests to HTTPS
if configured_host == "nominatim.openstreetmap.org"
true
else
super
end
end
def results(query)
return [] unless doc = fetch_data(query)
doc.is_a?(Array) ? doc : [doc]
end
def parse_raw_data(raw_data)
if raw_data.include?("Bandwidth limit exceeded")
raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Over API query limit.")
else
super(raw_data)
end
end
def query_url_params(query)
params = {
:format => "json",
:addressdetails => "1",
:"accept-language" => (query.language || configuration.language)
}.merge(super)
if query.reverse_geocode?
lat,lon = query.coordinates
params[:lat] = lat
params[:lon] = lon
else
params[:q] = query.sanitized_text
end
params
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/freegeoip.rb 0000644 0000041 0000041 00000002603 13503210114 021647 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/freegeoip'
module Geocoder::Lookup
class Freegeoip < Base
def name
"FreeGeoIP"
end
def supported_protocols
if configuration[:host]
[:http, :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)
end
def parse_raw_data(raw_data)
raw_data.match(/^404/) ? nil : super(raw_data)
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?
# note: Freegeoip.net returns plain text "Not Found" on bad request
(doc = fetch_data(query)) ? [doc] : []
end
def reserved_result(ip)
{
"ip" => ip,
"city" => "",
"region_code" => "",
"region_name" => "",
"metrocode" => "",
"zipcode" => "",
"latitude" => "0",
"longitude" => "0",
"country_name" => "Reserved",
"country_code" => "RD"
}
end
def host
configuration[:host] || "freegeoip.net"
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/pelias.rb 0000644 0000041 0000041 00000003252 13503210114 021160 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/pelias'
module Geocoder::Lookup
class Pelias < Base
def name
'Pelias'
end
def endpoint
configuration[:endpoint] || 'localhost'
end
def required_api_key_parts
['search-XXXX']
end
private # ----------------------------------------------------------------
def base_query_url(query)
query_type = query.reverse_geocode? ? 'reverse' : 'search'
"#{protocol}://#{endpoint}/v1/#{query_type}?"
end
def query_url_params(query)
params = {
api_key: configuration.api_key
}.merge(super)
if query.reverse_geocode?
lat, lon = query.coordinates
params[:'point.lat'] = lat
params[:'point.lon'] = lon
else
params[:text] = query.text
end
params
end
def results(query)
return [] unless doc = fetch_data(query)
# not all responses include a meta
if doc['meta']
error = doc.fetch('results', {}).fetch('error', {})
message = error.fetch('type', 'Unknown Error') + ': ' + error.fetch('message', 'No message')
log_message = 'Pelias Geocoding API error - ' + message
case doc['meta']['status_code']
when '200'
# nothing to see here
when '403'
raise_error(Geocoder::RequestDenied, message) || Geocoder.log(:warn, log_message)
when '429'
raise_error(Geocoder::OverQueryLimitError, message) || Geocoder.log(:warn, log_message)
else
raise_error(Geocoder::Error, message) || Geocoder.log(:warn, log_message)
end
end
doc['features'] || []
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/db_ip_com.rb 0000644 0000041 0000041 00000002410 13503210114 021611 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/db_ip_com'
module Geocoder::Lookup
class DbIpCom < Base
def name
'DB-IP.com'
end
def supported_protocols
[:https, :http]
end
def required_api_key_parts
['api_key']
end
private # ----------------------------------------------------------------
def base_query_url(query)
"#{protocol}://api.db-ip.com/v2/#{configuration.api_key}/#{query.sanitized_text}?"
end
##
# Same as query_url but without the api key.
#
def cache_key(query)
"#{protocol}://api.db-ip.com/v2/#{query.sanitized_text}?" + hash_to_query(cache_key_params(query))
end
def results(query)
return [] unless (doc = fetch_data(query))
case doc['error']
when 'maximum number of queries per day exceeded'
raise_error Geocoder::OverQueryLimitError ||
Geocoder.log(:warn, 'DB-API query limit exceeded.')
when 'invalid API key'
raise_error Geocoder::InvalidApiKey ||
Geocoder.log(:warn, 'Invalid DB-IP API key.')
when nil
[doc]
else
raise_error Geocoder::Error ||
Geocoder.log(:warn, "Request failed: #{doc['error']}")
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/google_premier.rb 0000644 0000041 0000041 00000003040 13503210114 022675 0 ustar www-data www-data require 'openssl'
require 'base64'
require 'geocoder/lookups/google'
require 'geocoder/results/google_premier'
module Geocoder::Lookup
class GooglePremier < Google
def name
"Google Premier"
end
def required_api_key_parts
["private key"]
end
def query_url(query)
path = "/maps/api/geocode/json?" + url_query_string(query)
"#{protocol}://maps.googleapis.com#{path}&signature=#{sign(path)}"
end
private # ---------------------------------------------------------------
def cache_key(query)
"#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + hash_to_query(cache_key_params(query))
end
def cache_key_params(query)
query_url_google_params(query).merge(super).reject do |k,v|
[:key, :client, :channel].include?(k)
end
end
def query_url_params(query)
query_url_google_params(query).merge(super).merge(
:key => nil, # don't use param inherited from Google lookup
:client => configuration.api_key[1],
:channel => configuration.api_key[2]
)
end
def sign(string)
raw_private_key = url_safe_base64_decode(configuration.api_key[0])
digest = OpenSSL::Digest.new('sha1')
raw_signature = OpenSSL::HMAC.digest(digest, raw_private_key, string)
url_safe_base64_encode(raw_signature)
end
def url_safe_base64_decode(base64_string)
Base64.decode64(base64_string.tr('-_', '+/'))
end
def url_safe_base64_encode(raw)
Base64.encode64(raw).tr('+/', '-_').strip
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/geoip2.rb 0000644 0000041 0000041 00000001760 13503210114 021072 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/geoip2'
module Geocoder
module Lookup
class Geoip2 < Base
attr_reader :gem_name
def initialize
unless configuration[:file].nil?
begin
@gem_name = configuration[:lib] || 'maxminddb'
require @gem_name
rescue LoadError
raise "Could not load Maxmind DB dependency. To use the GeoIP2 lookup you must add the #{@gem_name} gem to your Gemfile or have it installed in your system."
end
@mmdb = db_class.new(configuration[:file].to_s)
end
super
end
def name
'GeoIP2'
end
def required_api_key_parts
[]
end
private
def db_class
gem_name == 'hive_geoip2' ? Hive::GeoIP2 : MaxMindDB
end
def results(query)
return [] unless configuration[:file]
result = @mmdb.lookup(query.to_s)
result.nil? ? [] : [result]
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/geocoder_ca.rb 0000644 0000041 0000041 00000002445 13503210114 022140 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/geocoder_ca"
module Geocoder::Lookup
class GeocoderCa < Base
def name
"Geocoder.ca"
end
private # ---------------------------------------------------------------
def base_query_url(query)
"#{protocol}://geocoder.ca/?"
end
def results(query)
return [] unless doc = fetch_data(query)
if doc['error'].nil?
return [doc]
elsif doc['error']['code'] == "005"
# "Postal Code is not in the proper Format" => no results, just shut up
else
Geocoder.log(:warn, "Geocoder.ca service error: #{doc['error']['code']} (#{doc['error']['description']}).")
end
return []
end
def query_url_params(query)
params = {
:geoit => "xml",
:jsonp => 1,
:callback => "test",
:auth => configuration.api_key
}.merge(super)
if query.reverse_geocode?
lat,lon = query.coordinates
params[:latt] = lat
params[:longt] = lon
params[:corner] = 1
params[:reverse] = 1
else
params[:locate] = query.sanitized_text
params[:showpostal] = 1
end
params
end
def parse_raw_data(raw_data)
super raw_data[/^test\((.*)\)\;\s*$/, 1]
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/ip2location.rb 0000644 0000041 0000041 00000004517 13503210114 022133 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/ip2location'
module Geocoder::Lookup
class Ip2location < Base
def name
"IP2LocationApi"
end
def supported_protocols
[:http, :https]
end
private # ----------------------------------------------------------------
def base_query_url(query)
"#{protocol}://api.ip2location.com/?"
end
def query_url_params(query)
{
key: configuration.api_key ? configuration.api_key : "demo",
format: "json",
ip: query.sanitized_text
}.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 doc["response"] == "INVALID ACCOUNT"
raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "INVALID ACCOUNT")
return []
else
return [doc]
end
end
def reserved_result(query)
{
"country_code" => "INVALID IP ADDRESS",
"country_name" => "INVALID IP ADDRESS",
"region_name" => "INVALID IP ADDRESS",
"city_name" => "INVALID IP ADDRESS",
"latitude" => "INVALID IP ADDRESS",
"longitude" => "INVALID IP ADDRESS",
"zip_code" => "INVALID IP ADDRESS",
"time_zone" => "INVALID IP ADDRESS",
"isp" => "INVALID IP ADDRESS",
"domain" => "INVALID IP ADDRESS",
"net_speed" => "INVALID IP ADDRESS",
"idd_code" => "INVALID IP ADDRESS",
"area_code" => "INVALID IP ADDRESS",
"weather_station_code" => "INVALID IP ADDRESS",
"weather_station_name" => "INVALID IP ADDRESS",
"mcc" => "INVALID IP ADDRESS",
"mnc" => "INVALID IP ADDRESS",
"mobile_brand" => "INVALID IP ADDRESS",
"elevation" => "INVALID IP ADDRESS",
"usage_type" => "INVALID IP ADDRESS"
}
end
def query_url_params(query)
params = super
if configuration.has_key?(:package)
params.merge!(package: configuration[:package])
end
params
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/geocoder_us.rb 0000644 0000041 0000041 00000002251 13503210114 022177 0 ustar www-data www-data 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
geocoder-1.5.1/lib/geocoder/lookups/pointpin.rb 0000644 0000041 0000041 00000003561 13503210114 021546 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/pointpin'
module Geocoder::Lookup
class Pointpin < Base
def name
"Pointpin"
end
def required_api_key_parts
["key"]
end
def query_url(query)
"#{protocol}://geo.pointp.in/#{configuration.api_key}/json/#{query.sanitized_text}"
end
private # ----------------------------------------------------------------
def cache_key(query)
"#{protocol}://geo.pointp.in/json/#{query.sanitized_text}"
end
def results(query)
# don't look up a loopback or private address, just return the stored result
return [] if query.internal_ip_address?
doc = fetch_data(query)
if doc and doc.is_a?(Hash)
if !data_contains_error?(doc)
return [doc]
elsif doc['error']
case doc['error']
when "Invalid IP address"
raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, "Invalid Pointpin request.")
when "Invalid API key"
raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid Pointpin API key.")
when "Address not found"
Geocoder.log(:warn, "Address not found.")
end
else
raise_error(Geocoder::Error) || Geocoder.log(:warn, "Pointpin server error")
end
end
return []
end
def data_contains_error?(parsed_data)
parsed_data.keys.include?('error')
end
# TODO: replace this hash with what's actually returned by Pointpin
def reserved_result(ip)
{
"ip" => ip,
"city" => "",
"region_code" => "",
"region_name" => "",
"metrocode" => "",
"zipcode" => "",
"latitude" => "0",
"longitude" => "0",
"country_name" => "Reserved",
"country_code" => "RD"
}
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/maxmind_local.rb 0000644 0000041 0000041 00000004032 13503210114 022507 0 ustar www-data www-data require 'ipaddr'
require 'geocoder/lookups/base'
require 'geocoder/results/maxmind_local'
module Geocoder::Lookup
class MaxmindLocal < Base
def initialize
if !configuration[:file].nil?
begin
gem = RUBY_PLATFORM == 'java' ? 'jgeoip' : 'geoip'
require gem
rescue LoadError
raise "Could not load geoip dependency. To use MaxMind Local lookup you must add the #{gem} gem to your Gemfile or have it installed in your system."
end
end
super
end
def name
"MaxMind Local"
end
def required_api_key_parts
[]
end
private
def results(query)
if configuration[:file]
geoip_class = RUBY_PLATFORM == "java" ? JGeoIP : GeoIP
result = geoip_class.new(configuration[:file]).city(query.to_s)
result.nil? ? [] : [encode_hash(result.to_hash)]
elsif configuration[:package] == :city
addr = IPAddr.new(query.text).to_i
q = "SELECT l.country, l.region, l.city, l.latitude, l.longitude
FROM maxmind_geolite_city_location l WHERE l.loc_id = (SELECT b.loc_id FROM maxmind_geolite_city_blocks b
WHERE b.start_ip_num <= #{addr} AND #{addr} <= b.end_ip_num)"
format_result(q, [:country_name, :region_name, :city_name, :latitude, :longitude])
elsif configuration[:package] == :country
addr = IPAddr.new(query.text).to_i
q = "SELECT country, country_code FROM maxmind_geolite_country
WHERE start_ip_num <= #{addr} AND #{addr} <= end_ip_num"
format_result(q, [:country_name, :country_code2])
end
end
def encode_hash(hash, encoding = "UTF-8")
hash.inject({}) do |h,i|
h[i[0]] = i[1].is_a?(String) ? i[1].encode(encoding) : i[1]
h
end
end
def format_result(query, attr_names)
if r = ActiveRecord::Base.connection.execute(query).first
r = r.values if r.is_a?(Hash) # some db adapters return Hash, some Array
[Hash[*attr_names.zip(r).flatten]]
else
[]
end
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/bing.rb 0000644 0000041 0000041 00000005303 13503210114 020621 0 ustar www-data www-data require 'geocoder/lookups/base'
require "geocoder/results/bing"
module Geocoder::Lookup
class Bing < Base
def name
"Bing"
end
def map_link_url(coordinates)
"http://www.bing.com/maps/default.aspx?cp=#{coordinates.join('~')}"
end
def required_api_key_parts
["key"]
end
private # ---------------------------------------------------------------
def base_query_url(query)
text = CGI.escape(query.sanitized_text.strip)
url = "#{protocol}://dev.virtualearth.net/REST/v1/Locations/"
if query.reverse_geocode?
url + "#{text}?"
else
if r = query.options[:region]
url << "#{r}/"
end
# use the more forgiving 'unstructured' query format to allow special
# chars, newlines, brackets, typos.
url + "?q=#{text}&"
end
end
def results(query)
return [] unless doc = fetch_data(query)
if doc['statusCode'] == 200
return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
elsif doc['statusCode'] == 401 and doc["authenticationResultCode"] == "InvalidCredentials"
raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid Bing API key.")
elsif doc['statusCode'] == 403
raise_error(Geocoder::RequestDenied) || Geocoder.log(:warn, "Bing Geocoding API error: Forbidden Request")
elsif [500, 503].include?(doc['statusCode'])
raise_error(Geocoder::ServiceUnavailable) ||
Geocoder.log(:warn, "Bing Geocoding API error: Service Unavailable")
else
Geocoder.log(:warn, "Bing Geocoding API error: #{doc['statusCode']} (#{doc['statusDescription']}).")
end
return []
end
def query_url_params(query)
{
key: configuration.api_key,
language: (query.language || configuration.language)
}.merge(super)
end
def check_response_for_errors!(response)
super
if server_overloaded?(response)
raise_error(Geocoder::ServiceUnavailable) ||
Geocoder.log(:warn, "Bing Geocoding API error: Service Unavailable")
end
end
def valid_response?(response)
super(response) and not server_overloaded?(response)
end
def server_overloaded?(response)
# Occasionally, the servers processing service requests can be overloaded,
# and you may receive some responses that contain no results for queries that
# you would normally receive a result. To identify this situation,
# check the HTTP headers of the response. If the HTTP header X-MS-BM-WS-INFO is set to 1,
# it is best to wait a few seconds and try again.
response['x-ms-bm-ws-info'].to_i == 1
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/smarty_streets.rb 0000644 0000041 0000041 00000003145 13503210114 022774 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/smarty_streets'
module Geocoder::Lookup
class SmartyStreets < Base
def name
"SmartyStreets"
end
def required_api_key_parts
%w(auth-id auth-token)
end
# required by API as of 26 March 2015
def supported_protocols
[:https]
end
private # ---------------------------------------------------------------
def base_query_url(query)
if international?(query)
"#{protocol}://international-street.api.smartystreets.com/verify?"
elsif zipcode_only?(query)
"#{protocol}://us-zipcode.api.smartystreets.com/lookup?"
else
"#{protocol}://us-street.api.smartystreets.com/street-address?"
end
end
def zipcode_only?(query)
!query.text.is_a?(Array) and query.to_s.strip =~ /\A\d{5}(-\d{4})?\Z/
end
def international?(query)
!query.options[:country].nil?
end
def query_url_params(query)
params = {}
if international?(query)
params[:freeform] = query.sanitized_text
params[:country] = query.options[:country]
params[:geocode] = true
elsif zipcode_only?(query)
params[:zipcode] = query.sanitized_text
else
params[:street] = query.sanitized_text
end
if configuration.api_key.is_a?(Array)
params[:"auth-id"] = configuration.api_key[0]
params[:"auth-token"] = configuration.api_key[1]
else
params[:"auth-token"] = configuration.api_key
end
params.merge(super)
end
def results(query)
fetch_data(query) || []
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/postcode_anywhere_uk.rb 0000644 0000041 0000041 00000003160 13503210114 024122 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/postcode_anywhere_uk'
module Geocoder::Lookup
class PostcodeAnywhereUk < Base
# API documentation: http://www.postcodeanywhere.co.uk/Support/WebService/Geocoding/UK/Geocode/2/
DAILY_LIMIT_EXEEDED_ERROR_CODES = ['8', '17'] # api docs say these two codes are the same error
INVALID_API_KEY_ERROR_CODE = '2'
def name
'PostcodeAnywhereUk'
end
def required_api_key_parts
%w(key)
end
private # ----------------------------------------------------------------
def base_query_url(query)
"#{protocol}://services.postcodeanywhere.co.uk/Geocoding/UK/Geocode/v2.00/json.ws?"
end
def results(query)
response = fetch_data(query)
return [] if response.nil? || !response.is_a?(Array) || response.empty?
raise_exception_for_response(response[0]) if response[0]['Error']
response
end
def raise_exception_for_response(response)
case response['Error']
when *DAILY_LIMIT_EXEEDED_ERROR_CODES
raise_error(Geocoder::OverQueryLimitError, response['Cause']) || Geocoder.log(:warn, response['Cause'])
when INVALID_API_KEY_ERROR_CODE
raise_error(Geocoder::InvalidApiKey, response['Cause']) || Geocoder.log(:warn, response['Cause'])
else # anything else just raise general error with the api cause
raise_error(Geocoder::Error, response['Cause']) || Geocoder.log(:warn, response['Cause'])
end
end
def query_url_params(query)
{
:location => query.sanitized_text,
:key => configuration.api_key
}.merge(super)
end
end
end
geocoder-1.5.1/lib/geocoder/lookups/maxmind_geoip2.rb 0000644 0000041 0000041 00000003504 13503210114 022605 0 ustar www-data www-data require 'geocoder/lookups/base'
require 'geocoder/results/maxmind_geoip2'
module Geocoder::Lookup
class MaxmindGeoip2 < Base
def name
"MaxMind GeoIP2"
end
# Maxmind's GeoIP2 Precision Services only supports HTTPS,
# otherwise a `404 Not Found` HTTP response will be returned
def supported_protocols
[:https]
end
def query_url(query)
"#{protocol}://geoip.maxmind.com/geoip/v2.1/#{configured_service!}/#{query.sanitized_text.strip}"
end
private # ---------------------------------------------------------------
def cache_key(query)
query_url(query)
end
##
# Return the name of the configured service, or raise an exception.
#
def configured_service!
if s = configuration[:service] and services.include?(s) and configuration[:basic_auth][:user] and configuration[:basic_auth][:password]
return s
else
raise(
Geocoder::ConfigurationError, "When using MaxMind GeoIP2 you must specify a service and credentials: Geocoder.configure(maxmind_geoip2: {service: ..., basic_auth: {user: ..., password: ...}}), where service is one of: #{services.inspect}"
)
end
end
def data_contains_error?(doc)
(["code", "error"] - doc.keys).empty?
end
##
# Service names used in URL.
#
def services
[
:country,
:city,
:insights,
]
end
def results(query)
# don't look up a loopback or private address, just return the stored result
return [] if query.internal_ip_address?
doc = fetch_data(query)
if doc
if !data_contains_error?(doc)
return [doc]
else
Geocoder.log(:warn, "MaxMind GeoIP2 Geocoding API error: #{doc['code']} (#{doc['error']}).")
end
end
return []
end
end
end
geocoder-1.5.1/lib/geocoder/request.rb 0000644 0000041 0000041 00000010530 13503210114 017674 0 ustar www-data www-data require 'ipaddr'
module Geocoder
module Request
# The location() method is vulnerable to trivial IP spoofing.
# Don't use it in authorization/authentication code, or any
# other security-sensitive application. Use safe_location
# instead.
def location
@location ||= Geocoder.search(geocoder_spoofable_ip, ip_address: true).first
end
# This safe_location() protects you from trivial IP spoofing.
# For requests that go through a proxy that you haven't
# whitelisted as trusted in your Rack config, you will get the
# location for the IP of the last untrusted proxy in the chain,
# not the original client IP. You WILL NOT get the location
# corresponding to the original client IP for any request sent
# through a non-whitelisted proxy.
def safe_location
@safe_location ||= Geocoder.search(ip, ip_address: true).first
end
# There's a whole zoo of nonstandard headers added by various
# proxy softwares to indicate original client IP.
# ANY of these can be trivially spoofed!
# (except REMOTE_ADDR, which should by set by your server,
# and is included at the end as a fallback.
# Order does matter: we're following the convention established in
# ActionDispatch::RemoteIp::GetIp::calculate_ip()
# https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb
# where the forwarded_for headers, possibly containing lists,
# are arbitrarily preferred over headers expected to contain a
# single address.
GEOCODER_CANDIDATE_HEADERS = ['HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'HTTP_X_CLIENT_IP',
'HTTP_CLIENT_IP',
'HTTP_X_REAL_IP',
'HTTP_X_CLUSTER_CLIENT_IP',
'REMOTE_ADDR']
def geocoder_spoofable_ip
# We could use a more sophisticated IP-guessing algorithm here,
# in which we'd try to resolve the use of different headers by
# different proxies. The idea is that by comparing IPs repeated
# in different headers, you can sometimes decide which header
# was used by a proxy further along in the chain, and thus
# prefer the headers used earlier. However, the gains might not
# be worth the performance tradeoff, since this method is likely
# to be called on every request in a lot of applications.
GEOCODER_CANDIDATE_HEADERS.each do |header|
if @env.has_key? header
addrs = geocoder_split_ip_addresses(@env[header])
addrs = geocoder_remove_port_from_addresses(addrs)
addrs = geocoder_reject_non_ipv4_addresses(addrs)
addrs = geocoder_reject_trusted_ip_addresses(addrs)
return addrs.first if addrs.any?
end
end
@env['REMOTE_ADDR']
end
private
def geocoder_split_ip_addresses(ip_addresses)
ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
end
# use Rack's trusted_proxy?() method to filter out IPs that have
# been configured as trusted; includes private ranges by
# default. (we don't want every lookup to return the location
# of our own proxy/load balancer)
def geocoder_reject_trusted_ip_addresses(ip_addresses)
ip_addresses.reject { |ip| trusted_proxy?(ip) }
end
def geocoder_remove_port_from_addresses(ip_addresses)
ip_addresses.map do |ip|
# IPv4
if ip.count('.') > 0
ip.split(':').first
# IPv6 bracket notation
elsif match = ip.match(/\[(\S+)\]/)
match.captures.first
# IPv6 bare notation
else
ip
end
end
end
def geocoder_reject_non_ipv4_addresses(ip_addresses)
ips = []
for ip in ip_addresses
begin
valid_ip = IPAddr.new(ip)
rescue
valid_ip = false
end
ips << valid_ip.to_s if valid_ip
end
return ips.any? ? ips : ip_addresses
end
end
end
ActionDispatch::Request.__send__(:include, Geocoder::Request) if defined?(ActionDispatch::Request)
Rack::Request.__send__(:include, Geocoder::Request) if defined?(Rack::Request)
geocoder-1.5.1/lib/geocoder/configuration.rb 0000644 0000041 0000041 00000007156 13503210114 021065 0 ustar www-data www-data require 'singleton'
require 'geocoder/configuration_hash'
module Geocoder
##
# Configuration options should be set by passing a hash:
#
# Geocoder.configure(
# :timeout => 5,
# :lookup => :yandex,
# :api_key => "2a9fsa983jaslfj982fjasd",
# :units => :km
# )
#
def self.configure(options = nil, &block)
if !options.nil?
Configuration.instance.configure(options)
end
end
##
# Read-only access to the singleton's config data.
#
def self.config
Configuration.instance.data
end
##
# Read-only access to lookup-specific config data.
#
def self.config_for_lookup(lookup_name)
data = config.clone
data.reject!{ |key,value| !Configuration::OPTIONS.include?(key) }
if config.has_key?(lookup_name)
data.merge!(config[lookup_name])
end
data
end
##
# Merge the given hash into a lookup's existing configuration.
#
def self.merge_into_lookup_config(lookup_name, options)
base = Geocoder.config[lookup_name]
Geocoder.configure(lookup_name => base.merge(options))
end
class Configuration
include Singleton
OPTIONS = [
:timeout,
:lookup,
:ip_lookup,
:language,
:http_headers,
:use_https,
:http_proxy,
:https_proxy,
:api_key,
:cache,
:cache_prefix,
:always_raise,
:units,
:distances,
:basic_auth,
:logger,
:kernel_logger_level
]
attr_accessor :data
def self.set_defaults
instance.set_defaults
end
OPTIONS.each do |o|
define_method o do
@data[o]
end
define_method "#{o}=" do |value|
@data[o] = value
end
end
def configure(options)
@data.rmerge!(options)
end
def initialize # :nodoc
@data = Geocoder::ConfigurationHash.new
set_defaults
end
def set_defaults
# geocoding options
@data[:timeout] = 3 # geocoding service timeout (secs)
@data[:lookup] = :nominatim # name of street address geocoding service (symbol)
@data[:ip_lookup] = :ipinfo_io # name of IP address geocoding service (symbol)
@data[:language] = :en # ISO-639 language code
@data[:http_headers] = {} # HTTP headers for lookup
@data[:use_https] = false # use HTTPS for lookup requests? (if supported)
@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
# exceptions that should not be rescued by default
# (if you want to implement custom error handling);
# supports SocketError and Timeout::Error
@data[:always_raise] = []
# calculation options
@data[:units] = :mi # :mi or :km
@data[:distances] = :linear # :linear or :spherical
end
instance_eval(OPTIONS.map do |option|
o = option.to_s
<<-EOS
def #{o}
instance.data[:#{o}]
end
def #{o}=(value)
instance.data[:#{o}] = value
end
EOS
end.join("\n\n"))
end
end
geocoder-1.5.1/lib/geocoder/configuration_hash.rb 0000644 0000041 0000041 00000000331 13503210114 022054 0 ustar www-data www-data require 'hash_recursive_merge'
module Geocoder
class ConfigurationHash < Hash
include HashRecursiveMerge
def method_missing(meth, *args, &block)
has_key?(meth) ? self[meth] : super
end
end
end
geocoder-1.5.1/lib/geocoder/results/ 0000755 0000041 0000041 00000000000 13503210114 017361 5 ustar www-data www-data geocoder-1.5.1/lib/geocoder/results/opencagedata.rb 0000644 0000041 0000041 00000004070 13503210114 022322 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Opencagedata < Base
def poi
%w[stadium bus_stop tram_stop].each do |key|
return @data['components'][key] if @data['components'].key?(key)
end
return nil
end
def house_number
@data['components']['house_number']
end
def address
@data['formatted']
end
def street
%w[road pedestrian highway].each do |key|
return @data['components'][key] if @data['components'].key?(key)
end
return nil
end
def city
%w[city town village hamlet].each do |key|
return @data['components'][key] if @data['components'].key?(key)
end
return nil
end
def village
@data['components']['village']
end
def state
@data['components']['state']
end
def state_code
@data['components']['state_code']
end
def postal_code
@data['components']['postcode'].to_s
end
def county
@data['components']['county']
end
def country
@data['components']['country']
end
def country_code
@data['components']['country_code']
end
def suburb
@data['components']['suburb']
end
def coordinates
[@data['geometry']['lat'].to_f, @data['geometry']['lng'].to_f]
end
def viewport
bounds = @data['bounds'] || fail
south, west = %w(lat lng).map { |i| bounds['southwest'][i] }
north, east = %w(lat lng).map { |i| bounds['northeast'][i] }
[south, west, north, east]
end
def time_zone
# The OpenCage API documentation states that `annotations` is available
# "when possible" https://geocoder.opencagedata.com/api#annotations
@data
.fetch('annotations', {})
.fetch('timezone', {})
.fetch('name', nil)
end
def self.response_attributes
%w[boundingbox license
formatted stadium]
end
response_attributes.each do |a|
unless method_defined?(a)
define_method a do
@data[a]
end
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/here.rb 0000644 0000041 0000041 00000002777 13503210114 020646 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Here < Base
##
# A string in the given format.
#
def address(format = :full)
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]
end
def route
address_data['Street']
end
def street_number
address_data['HouseNumber']
end
def state
address_data['County']
end
def province
address_data['County']
end
def postal_code
address_data['PostalCode']
end
def city
address_data['City']
end
def state_code
address_data['State']
end
def province_code
address_data['State']
end
def country
fail unless d = address_data['AdditionalData']
if v = d.find{|ad| ad['key']=='CountryName'}
return v['value']
end
end
def country_code
address_data['Country']
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']
[south, west, north, east]
end
private # ----------------------------------------------------------------
def address_data
@data['Location']['Address'] || fail
end
end
end
geocoder-1.5.1/lib/geocoder/results/amap.rb 0000644 0000041 0000041 00000003354 13503210114 020631 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Amap < Base
def coordinates
location = @data['location'] || @data['roadinters'].try(:first).try(:[], 'location') \
|| address_components.try(:[], 'streetNumber').try(:[], 'location')
location.to_s.split(",").reverse.map(&:to_f)
end
def address
formatted_address
end
def state
province
end
def province
address_components['province']
end
def city
address_components['city'] == [] ? province : address_components["city"]
end
def district
address_components['district']
end
def street
if address_components["neighborhood"]["name"] != []
return address_components["neighborhood"]["name"]
elsif address_components['township'] != []
return address_components["township"]
else
return @data['street'] || address_components['streetNumber'].try(:[], 'street')
end
end
def street_number
@data['number'] || address_components['streetNumber'].try(:[], 'number')
end
def formatted_address
@data['formatted_address']
end
def address_components
@data['addressComponent'] || @data
end
def state_code
""
end
def postal_code
""
end
def country
"China"
end
def country_code
"CN"
end
##
# Get address components of a given type. Valid types are defined in
# Baidu's Geocoding API documentation and include (among others):
#
# :business
# :cityCode
#
def self.response_attributes
%w[roads pois roadinters]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end geocoder-1.5.1/lib/geocoder/results/ipdata_co.rb 0000644 0000041 0000041 00000001157 13503210114 021635 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class IpdataCo < Base
def city
@data['city']
end
def state
@data['region']
end
def state_code
@data['region_code']
end
def country
@data['country_name']
end
def country_code
@data['country_code']
end
def postal_code
@data['postal']
end
def self.response_attributes
%w[ip asn organisation currency currency_symbol calling_code flag time_zone is_eu]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/geoportail_lu.rb 0000644 0000041 0000041 00000002321 13503210114 022551 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class GeoportailLu < Base
def coordinates
geomlonlat['coordinates'].reverse if geolocalized?
end
def address
full_address
end
def city
try_to_extract 'locality', detailled_address
end
def state
'Luxembourg'
end
def state_code
'LU'
end
def postal_code
try_to_extract 'zip', detailled_address
end
def street_address
[street_number, street].compact.join(' ')
end
def street_number
try_to_extract 'postnumber', detailled_address
end
def street
try_to_extract 'street', detailled_address
end
def full_address
data['address']
end
def geomlonlat
data['geomlonlat']
end
def detailled_address
data['AddressDetails']
end
alias_method :country, :state
alias_method :province, :state
alias_method :country_code, :state_code
alias_method :province_code, :state_code
private
def geolocalized?
!!try_to_extract('coordinates', geomlonlat)
end
def try_to_extract(key, hash)
if hash.is_a?(Hash) and hash.include?(key)
hash[key]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/baidu_ip.rb 0000644 0000041 0000041 00000001453 13503210114 021465 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class BaiduIp < Base
def coordinates
[point['y'].to_f, point['x'].to_f]
end
def address
@data['address']
end
def state
province
end
def province
address_detail['province']
end
def city
address_detail['city']
end
def district
address_detail['district']
end
def street
address_detail['street']
end
def street_number
address_detail['street_number']
end
def state_code
""
end
def postal_code
""
end
def country
"China"
end
def country_code
"CN"
end
private
def address_detail
@data['address_detail']
end
def point
@data['point']
end
end
end
geocoder-1.5.1/lib/geocoder/results/yandex.rb 0000644 0000041 0000041 00000007375 13503210114 021212 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Yandex < Base
def coordinates
@data['GeoObject']['Point']['pos'].split(' ').reverse.map(&:to_f)
end
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
end
def country
if address_details
address_details['CountryName']
else
""
end
end
def country_code
if address_details
address_details['CountryNameCode']
else
""
end
end
def state
if address_details and address_details['AdministrativeArea']
address_details['AdministrativeArea']['AdministrativeAreaName']
else
""
end
end
def sub_state
if !state.empty? and address_details and address_details['AdministrativeArea']['SubAdministrativeArea']
address_details['AdministrativeArea']['SubAdministrativeArea']['SubAdministrativeAreaName']
else
""
end
end
def state_code
""
end
def postal_code
""
end
def premise_name
address_details['Locality']['Premise']['PremiseName']
end
def street
thoroughfare_data && thoroughfare_data['ThoroughfareName']
end
def street_number
thoroughfare_data && thoroughfare_data['Premise'] && thoroughfare_data['Premise']['PremiseNumber']
end
def kind
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['kind']
end
def precision
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision']
end
def viewport
envelope = @data['GeoObject']['boundedBy']['Envelope'] || fail
east, north = envelope['upperCorner'].split(' ').map(&:to_f)
west, south = envelope['lowerCorner'].split(' ').map(&:to_f)
[south, west, north, east]
end
private # ----------------------------------------------------------------
def thoroughfare_data
locality_data && locality_data['Thoroughfare']
end
def locality_data
dependent_locality && subadmin_locality && admin_locality
end
def admin_locality
address_details && address_details['AdministrativeArea'] &&
address_details['AdministrativeArea']['Locality']
end
def subadmin_locality
address_details && address_details['AdministrativeArea'] &&
address_details['AdministrativeArea']['SubAdministrativeArea'] &&
address_details['AdministrativeArea']['SubAdministrativeArea']['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']
end
def address_details
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['AddressDetails']['Country']
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
""
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/tencent.rb 0000644 0000041 0000041 00000003331 13503210114 021346 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Tencent < Base
def coordinates
['lat', 'lng'].map{ |i| @data['location'][i] }
end
def address
"#{province}#{city}#{district}#{street}#{street_number}"
#@data['title'] or @data['address']
end
# NOTE: The Tencent reverse geocoding API has the field named
# 'address_component' compared to 'address_components' in the
# regular geocoding API.
def province
@data['address_components'] and (@data['address_components']['province']) or
(@data['address_component'] and @data['address_component']['province']) or
""
end
alias_method :state, :province
def city
@data['address_components'] and (@data['address_components']['city']) or
(@data['address_component'] and @data['address_component']['city']) or
""
end
def district
@data['address_components'] and (@data['address_components']['district']) or
(@data['address_component'] and @data['address_component']['district']) or
""
end
def street
@data['address_components'] and (@data['address_components']['street']) or
(@data['address_component'] and @data['address_component']['street']) or
""
end
def street_number
@data['address_components'] and (@data['address_components']['street_number']) or
(@data['address_component'] and @data['address_component']['street_number']) or
""
end
def address_components
@data['address_components'] or @data['address_component']
end
def state_code
""
end
def postal_code
""
end
def country
"China"
end
def country_code
"CN"
end
end
end geocoder-1.5.1/lib/geocoder/results/postcodes_io.rb 0000644 0000041 0000041 00000001113 13503210114 022374 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class PostcodesIo < Base
def coordinates
[@data['latitude'].to_f, @data['longitude'].to_f]
end
def quality
@data['quality']
end
def postal_code
@data['postcode']
end
alias address postal_code
def city
@data['admin_ward']
end
def county
@data['admin_county']
end
alias state county
def state_code
@data['codes']['admin_county']
end
def country
'United Kingdom'
end
def country_code
'UK'
end
end
end
geocoder-1.5.1/lib/geocoder/results/telize.rb 0000644 0000041 0000041 00000001142 13503210114 021200 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Telize < Base
def city
@data['city']
end
def state
@data['region']
end
def state_code
@data['region_code']
end
def country
@data['country']
end
def country_code
@data['country_code']
end
def postal_code
@data['postal_code']
end
def self.response_attributes
%w[timezone isp dma_code area_code ip asn continent_code country_code3]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/ban_data_gouv_fr.rb 0000644 0000041 0000041 00000014747 13503210114 023203 0 ustar www-data www-data # encoding: utf-8
require 'geocoder/results/base'
module Geocoder::Result
class BanDataGouvFr < Base
#### BASE METHODS ####
def self.response_attributes
%w[limit attribution version licence type features]
end
response_attributes.each do |a|
unless method_defined?(a)
define_method a do
@data[a]
end
end
end
#### BEST RESULT ####
def result
features[0] if features.any?
end
#### GEOMETRY ####
def geometry
result['geometry'] if result
end
def precision
geometry['type'] if geometry
end
def coordinates
coords = geometry["coordinates"]
return [coords[1].to_f, coords[0].to_f]
end
#### PROPERTIES ####
# List of raw attrbutes returned by BAN data gouv fr API:
#
# :id => [string] UUID of the result, said to be not stable
# atm, based on IGN reference (Institut national de
# l'information géographique et forestière)
#
# :type => [string] result type (housenumber, street, city,
# town, village, locality)
#
# :score => [float] value between 0 and 1 giving result's
# relevancy
#
# :housenumber => [string] street number and extra information
# (bis, ter, A, B)
#
# :street => [string] street name
#
# :name => [string] housenumber and street name
#
# :postcode => [string] city post code (used for mails by La Poste,
# beware many cities got severeal postcodes)
#
# :citycode => [string] city code (INSEE reference,
# consider it as a french institutional UUID)
#
# :city => [string] city name
#
# :context => [string] department code, department name and
# region code
#
# :label => [string] full address without state, country name
# and country code
#
# CITIES ONLY PROPERTIES
#
# :adm_weight => [string] administrative weight (importance) of
# the city
#
# :population => [float] number of inhabitants with a 1000 factor
#
# For up to date doc (in french only) : https://adresse.data.gouv.fr/api/
#
def properties
result['properties'] if result
end
# List of usable Geocoder results' methods
#
# score => [float] result relevance 0 to 1
#
# location_id => [string] location's IGN UUID
#
# result_type => [string] housenumber / street / city
# / town / village / locality
#
# international_address => [string] full address with country code
#
# national_address => [string] full address with country code
#
# street_address => [string] housenumber + extra inf
# + street name
#
# street_number => [string] housenumber + extra inf
# (bis, ter, etc)
#
# street_name => [string] street's name
#
# city_name => [string] city's name
#
# city_code => [string] city's INSEE UUID
#
# postal_code => [string] city's postal code (used for mails)
#
# context => [string] city's department code, department
# name and region name
#
# demartment_name => [string] city's department name
#
# department_code => [string] city's department INSEE UUID
#
# region_name => [string] city's region name
#
# population => [string] city's inhabitants count
#
# administrative_weight => [integer] city's importance on a scale
# from 6 (capital city) to 1 (regular village)
#
def score
properties['score']
end
def location_id
properties['id']
end
# Types
#
# housenumber
# street
# city
# town
# village
# locality
#
def result_type
properties['type']
end
def international_address
"#{national_address}, #{country}"
end
def national_address
properties['label']
end
def street_address
properties['name']
end
def street_number
properties['housenumber']
end
def street_name
properties['street']
end
def city_name
properties['city']
end
def city_code
properties['citycode']
end
def postal_code
properties['postcode']
end
def context
properties['context'].split(/,/).map(&:strip)
end
def department_code
context[0] if context.length > 0
end
# Monkey logic to handle fact Paris is both a city and a department
# in Île-de-France region
def department_name
if context.length > 1
if context[1] == "Île-de-France"
"Paris"
else
context[1]
end
end
end
def region_name
if context.length == 2 && context[1] == "Île-de-France"
context[1]
elsif context.length > 2
context[2]
end
end
def country
"France"
end
# Country code types
# FR : France
# GF : Guyane Française
# RE : Réunion
# NC : Nouvelle-Calédonie
# GP : Guadeloupe
# MQ : Martinique
# MU : Maurice
# PF : Polynésie française
#
# Will need refacto to handle different country codes, but BAN API
# is currently mainly designed for geocode FR country code addresses
def country_code
"FR"
end
#### ALIAS METHODS ####
alias_method :address, :international_address
alias_method :street, :street_name
alias_method :city, :city_name
alias_method :state, :region_name
alias_method :state_code, :state
#### CITIES' METHODS ####
def population
(properties['population'].to_f * 1000).to_i if city?(result_type)
end
def administrative_weight
properties['adm_weight'].to_i if city?(result_type)
end
private
def city?(result_type)
%w(village town city).include?(result_type)
end
end
end
geocoder-1.5.1/lib/geocoder/results/baidu.rb 0000644 0000041 0000041 00000002727 13503210114 021002 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Baidu < Base
def coordinates
['lat', 'lng'].map{ |i| @data['location'][i] }
end
def address
@data['formatted_address']
end
def province
@data['addressComponent'] and @data['addressComponent']['province'] or ""
end
alias_method :state, :province
def city
@data['addressComponent'] and @data['addressComponent']['city'] or ""
end
def district
@data['addressComponent'] and @data['addressComponent']['district'] or ""
end
def street
@data['addressComponent'] and @data['addressComponent']['street'] or ""
end
def street_number
@data['addressComponent'] and @data['addressComponent']['street_number']
end
def formatted_address
@data['formatted_address'] or ""
end
alias_method :address, :formatted_address
def address_components
@data['addressComponent']
end
def state_code
""
end
def postal_code
""
end
def country
"China"
end
def country_code
"CN"
end
##
# Get address components of a given type. Valid types are defined in
# Baidu's Geocoding API documentation and include (among others):
#
# :business
# :cityCode
#
def self.response_attributes
%w[business cityCode]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/ipapi_com.rb 0000644 0000041 0000041 00000001343 13503210114 021647 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class IpapiCom < Base
def coordinates
[lat, lon]
end
def address
"#{city}, #{state_code} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
end
def state
region_name
end
def state_code
region
end
def postal_code
zip
end
def country_code
@data['countryCode']
end
def region_name
@data['regionName']
end
def self.response_attributes
%w[country region city zip timezone isp org as reverse query status message mobile proxy lat lon]
end
response_attributes.each do |attribute|
define_method attribute do
@data[attribute]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/ipinfo_io.rb 0000644 0000041 0000041 00000001355 13503210114 021665 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class IpinfoIo < Base
def address(format = :full)
"#{city} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
end
def coordinates
@data['loc'].to_s.split(",").map(&:to_f)
end
def city
@data['city']
end
def state
@data['region']
end
def country
@data['country']
end
def postal_code
@data['postal']
end
def country_code
@data.fetch('country', '')
end
def state_code
@data.fetch('region_code', '')
end
def self.response_attributes
%w(ip region postal)
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/base.rb 0000644 0000041 0000041 00000003074 13503210114 020624 0 ustar www-data www-data module Geocoder
module Result
class Base
# data (hash) fetched from geocoding service
attr_accessor :data
# true if result came from cache, false if from request to geocoding
# service; nil if cache is not configured
attr_accessor :cache_hit
##
# Takes a hash of data from a parsed geocoding service response.
#
def initialize(data)
@data = data
@cache_hit = nil
end
##
# A string in the given format.
#
# This default implementation dumbly follows the United States address
# format and will return incorrect results for most countries. Some APIs
# return properly formatted addresses and those should be funneled
# through this method.
#
def address(format = :full)
if state_code.to_s != ""
s = ", #{state_code}"
elsif state.to_s != ""
s = ", #{state}"
else
s = ""
end
"#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, '')
end
##
# A two-element array: [lat, lon].
#
def coordinates
[@data['latitude'].to_f, @data['longitude'].to_f]
end
def latitude
coordinates[0]
end
def longitude
coordinates[1]
end
def state
fail
end
def province
state
end
def state_code
fail
end
def province_code
state_code
end
def country
fail
end
def country_code
fail
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/location_iq.rb 0000644 0000041 0000041 00000000146 13503210114 022210 0 ustar www-data www-data require 'geocoder/results/nominatim'
module Geocoder::Result
class LocationIq < Nominatim
end
end geocoder-1.5.1/lib/geocoder/results/maxmind.rb 0000644 0000041 0000041 00000005214 13503210114 021345 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Maxmind < Base
##
# Hash mapping service names to names of returned fields.
#
def self.field_names
{
:country => [
:country_code,
:error
],
:city => [
:country_code,
:region_code,
:city_name,
:latitude,
:longitude,
:error
],
:city_isp_org => [
:country_code,
:region_code,
:city_name,
:postal_code,
:latitude,
:longitude,
:metro_code,
:area_code,
:isp_name,
:organization_name,
:error
],
:omni => [
:country_code,
:country_name,
:region_code,
:region_name,
:city_name,
:latitude,
:longitude,
:metro_code,
:area_code,
:time_zone,
:continent_code,
:postal_code,
:isp_name,
:organization_name,
:domain,
:as_number,
:netspeed,
:user_type,
:accuracy_radius,
:country_confidence_factor,
:city_confidence_factor,
:region_confidence_factor,
:postal_confidence_factor,
:error
]
}
end
##
# Name of the MaxMind service being used.
#
def service_name
# it would be much better to infer this from the length of the @data
# array, but MaxMind seems to send inconsistent and wide-ranging response
# lengths (see https://github.com/alexreisner/geocoder/issues/396)
Geocoder.config.maxmind[:service]
end
def field_names
self.class.field_names[service_name]
end
def data_hash
@data_hash ||= Hash[*field_names.zip(@data).flatten]
end
def coordinates
[data_hash[:latitude].to_f, data_hash[:longitude].to_f]
end
def city
data_hash[:city_name]
end
def state # not given by MaxMind
data_hash[:region_name] || data_hash[:region_code]
end
def state_code
data_hash[:region_code]
end
def country #not given by MaxMind
data_hash[:country_name] || data_hash[:country_code]
end
def country_code
data_hash[:country_code]
end
def postal_code
data_hash[:postal_code]
end
def method_missing(method, *args, &block)
if field_names.include?(method)
data_hash[method]
else
super
end
end
def respond_to?(method)
if field_names.include?(method)
true
else
super
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/latlon.rb 0000644 0000041 0000041 00000002273 13503210114 021203 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Latlon < Base
def city
address_components["city"]
end
def coordinates
[@data['lat'].to_f, @data['lon'].to_f]
end
def country
"United States" # LatLon.io only supports the US
end
def country_code
"US" # LatLon.io only supports the US
end
def formatted_address(format = :full)
address_components["address"]
end
alias_method :address, :formatted_address
def number
address_components["number"]
end
def prefix
address_components["prefix"]
end
def state
address_components["state"]
end
alias_method :state_code, :state
def street
[street_name, street_type].compact.join(' ')
end
def street_name
address_components["street_name"]
end
def street_type
address_components["street_type"]
end
def suffix
address_components["suffix"]
end
def unit
address_components["unit"]
end
def zip
address_components["zip"]
end
alias_method :postal_code, :zip
private
def address_components
@data["address"] || {}
end
end
end
geocoder-1.5.1/lib/geocoder/results/test.rb 0000644 0000041 0000041 00000001426 13503210114 020670 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder
module Result
class Test < Base
def self.add_result_attribute(attr)
begin
remove_method(attr) if method_defined?(attr)
rescue NameError # method defined on superclass
end
define_method(attr) do
@data[attr.to_s] || @data[attr.to_sym]
end
end
%w[coordinates neighborhood city state state_code sub_state
sub_state_code province province_code postal_code country
country_code address street_address street_number route geometry].each do |attr|
add_result_attribute(attr)
end
def initialize(data)
data.each_key do |attr|
Test.add_result_attribute(attr)
end
super
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/mapbox.rb 0000644 0000041 0000041 00000001650 13503210114 021176 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Mapbox < Base
def coordinates
data['geometry']['coordinates'].reverse.map(&:to_f)
end
def place_name
data['text']
end
def street
data['properties']['address']
end
def city
context_part('place')
end
def state
context_part('region')
end
alias_method :state_code, :state
def postal_code
context_part('postcode')
end
def country
context_part('country')
end
alias_method :country_code, :country
def neighborhood
context_part('neighborhood')
end
def address
[place_name, street, city, state, postal_code, country].compact.join(', ')
end
private
def context_part(name)
context.map { |c| c['text'] if c['id'] =~ Regexp.new(name) }.compact.first
end
def context
Array(data['context'])
end
end
end
geocoder-1.5.1/lib/geocoder/results/esri.rb 0000644 0000041 0000041 00000002770 13503210114 020656 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Esri < Base
def address
address_key = reverse_geocode? ? 'Address' : 'Match_addr'
attributes[address_key]
end
def city
if !reverse_geocode? && is_city?
place_name
else
attributes['City']
end
end
def state_code
attributes['Region']
end
alias_method :state, :state_code
def country
country_key = reverse_geocode? ? "CountryCode" : "Country"
attributes[country_key]
end
alias_method :country_code, :country
def postal_code
attributes['Postal']
end
def place_name
place_name_key = reverse_geocode? ? "Address" : "PlaceName"
attributes[place_name_key]
end
def place_type
reverse_geocode? ? "Address" : attributes['Type']
end
def coordinates
[geometry["y"], geometry["x"]]
end
def viewport
north = attributes['Ymax']
south = attributes['Ymin']
east = attributes['Xmax']
west = attributes['Xmin']
[south, west, north, east]
end
private
def attributes
reverse_geocode? ? @data['address'] : @data['locations'].first['feature']['attributes']
end
def geometry
reverse_geocode? ? @data["location"] : @data['locations'].first['feature']["geometry"]
end
def reverse_geocode?
@data['locations'].nil?
end
def is_city?
['City', 'State Capital', 'National Capital'].include?(place_type)
end
end
end
geocoder-1.5.1/lib/geocoder/results/google.rb 0000644 0000041 0000041 00000006251 13503210114 021166 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Google < Base
def coordinates
['lat', 'lng'].map{ |i| geometry['location'][i] }
end
def address(format = :full)
formatted_address
end
def neighborhood
if neighborhood = address_components_of_type(:neighborhood).first
neighborhood['long_name']
end
end
def city
fields = [:locality, :sublocality,
:administrative_area_level_3,
:administrative_area_level_2]
fields.each do |f|
if entity = address_components_of_type(f).first
return entity['long_name']
end
end
return nil # no appropriate components found
end
def state
if state = address_components_of_type(:administrative_area_level_1).first
state['long_name']
end
end
def state_code
if state = address_components_of_type(:administrative_area_level_1).first
state['short_name']
end
end
def sub_state
if state = address_components_of_type(:administrative_area_level_2).first
state['long_name']
end
end
def sub_state_code
if state = address_components_of_type(:administrative_area_level_2).first
state['short_name']
end
end
def country
if country = address_components_of_type(:country).first
country['long_name']
end
end
def country_code
if country = address_components_of_type(:country).first
country['short_name']
end
end
def postal_code
if postal = address_components_of_type(:postal_code).first
postal['long_name']
end
end
def route
if route = address_components_of_type(:route).first
route['long_name']
end
end
def street_number
if street_number = address_components_of_type(:street_number).first
street_number['long_name']
end
end
def street_address
[street_number, route].compact.join(' ')
end
def types
@data['types']
end
def formatted_address
@data['formatted_address']
end
def address_components
@data['address_components']
end
##
# Get address components of a given type. Valid types are defined in
# Google's Geocoding API documentation and include (among others):
#
# :street_number
# :locality
# :neighborhood
# :route
# :postal_code
#
def address_components_of_type(type)
address_components.select{ |c| c['types'].include?(type.to_s) }
end
def geometry
@data['geometry']
end
def precision
geometry['location_type'] if geometry
end
def partial_match
@data['partial_match']
end
def place_id
@data['place_id']
end
def viewport
viewport = geometry['viewport'] || fail
bounding_box_from viewport
end
def bounds
bounding_box_from geometry['bounds']
end
private
def bounding_box_from(box)
return nil unless box
south, west = %w(lat lng).map { |c| box['southwest'][c] }
north, east = %w(lat lng).map { |c| box['northeast'][c] }
[south, west, north, east]
end
end
end
geocoder-1.5.1/lib/geocoder/results/mapquest.rb 0000644 0000041 0000041 00000001453 13503210114 021550 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Mapquest < Base
def coordinates
%w[lat lng].map{ |l| @data["latLng"][l] }
end
def city
@data['adminArea5']
end
def street
@data['street']
end
def state
@data['adminArea3']
end
def county
@data['adminArea4']
end
alias_method :state_code, :state
#FIXME: these might not be right, unclear with MQ documentation
alias_method :province, :state
alias_method :province_code, :state
def postal_code
@data['postalCode'].to_s
end
def country
@data['adminArea1']
end
def country_code
country
end
def address
[street, city, state, postal_code, country].reject{|s| s.length == 0 }.join(", ")
end
end
end
geocoder-1.5.1/lib/geocoder/results/dstk.rb 0000644 0000041 0000041 00000000132 13503210114 020647 0 ustar www-data www-data require 'geocoder/results/google'
module Geocoder::Result
class Dstk < Google
end
end geocoder-1.5.1/lib/geocoder/results/google_places_search.rb 0000644 0000041 0000041 00000001151 13503210114 024034 0 ustar www-data www-data require "geocoder/results/google"
module Geocoder
module Result
class GooglePlacesSearch < Google
def types
@data["types"] || []
end
def rating
@data["rating"]
end
def photos
@data["photos"]
end
def city
""
end
def state
""
end
def state_code
""
end
def province
""
end
def province_code
""
end
def postal_code
""
end
def country
""
end
def country_code
""
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/geocodio.rb 0000644 0000041 0000041 00000002706 13503210114 021503 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Geocodio < Base
def number
address_components["number"]
end
def street
address_components["street"]
end
def suffix
address_components["suffix"]
end
def street_address
[number, address_components["formatted_street"]].compact.join(' ')
end
def state
address_components["state"]
end
alias_method :state_code, :state
def zip
# Postal code is not returned for Canada geocode results
address_components["zip"] || ""
end
alias_method :postal_code, :zip
def country
# Geocodio supports US and Canada, however they don't return the full
# country name.
if country_code == "CA"
"Canada"
else
"United States"
end
end
def country_code
address_components['country']
end
def city
address_components["city"]
end
def postdirectional
address_components["postdirectional"]
end
def location
@data['location']
end
def coordinates
['lat', 'lng'].map{ |i| location[i].to_f } if location
end
def accuracy
@data['accuracy'].to_f if @data.key?('accuracy')
end
def formatted_address(format = :full)
@data['formatted_address']
end
alias_method :address, :formatted_address
private
def address_components
@data['address_components'] || {}
end
end
end
geocoder-1.5.1/lib/geocoder/results/ipstack.rb 0000644 0000041 0000041 00000002443 13503210114 021347 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Ipstack < Base
def address(format = :full)
s = region_code.empty? ? "" : ", #{region_code}"
"#{city}#{s} #{zip}, #{country_name}".sub(/^[ ,]*/, "")
end
def state
@data['region_name']
end
def state_code
@data['region_code']
end
def country
@data['country_name']
end
def postal_code
@data['zip'] || @data['zipcode'] || @data['zip_code']
end
def self.response_attributes
[
['ip', ''],
['hostname', ''],
['continent_code', ''],
['continent_name', ''],
['country_code', ''],
['country_name', ''],
['region_code', ''],
['region_name', ''],
['city', ''],
['zip', ''],
['latitude', 0],
['longitude', 0],
['location', {}],
['time_zone', {}],
['currency', {}],
['connection', {}],
['security', {}],
]
end
response_attributes.each do |attr, default|
define_method attr do
@data[attr] || default
end
end
def metro_code
Geocoder.log(:warn, "Ipstack does not implement `metro_code` in api results. Please discontinue use.")
0 # no longer implemented by ipstack
end
end
end
geocoder-1.5.1/lib/geocoder/results/google_places_details.rb 0000644 0000041 0000041 00000001131 13503210114 024212 0 ustar www-data www-data require "geocoder/results/google"
module Geocoder
module Result
class GooglePlacesDetails < Google
def place_id
@data["place_id"]
end
def types
@data["types"] || []
end
def reviews
@data["reviews"] || []
end
def rating
@data["rating"]
end
def rating_count
@data["user_ratings_total"]
end
def phone_number
@data["international_phone_number"]
end
def website
@data["website"]
end
def photos
@data["photos"]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/pickpoint.rb 0000644 0000041 0000041 00000000145 13503210114 021706 0 ustar www-data www-data require 'geocoder/results/nominatim'
module Geocoder::Result
class Pickpoint < Nominatim
end
end geocoder-1.5.1/lib/geocoder/results/nominatim.rb 0000644 0000041 0000041 00000003676 13503210114 021715 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Nominatim < Base
def poi
return @data['address'][place_type] if @data['address'].key?(place_type)
return nil
end
def house_number
@data['address']['house_number']
end
def address
@data['display_name']
end
def street
%w[road pedestrian highway].each do |key|
return @data['address'][key] if @data['address'].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)
end
return nil
end
def village
@data['address']['village']
end
def town
@data['address']['town']
end
def state
@data['address']['state']
end
alias_method :state_code, :state
def postal_code
@data['address']['postcode']
end
def county
@data['address']['county']
end
def country
@data['address']['country']
end
def country_code
@data['address']['country_code']
end
def suburb
@data['address']['suburb']
end
def city_district
@data['address']['city_district']
end
def state_district
@data['address']['state_district']
end
def neighbourhood
@data['address']['neighbourhood']
end
def coordinates
[@data['lat'].to_f, @data['lon'].to_f]
end
def place_class
@data['class']
end
def place_type
@data['type']
end
def viewport
south, north, west, east = @data['boundingbox'].map(&:to_f)
[south, west, north, east]
end
def self.response_attributes
%w[place_id osm_type osm_id boundingbox license
polygonpoints display_name class type stadium]
end
response_attributes.each do |a|
unless method_defined?(a)
define_method a do
@data[a]
end
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/freegeoip.rb 0000644 0000041 0000041 00000001112 13503210114 021646 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Freegeoip < Base
def city
@data['city']
end
def state
@data['region_name']
end
def state_code
@data['region_code']
end
def country
@data['country_name']
end
def country_code
@data['country_code']
end
def postal_code
@data['zipcode'] || @data['zip_code']
end
def self.response_attributes
%w[metro_code ip]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/pelias.rb 0000644 0000041 0000041 00000001575 13503210114 021173 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Pelias < Base
def address(format = :full)
properties['label']
end
def city
locality
end
def coordinates
geometry['coordinates'].reverse
end
def country_code
properties['country_a']
end
def postal_code
properties['postalcode'].to_s
end
def province
state
end
def state
properties['region']
end
def state_code
properties['region_a']
end
def self.response_attributes
%w[county confidence country gid id layer localadmin locality neighborhood]
end
response_attributes.each do |a|
define_method a do
properties[a]
end
end
private
def geometry
@data.fetch('geometry', {})
end
def properties
@data.fetch('properties', {})
end
end
end
geocoder-1.5.1/lib/geocoder/results/db_ip_com.rb 0000644 0000041 0000041 00000001674 13503210114 021631 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class DbIpCom < Base
def coordinates
['latitude', 'longitude'].map{ |coordinate_name| @data[coordinate_name] }
end
def city
@data['city']
end
def district
@data['district']
end
def state_code
@data['stateProv']
end
alias_method :state, :state_code
def zip_code
@data['zipCode']
end
alias_method :postal_code, :zip_code
def country_name
@data['countryName']
end
alias_method :country, :country_name
def country_code
@data['countryCode']
end
def continent_name
@data['continentName']
end
alias_method :continent, :continent_name
def continent_code
@data['continentCode']
end
def time_zone
@data['timeZone']
end
def gmt_offset
@data['gmtOffset']
end
def currency_code
@data['currencyCode']
end
end
end
geocoder-1.5.1/lib/geocoder/results/google_premier.rb 0000644 0000041 0000041 00000000143 13503210114 022703 0 ustar www-data www-data require 'geocoder/results/google'
module Geocoder::Result
class GooglePremier < Google
end
end geocoder-1.5.1/lib/geocoder/results/geoip2.rb 0000644 0000041 0000041 00000002630 13503210114 021074 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder
module Result
class Geoip2 < Base
def coordinates
%w[latitude longitude].map do |l|
data.fetch('location', {}).fetch(l, 0.0)
end
end
def city
fetch_name(
data.fetch('city', {}).fetch('names', {})
)
end
def state
fetch_name(
data.fetch('subdivisions', []).fetch(0, {}).fetch('names', {})
)
end
def state_code
data.fetch('subdivisions', []).fetch(0, {}).fetch('iso_code', '')
end
def country
fetch_name(
data.fetch('country', {}).fetch('names', {})
)
end
def country_code
data.fetch('country', {}).fetch('iso_code', '')
end
def postal_code
data.fetch('postal', {}).fetch('code', '')
end
def self.response_attributes
%w[ip]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
def language=(l)
@language = l.to_s
end
def language
@language ||= default_language
end
def data
@data.to_hash
end
private
def default_language
@default_language = Geocoder.config[:language].to_s
end
def fetch_name(names)
names[language] || names[default_language] || ''
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/geocoder_ca.rb 0000644 0000041 0000041 00000002640 13503210114 022142 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class GeocoderCa < Base
def coordinates
[@data['latt'].to_f, @data['longt'].to_f]
end
def address(format = :full)
"#{street_address}, #{city}, #{state} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
end
def street_address
"#{@data['stnumber']} #{@data['staddress']}"
end
def city
@data['city'] or (@data['standard'] and @data['standard']['city']) or ""
end
def state
@data['prov'] or (@data['standard'] and @data['standard']['prov']) or ""
end
alias_method :state_code, :state
def postal_code
@data['postal'] or (@data['standard'] and @data['standard']['postal']) or ""
end
def country
country_code == 'CA' ? 'Canada' : 'United States'
end
def country_code
return nil if state.nil? || state == ""
canadian_province_abbreviations.include?(state) ? "CA" : "US"
end
def self.response_attributes
%w[latt longt inlatt inlongt distance stnumber staddress prov
NearRoad NearRoadDistance betweenRoad1 betweenRoad2
intersection major_intersection]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
private # ----------------------------------------------------------------
def canadian_province_abbreviations
%w[ON QC NS NB MB BC PE SK AB NL]
end
end
end
geocoder-1.5.1/lib/geocoder/results/ip2location.rb 0000644 0000041 0000041 00000001121 13503210114 022124 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Ip2location < Base
def address(format = :full)
"#{city_name} #{zip_code}, #{country_name}".sub(/^[ ,]*/, '')
end
def self.response_attributes
%w[country_code country_name region_name city_name latitude longitude
zip_code time_zone isp domain net_speed idd_code area_code usage_type
weather_station_code weather_station_name mcc mnc mobile_brand elevation]
end
response_attributes.each do |attr|
define_method attr do
@data[attr] || ""
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/geocoder_us.rb 0000644 0000041 0000041 00000001105 13503210114 022201 0 ustar www-data www-data 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
geocoder-1.5.1/lib/geocoder/results/pointpin.rb 0000644 0000041 0000041 00000001324 13503210114 021546 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Pointpin < Base
def address
[ city_name, state, postal_code, country ].select{ |i| i.to_s != "" }.join(", ")
end
def city
@data['city_name']
end
def state
@data['region_name']
end
def state_code
@data['region_code']
end
def country
@data['country_name']
end
def postal_code
@data['postcode']
end
def self.response_attributes
%w[continent_code ip country_code country_name region_name city_name postcode latitude longitude time_zone languages]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/maxmind_local.rb 0000644 0000041 0000041 00000001217 13503210114 022516 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class MaxmindLocal < Base
def coordinates
[@data[:latitude], @data[:longitude]]
end
def city
@data[:city_name]
end
def state
@data[:region_name]
end
def state_code
"" # Not available in Maxmind's database
end
def country
@data[:country_name]
end
def country_code
@data[:country_code2]
end
def postal_code
@data[:postal_code]
end
def self.response_attributes
%w[ip]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/bing.rb 0000644 0000041 0000041 00000001531 13503210114 020625 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class Bing < Base
def address(format = :full)
@data['address']['formattedAddress']
end
def city
@data['address']['locality']
end
def state_code
@data['address']['adminDistrict']
end
alias_method :state, :state_code
def country
@data['address']['countryRegion']
end
alias_method :country_code, :country
def postal_code
@data['address']['postalCode'].to_s
end
def coordinates
@data['point']['coordinates']
end
def address_data
@data['address']
end
def viewport
@data['bbox']
end
def self.response_attributes
%w[bbox name confidence entityType]
end
response_attributes.each do |a|
define_method a do
@data[a]
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/smarty_streets.rb 0000644 0000041 0000041 00000005370 13503210114 023003 0 ustar www-data www-data require 'geocoder/lookups/base'
module Geocoder::Result
class SmartyStreets < Base
def coordinates
result = %w(latitude longitude).map do |i|
zipcode_endpoint? ? zipcodes.first[i] : metadata[i]
end
if result.compact.empty?
nil
else
result
end
end
def address
parts =
if international_endpoint?
(1..12).map { |i| @data["address#{i}"] }
else
[
delivery_line_1,
delivery_line_2,
last_line
]
end
parts.select{ |i| i.to_s != "" }.join(" ")
end
def state
if international_endpoint?
components['administrative_area']
elsif zipcode_endpoint?
city_states.first['state']
else
components['state_abbreviation']
end
end
def state_code
if international_endpoint?
components['administrative_area']
elsif zipcode_endpoint?
city_states.first['state_abbreviation']
else
components['state_abbreviation']
end
end
def country
international_endpoint? ?
components['country_iso_3'] :
"United States"
end
def country_code
international_endpoint? ?
components['country_iso_3'] :
"US"
end
## Extra methods not in base.rb ------------------------
def street
international_endpoint? ?
components['thoroughfare_name'] :
components['street_name']
end
def city
if international_endpoint?
components['locality']
elsif zipcode_endpoint?
city_states.first['city']
else
components['city_name']
end
end
def zipcode
if international_endpoint?
components['postal_code']
elsif zipcode_endpoint?
zipcodes.first['zipcode']
else
components['zipcode']
end
end
alias_method :postal_code, :zipcode
def zip4
components['plus4_code']
end
alias_method :postal_code_extended, :zip4
def fips
zipcode_endpoint? ?
zipcodes.first['county_fips'] :
metadata['county_fips']
end
def zipcode_endpoint?
zipcodes.any?
end
def international_endpoint?
!@data['address1'].nil?
end
[
:delivery_line_1,
:delivery_line_2,
:last_line,
:delivery_point_barcode,
:addressee
].each do |m|
define_method(m) do
@data[m.to_s] || ''
end
end
[
:components,
:metadata,
:analysis
].each do |m|
define_method(m) do
@data[m.to_s] || {}
end
end
[
:city_states,
:zipcodes
].each do |m|
define_method(m) do
@data[m.to_s] || []
end
end
end
end
geocoder-1.5.1/lib/geocoder/results/postcode_anywhere_uk.rb 0000644 0000041 0000041 00000001523 13503210114 024130 0 ustar www-data www-data require 'geocoder/results/base'
module Geocoder::Result
class PostcodeAnywhereUk < Base
def coordinates
[@data['Latitude'].to_f, @data['Longitude'].to_f]
end
def blank_result
''
end
alias_method :state, :blank_result
alias_method :state_code, :blank_result
alias_method :postal_code, :blank_result
def address
@data['Location']
end
def city
# is this too big a jump to assume that the API always
# returns a City, County as the last elements?
city = @data['Location'].split(',')[-2] || blank_result
city.strip
end
def os_grid
@data['OsGrid']
end
# This is a UK only API; all results are UK specific and
# so ommitted from API response.
def country
'United Kingdom'
end
def country_code
'UK'
end
end
end
geocoder-1.5.1/lib/geocoder/results/maxmind_geoip2.rb 0000644 0000041 0000041 00000000633 13503210114 022612 0 ustar www-data www-data require 'geocoder/results/geoip2'
module Geocoder::Result
class MaxmindGeoip2 < Geoip2
# MindmindGeoip2 has the same results as Geoip2 because both are from MaxMind's GeoIP2 Precision Services
# See http://dev.maxmind.com/geoip/geoip2/web-services/ The difference being that Maxmind calls the service
# directly while GeoIP2 uses Hive::GeoIP2. See https://github.com/desuwa/hive_geoip2
end
end
geocoder-1.5.1/lib/geocoder/esri_token.rb 0000644 0000041 0000041 00000001717 13503210114 020355 0 ustar www-data www-data module Geocoder
class EsriToken
attr_accessor :value, :expires_at
def initialize(value, expires_at)
@value = value
@expires_at = expires_at
end
def to_s
@value
end
def active?
@expires_at > Time.now
end
def self.generate_token(client_id, client_secret, expires=1440)
# creates a new token that will expire in 1 day by default
getToken = Net::HTTP.post_form URI('https://www.arcgis.com/sharing/rest/oauth2/token'),
f: 'json',
client_id: client_id,
client_secret: client_secret,
grant_type: 'client_credentials',
expiration: expires # (minutes) max: 20160, default: 1 day
response = JSON.parse(getToken.body)
if response['error']
Geocoder.log(:warn, response['error'])
else
token_value = response['access_token']
expires_at = Time.now + (expires * 60)
new(token_value, expires_at)
end
end
end
end
geocoder-1.5.1/lib/geocoder/cli.rb 0000644 0000041 0000041 00000007562 13503210114 016766 0 ustar www-data www-data require 'geocoder'
require 'optparse'
module Geocoder
class Cli
def self.run(args, out = STDOUT)
show_url = false
show_json = false
# remove arguments that are probably coordinates so they are not
# processed as arguments (eg: -31.96047031,115.84274631)
coords = args.select{ |i| i.match(/^-\d/) }
args -= coords
OptionParser.new{ |opts|
opts.banner = "Usage:\n geocode [options] "
opts.separator "\nOptions: "
opts.on("-k ", "--key ",
"Key for geocoding API (usually optional). Enclose multi-part keys in quotes and separate parts by spaces") do |key|
if (key_parts = key.split(/\s+/)).size > 1
Geocoder.configure(:api_key => key_parts)
else
Geocoder.configure(:api_key => key)
end
end
opts.on("-l ", "--language ",
"Language of output (see API docs for valid choices)") do |language|
Geocoder.configure(:language => language)
end
opts.on("-p ", "--proxy ",
"HTTP proxy server to use (user:pass@host:port)") do |proxy|
Geocoder.configure(:http_proxy => proxy)
end
opts.on("-s ", Geocoder::Lookup.all_services_except_test, "--service ",
"Geocoding service: #{Geocoder::Lookup.all_services_except_test * ', '}") do |service|
Geocoder.configure(:lookup => service.to_sym)
Geocoder.configure(:ip_lookup => service.to_sym)
end
opts.on("-t ", "--timeout ",
"Maximum number of seconds to wait for API response") do |timeout|
Geocoder.configure(:timeout => timeout.to_i)
end
opts.on("-j", "--json", "Print API's raw JSON response") do
show_json = true
end
opts.on("-u", "--url", "Print URL for API query instead of result") do
show_url = true
end
opts.on_tail("-v", "--version", "Print version number") do
require "geocoder/version"
out << "Geocoder #{Geocoder::VERSION}\n"
exit
end
opts.on_tail("-h", "--help", "Print this help") do
out << "Look up geographic information about a location.\n\n"
out << opts
out << "\nCreated and maintained by Alex Reisner, available under the MIT License.\n"
out << "Report bugs and contribute at http://github.com/alexreisner/geocoder\n"
exit
end
}.parse!(args)
# concatenate args with coords that might have been removed
# before option processing
query = (args + coords).join(" ")
if query == ""
out << "Please specify a location (run `geocode -h` for more info).\n"
exit 1
end
if show_url and show_json
out << "You can only specify one of -j and -u.\n"
exit 2
end
if show_url
q = Geocoder::Query.new(query)
out << q.url + "\n"
exit 0
end
if show_json
q = Geocoder::Query.new(query)
out << q.lookup.send(:fetch_raw_data, q) + "\n"
exit 0
end
if (result = Geocoder.search(query).first)
nominatim = Geocoder::Lookup.get(:nominatim)
lines = [
["Latitude", result.latitude],
["Longitude", result.longitude],
["Full address", result.address],
["City", result.city],
["State/province", result.state],
["Postal code", result.postal_code],
["Country", result.country],
["Map", nominatim.map_link_url(result.coordinates)],
]
lines.each do |line|
out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
end
exit 0
else
out << "Location '#{query}' not found.\n"
exit 1
end
end
end
end
geocoder-1.5.1/lib/geocoder/kernel_logger.rb 0000644 0000041 0000041 00000001107 13503210114 021023 0 ustar www-data www-data module Geocoder
class KernelLogger
include Singleton
def add(level, message)
return unless log_message_at_level?(level)
case level
when ::Logger::DEBUG, ::Logger::INFO
puts message
when ::Logger::WARN
warn message
when ::Logger::ERROR
raise message
when ::Logger::FATAL
fail message
end
end
private # ----------------------------------------------------------------
def log_message_at_level?(level)
level >= Geocoder.config.kernel_logger_level
end
end
end
geocoder-1.5.1/lib/geocoder/logger.rb 0000644 0000041 0000041 00000002144 13503210114 017465 0 ustar www-data www-data require 'logger'
module Geocoder
def self.log(level, message)
Logger.instance.log(level, message)
end
class Logger
include Singleton
SEVERITY = {
debug: ::Logger::DEBUG,
info: ::Logger::INFO,
warn: ::Logger::WARN,
error: ::Logger::ERROR,
fatal: ::Logger::FATAL
}
def log(level, message)
unless valid_level?(level)
raise StandardError, "Geocoder tried to log a message with an invalid log level."
end
if current_logger.respond_to? :add
current_logger.add(SEVERITY[level], message)
else
raise Geocoder::ConfigurationError, "Please specify valid logger for Geocoder. " +
"Logger specified must be :kernel or must respond to `add(level, message)`."
end
nil
end
private # ----------------------------------------------------------------
def current_logger
logger = Geocoder.config[:logger]
if logger == :kernel
logger = Geocoder::KernelLogger.instance
end
logger
end
def valid_level?(level)
SEVERITY.keys.include?(level)
end
end
end
geocoder-1.5.1/lib/hash_recursive_merge.rb 0000644 0000041 0000041 00000004740 13503210114 020614 0 ustar www-data www-data #
# = 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
# 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
geocoder-1.5.1/lib/maxmind_database.rb 0000644 0000041 0000041 00000006550 13503210114 017705 0 ustar www-data www-data require 'csv'
require 'net/http'
module Geocoder
module MaxmindDatabase
extend self
def download(package, dir = "tmp")
filepath = File.expand_path(File.join(dir, archive_filename(package)))
open(filepath, 'wb') do |file|
uri = URI.parse(archive_url(package))
Net::HTTP.start(uri.host, uri.port) do |http|
http.request_get(uri.path) do |resp|
# TODO: show progress
resp.read_body do |segment|
file.write(segment)
end
end
end
end
end
def insert(package, dir = "tmp")
data_files(package, dir).each do |filepath,table|
print "Resetting table #{table}..."
ActiveRecord::Base.connection.execute("DELETE FROM #{table}")
puts "done"
insert_into_table(table, filepath)
end
end
def archive_filename(package)
p = archive_url_path(package)
s = !(pos = p.rindex('/')).nil? && pos + 1 || 0
p[s..-1]
end
private # -------------------------------------------------------------
def table_columns(table_name)
{
maxmind_geolite_city_blocks: %w[start_ip_num end_ip_num loc_id],
maxmind_geolite_city_location: %w[loc_id country region city postal_code latitude longitude metro_code area_code],
maxmind_geolite_country: %w[start_ip end_ip start_ip_num end_ip_num country_code country]
}[table_name.to_sym]
end
def insert_into_table(table, filepath)
start_time = Time.now
print "Loading data for table #{table}"
rows = []
columns = table_columns(table)
CSV.foreach(filepath, encoding: "ISO-8859-1") do |line|
# Some files have header rows.
# skip if starts with "Copyright" or "locId" or "startIpNum"
next if line.first.match(/[A-z]/)
rows << line.to_a
if rows.size == 10000
insert_rows(table, columns, rows)
rows = []
print "."
end
end
insert_rows(table, columns, rows) if rows.size > 0
puts "done (#{Time.now - start_time} seconds)"
end
def insert_rows(table, headers, rows)
value_strings = rows.map do |row|
"(" + row.map{ |col| sql_escaped_value(col) }.join(',') + ")"
end
q = "INSERT INTO #{table} (#{headers.join(',')}) " +
"VALUES #{value_strings.join(',')}"
ActiveRecord::Base.connection.execute(q)
end
def sql_escaped_value(value)
value.to_i.to_s == value ? value :
ActiveRecord::Base.connection.quote(value)
end
def data_files(package, dir = "tmp")
case package
when :geolite_city_csv
# use the last two in case multiple versions exist
files = Dir.glob(File.join(dir, "GeoLiteCity_*/*.csv"))[-2..-1].sort
Hash[*files.zip(["maxmind_geolite_city_blocks", "maxmind_geolite_city_location"]).flatten]
when :geolite_country_csv
{File.join(dir, "GeoIPCountryWhois.csv") => "maxmind_geolite_country"}
end
end
def archive_url(package)
base_url + archive_url_path(package)
end
def archive_url_path(package)
{
geolite_country_csv: "GeoIPCountryCSV.zip",
geolite_city_csv: "GeoLiteCity_CSV/GeoLiteCity-latest.zip",
geolite_asn_csv: "asnum/GeoIPASNum2.zip"
}[package]
end
def base_url
"http://geolite.maxmind.com/download/geoip/database/"
end
end
end
geocoder-1.5.1/lib/generators/ 0000755 0000041 0000041 00000000000 13503210114 016242 5 ustar www-data www-data geocoder-1.5.1/lib/generators/geocoder/ 0000755 0000041 0000041 00000000000 13503210114 020031 5 ustar www-data www-data geocoder-1.5.1/lib/generators/geocoder/maxmind/ 0000755 0000041 0000041 00000000000 13503210114 021466 5 ustar www-data www-data geocoder-1.5.1/lib/generators/geocoder/maxmind/geolite_city_generator.rb 0000644 0000041 0000041 00000001715 13503210114 026545 0 ustar www-data www-data require 'rails/generators/migration'
require 'generators/geocoder/migration_version'
module Geocoder
module Generators
module Maxmind
class GeoliteCityGenerator < Rails::Generators::Base
include Rails::Generators::Migration
include Generators::MigrationVersion
source_root File.expand_path('../templates', __FILE__)
def copy_migration_files
migration_template "migration/geolite_city.rb", "db/migrate/geocoder_maxmind_geolite_city.rb"
end
# Define the next_migration_number method (necessary for the
# migration_template method to work)
def self.next_migration_number(dirname)
if ActiveRecord::Base.timestamped_migrations
sleep 1 # make sure each time we get a different timestamp
Time.new.utc.strftime("%Y%m%d%H%M%S")
else
"%.3d" % (current_migration_number(dirname) + 1)
end
end
end
end
end
end
geocoder-1.5.1/lib/generators/geocoder/maxmind/geolite_country_generator.rb 0000644 0000041 0000041 00000001726 13503210114 027302 0 ustar www-data www-data require 'rails/generators/migration'
require 'generators/geocoder/migration_version'
module Geocoder
module Generators
module Maxmind
class GeoliteCountryGenerator < Rails::Generators::Base
include Rails::Generators::Migration
include Generators::MigrationVersion
source_root File.expand_path('../templates', __FILE__)
def copy_migration_files
migration_template "migration/geolite_country.rb", "db/migrate/geocoder_maxmind_geolite_country.rb"
end
# Define the next_migration_number method (necessary for the
# migration_template method to work)
def self.next_migration_number(dirname)
if ActiveRecord::Base.timestamped_migrations
sleep 1 # make sure each time we get a different timestamp
Time.new.utc.strftime("%Y%m%d%H%M%S")
else
"%.3d" % (current_migration_number(dirname) + 1)
end
end
end
end
end
end
geocoder-1.5.1/lib/generators/geocoder/maxmind/templates/ 0000755 0000041 0000041 00000000000 13503210114 023464 5 ustar www-data www-data geocoder-1.5.1/lib/generators/geocoder/maxmind/templates/migration/ 0000755 0000041 0000041 00000000000 13503210114 025455 5 ustar www-data www-data geocoder-1.5.1/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb 0000644 0000041 0000041 00000002162 13503210114 030463 0 ustar www-data www-data class GeocoderMaxmindGeoliteCity < ActiveRecord::Migration<%= migration_version %>
def self.up
create_table :maxmind_geolite_city_blocks, id: false do |t|
t.column :start_ip_num, :bigint, null: false
t.column :end_ip_num, :bigint, null: false
t.column :loc_id, :bigint, null: false
end
add_index :maxmind_geolite_city_blocks, :loc_id
add_index :maxmind_geolite_city_blocks, :start_ip_num, unique: true
add_index :maxmind_geolite_city_blocks, [:end_ip_num, :start_ip_num], unique: true, name: 'index_maxmind_geolite_city_blocks_on_end_ip_num_range'
create_table :maxmind_geolite_city_location, id: false do |t|
t.column :loc_id, :bigint, null: false
t.string :country, null: false
t.string :region, null: false
t.string :city
t.string :postal_code, null: false
t.float :latitude
t.float :longitude
t.integer :metro_code
t.integer :area_code
end
add_index :maxmind_geolite_city_location, :loc_id, unique: true
end
def self.down
drop_table :maxmind_geolite_city_location
drop_table :maxmind_geolite_city_blocks
end
end
geocoder-1.5.1/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb 0000644 0000041 0000041 00000001074 13503210114 031217 0 ustar www-data www-data class GeocoderMaxmindGeoliteCountry < ActiveRecord::Migration<%= migration_version %>
def self.up
create_table :maxmind_geolite_country, id: false do |t|
t.column :start_ip, :string
t.column :end_ip, :string
t.column :start_ip_num, :bigint, null: false
t.column :end_ip_num, :bigint, null: false
t.column :country_code, :string, null: false
t.column :country, :string, null: false
end
add_index :maxmind_geolite_country, :start_ip_num, unique: true
end
def self.down
drop_table :maxmind_geolite_country
end
end
geocoder-1.5.1/lib/generators/geocoder/migration_version.rb 0000644 0000041 0000041 00000000431 13503210114 024112 0 ustar www-data www-data module Geocoder
module Generators
module MigrationVersion
def rails_5?
Rails::VERSION::MAJOR == 5
end
def migration_version
if rails_5?
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end
end
end
end
end
geocoder-1.5.1/lib/generators/geocoder/config/ 0000755 0000041 0000041 00000000000 13503210114 021276 5 ustar www-data www-data geocoder-1.5.1/lib/generators/geocoder/config/templates/ 0000755 0000041 0000041 00000000000 13503210114 023274 5 ustar www-data www-data geocoder-1.5.1/lib/generators/geocoder/config/templates/initializer.rb 0000644 0000041 0000041 00000002107 13503210114 026144 0 ustar www-data www-data Geocoder.configure(
# Geocoding options
# timeout: 3, # geocoding service timeout (secs)
# lookup: :nominatim, # name of geocoding service (symbol)
# ip_lookup: :ipinfo_io, # name of IP address geocoding service (symbol)
# language: :en, # ISO-639 language code
# use_https: false, # use HTTPS for lookup requests? (if supported)
# http_proxy: nil, # HTTP proxy server (user:pass@host:port)
# 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);
# supports SocketError and Timeout::Error
# always_raise: [],
# Calculation options
# units: :mi, # :km for kilometers or :mi for miles
# distances: :linear # :spherical or :linear
)
geocoder-1.5.1/lib/generators/geocoder/config/config_generator.rb 0000644 0000041 0000041 00000000631 13503210114 025136 0 ustar www-data www-data require 'rails/generators'
module Geocoder
class ConfigGenerator < Rails::Generators::Base
source_root File.expand_path("../templates", __FILE__)
desc "This generator creates an initializer file at config/initializers, " +
"with the default configuration options for Geocoder."
def add_initializer
template "initializer.rb", "config/initializers/geocoder.rb"
end
end
end
geocoder-1.5.1/geocoder.gemspec 0000644 0000041 0000041 00000016501 13503210114 016462 0 ustar www-data www-data #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: geocoder 1.5.1 ruby lib
Gem::Specification.new do |s|
s.name = "geocoder".freeze
s.version = "1.5.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.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.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.summary = "Complete geocoding solution for Ruby.".freeze
end