Skip to content
Snippets Groups Projects
Commit 7005c7bb authored by Idris's avatar Idris
Browse files

Merge branch 'master' of git://github.com/alexreisner/geocoder

parents e35930dc f197ec52
No related branches found
No related tags found
No related merge requests found
Showing
with 1349 additions and 772 deletions
......@@ -2,3 +2,4 @@ pkg/*
rdoc/*
*.gem
.bundle
Gemfile.lock
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- jruby-19mode
gemfile:
- Gemfile
- gemfiles/Gemfile.mongoid-2.4.x
env: SSL_CERT_DIR=/etc/ssl/certs
matrix:
exclude:
- rvm: 1.8.7
gemfile: Gemfile
env: SSL_CERT_DIR=/etc/ssl/certs
- rvm: 1.9.2
gemfile: Gemfile
env: SSL_CERT_DIR=/etc/ssl/certs
- rvm: 1.9.3
gemfile: gemfiles/Gemfile.mongoid-2.4.x
env: SSL_CERT_DIR=/etc/ssl/certs
- rvm: jruby-19mode
gemfile: gemfiles/Gemfile.mongoid-2.4.x
env: SSL_CERT_DIR=/etc/ssl/certs
= Changelog
Changelog
=========
Per-release changes to Geocoder.
== 1.1.1 (2012 Feb 16)
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.
......@@ -10,7 +77,8 @@ Per-release changes to Geocoder.
* 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)
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.
......@@ -22,17 +90,20 @@ Per-release changes to Geocoder.
* 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)
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)
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)
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).
......@@ -40,19 +111,22 @@ Per-release changes to Geocoder.
* 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)
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)
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)
1.0.0 (2011 May 9)
------------------
* Add command line interface.
* Add support for local proxy (thanks github.com/Olivier).
......@@ -61,11 +135,13 @@ Per-release changes to Geocoder.
* 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)
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)
0.9.12 (2011 Apr 6)
-------------------
* Add support for Mongoid.
* Add bearing_to/from methods to geocoded objects.
......@@ -76,7 +152,8 @@ Per-release changes to Geocoder.
* DEPRECATION: Geocoder.near should not take <tt>:limit</tt> or <tt>:offset</tt> 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)
0.9.11 (2011 Mar 25)
--------------------
* Add support for result caching.
* Add support for Geocoder.ca geocoding service.
......@@ -86,12 +163,14 @@ Per-release changes to Geocoder.
* DEPRECATION: <tt>Geocoder.search</tt> now returns an array instead of a single result.
* DEPRECATION: <tt>obj.nearbys</tt> second argument is now an options hash (instead of units). Please change <tt>obj.nearbys(20, :km)</tt> to: <tt>obj.nearbys(20, :units => :km)</tt>.
== 0.9.10 (2011 Mar 9)
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)
0.9.9 (2011 Mar 9)
------------------
* Add support for IP address geocoding via FreeGeoIp.net.
* Add support for Yahoo PlaceFinder geocoding API.
......@@ -106,7 +185,8 @@ Per-release changes to Geocoder.
* DEPRECATION: <tt>fetch_address!</tt> 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)
0.9.8 (2011 Feb 8)
------------------
* Include <tt>geocode:all</tt> Rake task in gem (was missing!).
* Add <tt>Geocoder.search</tt> for access to Google's full response.
......@@ -114,87 +194,105 @@ Per-release changes to Geocoder.
* Emit warnings on Google connection problems and errors.
* Refactor: insert Geocoder into ActiveRecord via Railtie.
== 0.9.7 (2011 Feb 1)
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)
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)
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)
0.9.4 (2010 Aug 2)
------------------
* Google Maps API key no longer required (uses geocoder v3).
== 0.9.3 (2010 Aug 2)
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)
0.9.2 (2010 Jun 3)
------------------
* Fix LIMIT clause bug in PostgreSQL (reported by github.com/kenzie).
== 0.9.1 (2010 May 4)
0.9.1 (2010 May 4)
------------------
* Use scope instead of named_scope in Rails 3.
== 0.9.0 (2010 Apr 2)
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)
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)
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)
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)
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)
0.8.5 (2009 Oct 26)
-------------------
* Avoid calling deprecated method from within Geocoder itself.
== 0.8.4 (2009 Oct 23)
0.8.4 (2009 Oct 23)
-------------------
* Deprecate <tt>find_near</tt> class method in favor of +near+ named scope.
== 0.8.3 (2009 Oct 23)
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)
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)
0.8.1 (2009 Oct 8)
------------------
* Extract XML-fetching code from <tt>Geocoder.search</tt> and place in Geocoder._fetch_xml (for ease of mocking).
* Add tests for coordinate-fetching instance methods.
== 0.8.0 (2009 Oct 1)
0.8.0 (2009 Oct 1)
------------------
First release.
source "http://rubygems.org"
group :development, :test do
gem 'rake'
gem 'mongoid', '3.0.13'
gem 'bson_ext', :platforms => :ruby
gem 'rails'
platforms :jruby do
gem 'jruby-openssl'
end
end
gemspec
README.md 0 → 100644
This diff is collapsed.
This diff is collapsed.
# 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 AutoexpireCache
def initialize(store)
@store = store
@ttl = 86400
end
def [](url)
@store.[](url)
end
def []=(url, value)
@store.[]=(url, value)
@store.expire(url, @ttl)
end
def keys
@store.keys
end
def del(url)
@store.del(url)
end
end
Geocoder.configure(:cache => AutoexpireCache.new(Redis.new))
source "http://rubygems.org"
gemspec :path => '..'
group :development, :test do
gem 'rake'
gem 'mongoid', '2.4.11'
gem 'bson_ext', :platforms => :ruby
gem 'rails'
platforms :jruby do
gem 'jruby-openssl'
end
end
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.configure(
# geocoding options
# :timeout => 3, # geocoding service timeout (secs)
# :lookup => :google, # name of 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 #keys)
# :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 TimeoutError
# :always_raise => [],
# calculation options
# :units => :mi, # :km for kilometers or :mi for miles
# :distances => :linear # :spherical or :linear
)
require "geocoder/configuration"
require "geocoder/query"
require "geocoder/calculations"
require "geocoder/exceptions"
require "geocoder/cache"
require "geocoder/request"
require "geocoder/models/active_record"
require "geocoder/models/mongoid"
require "geocoder/models/mongo_mapper"
require "geocoder/lookup"
require "geocoder/models/active_record" if defined?(::ActiveRecord)
require "geocoder/models/mongoid" if defined?(::Mongoid)
require "geocoder/models/mongo_mapper" if defined?(::MongoMapper)
module Geocoder
extend self
......@@ -13,15 +15,16 @@ module Geocoder
##
# Search for information about an address or a set of coordinates.
#
def search(query)
blank_query?(query) ? [] : lookup(query).search(query)
def search(query, options = {})
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
query.blank? ? [] : query.execute
end
##
# Look up the coordinates of the given street or IP address.
#
def coordinates(address)
if (results = search(address)).size > 0
def coordinates(address, options = {})
if (results = search(address, options)).size > 0
results.first.coordinates
end
end
......@@ -30,8 +33,8 @@ module Geocoder
# Look up the address of the given coordinates ([lat,lon])
# or IP address (string).
#
def address(query)
if (results = search(query)).size > 0
def address(query, options = {})
if (results = search(query, options)).size > 0
results.first.address
end
end
......@@ -40,89 +43,8 @@ module Geocoder
# The working Cache object, or +nil+ if none configured.
#
def cache
if @cache.nil? and store = Configuration.cache
@cache = Cache.new(store, Configuration.cache_prefix)
end
@cache
end
##
# Array of valid Lookup names.
#
def valid_lookups
street_lookups + ip_lookups
end
##
# All street address lookups, default first.
#
def street_lookups
[:google, :google_premier, :yahoo, :bing, :geocoder_ca, :yandex, :nominatim]
end
##
# All IP address lookups, default first.
#
def ip_lookups
[:freegeoip]
end
private # -----------------------------------------------------------------
##
# Get a Lookup object (which communicates with the remote geocoding API).
# Takes a search query and returns an IP or street address Lookup
# depending on the query contents.
#
def lookup(query)
if ip_address?(query)
get_lookup(ip_lookups.first)
else
get_lookup(Configuration.lookup || street_lookups.first)
end
end
##
# Retrieve a Lookup object from the store.
#
def get_lookup(name)
@lookups = {} unless defined?(@lookups)
@lookups[name] = spawn_lookup(name) unless @lookups.include?(name)
@lookups[name]
end
##
# Spawn a Lookup of the given name.
#
def spawn_lookup(name)
if valid_lookups.include?(name)
name = name.to_s
require "geocoder/lookups/#{name}"
klass = name.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
Geocoder::Lookup.const_get(klass).new
else
valids = valid_lookups.map(&:inspect).join(", ")
raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
"(#{name.inspect} is not one of: #{valids})."
end
end
##
# Does the given value look like an IP address?
#
# Does not check for actual validity, just the appearance of four
# dot-delimited numbers.
#
def ip_address?(value)
!!value.to_s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
end
##
# Is the given search query blank? (ie, should we not bother searching?)
#
def blank_query?(value)
!!value.to_s.match(/^\s*$/)
warn "WARNING: Calling Geocoder.cache is DEPRECATED. The #cache method now belongs to the Geocoder::Lookup object."
Geocoder::Lookup.get(Geocoder.config.lookup).send(:configuration).cache
end
end
......
......@@ -10,14 +10,24 @@ module Geocoder
# Read from the Cache.
#
def [](url)
interpret store[key_for(url)]
interpret case
when store.respond_to?(:[])
store[key_for(url)]
when store.respond_to?(:get)
store.get key_for(url)
end
end
##
# Write to the Cache.
#
def []=(url, value)
store[key_for(url)] = value
case
when store.respond_to?(:[]=)
store[key_for(url)] = value
when store.respond_to?(:set)
store.set key_for(url), value
end
end
##
......@@ -68,7 +78,8 @@ module Geocoder
end
def expire_single_url(url)
store.del(key_for(url))
key = key_for(url)
store.respond_to?(:del) ? store.del(key) : store.delete(key)
end
end
end
......@@ -21,10 +21,26 @@ module Geocoder
#
KM_IN_MI = 0.621371192
# 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 = :mi)
def latitude_degree_distance(units = nil)
units ||= Geocoder.config.units
2 * Math::PI * earth_radius(units) / 360
end
......@@ -32,7 +48,8 @@ module Geocoder
# 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 = :mi)
def longitude_degree_distance(latitude, units = nil)
units ||= Geocoder.config.units
latitude_degree_distance(units) * Math.cos(to_radians(latitude))
end
......@@ -49,12 +66,13 @@ module Geocoder
#
# The options hash supports:
#
# * <tt>:units</tt> - <tt>:mi</tt> (default) or <tt>:km</tt>
# * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>
# Use Geocoder.configure(:units => ...) to configure default units.
#
def distance_between(point1, point2, options = {})
# set default options
options[:units] ||= :mi
options[:units] ||= Geocoder.config.units
# convert to coordinate arrays
point1 = extract_coordinates(point1)
......@@ -81,17 +99,18 @@ module Geocoder
# See Geocoder::Calculations.distance_between for
# ways of specifying the points. Also accepts an options hash:
#
# * <tt>:method</tt> - <tt>:linear</tt> (default) or <tt>:spherical</tt>;
# * <tt>:method</tt> - <tt>:linear</tt> or <tt>:spherical</tt>;
# the spherical method is "correct" in that it returns the shortest path
# (one along a great circle) but the linear method is the default as it
# is less confusing (returns due east or west when given two points with
# the same latitude)
# (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
......@@ -165,7 +184,7 @@ module Geocoder
end
##
# Returns coordinates of the lower-left and upper-right corners of a box
# 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).
......@@ -177,12 +196,13 @@ module Geocoder
# See Geocoder::Calculations.distance_between for
# ways of specifying the point. Also accepts an options hash:
#
# * <tt>:units</tt> - <tt>:mi</tt> (default) or <tt>:km</tt>
# * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>.
# Use Geocoder.configure(:units => ...) to configure default units.
#
def bounding_box(point, radius, options = {})
lat,lon = extract_coordinates(point)
radius = radius.to_f
units = options[:units] || :mi
units = options[:units] || Geocoder.config.units
[
lat - (radius / latitude_degree_distance(units)),
lon - (radius / longitude_degree_distance(lat, units)),
......@@ -219,11 +239,13 @@ module Geocoder
end
end
def distance_to_radians(distance, units = :mi)
def distance_to_radians(distance, units = nil)
units ||= Geocoder.config.units
distance.to_f / earth_radius(units)
end
def radians_to_distance(radians, units = :mi)
def radians_to_distance(radians, units = nil)
units ||= Geocoder.config.units
radians * earth_radius(units)
end
......@@ -242,9 +264,11 @@ module Geocoder
end
##
# Radius of the Earth in the given units (:mi or :km). Default is :mi.
# Radius of the Earth in the given units (:mi or :km).
# Use Geocoder.configure(:units => ...) to configure default units.
#
def earth_radius(units = :mi)
def earth_radius(units = nil)
units ||= Geocoder.config.units
units == :km ? EARTH_RADIUS : to_miles(EARTH_RADIUS)
end
......@@ -270,10 +294,26 @@ module Geocoder
#
def extract_coordinates(point)
case point
when Array; point
when String; Geocoder.coordinates(point)
else point.to_coordinates
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
......@@ -13,33 +13,33 @@ module Geocoder
opts.separator "\nOptions: "
opts.on("-k <key>", "--key <key>",
"Key for geocoding API (optional for most). For Google Premier use 'key client channel' separated by spaces") do |key|
premier_key = key.split(' ')
if premier_key.length == 3
Geocoder::Configuration.api_key = premier_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::Configuration.api_key = key
Geocoder.configure(:api_key => key)
end
end
opts.on("-l <language>", "--language <language>",
"Language of output (see API docs for valid choices)") do |language|
Geocoder::Configuration.language = language
Geocoder.configure(:language => language)
end
opts.on("-p <proxy>", "--proxy <proxy>",
"HTTP proxy server to use (user:pass@host:port)") do |proxy|
Geocoder::Configuration.http_proxy = proxy
Geocoder.configure(:http_proxy => proxy)
end
opts.on("-s <service>", Geocoder.street_lookups, "--service <service>",
"Geocoding service: #{Geocoder.street_lookups * ', '}") do |service|
Geocoder::Configuration.lookup = service.to_sym
opts.on("-s <service>", Geocoder::Lookup.all_services_except_test, "--service <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 <seconds>", "--timeout <seconds>",
"Maximum number of seconds to wait for API response") do |timeout|
Geocoder::Configuration.timeout = timeout.to_i
Geocoder.configure(:timeout => timeout.to_i)
end
opts.on("-j", "--json", "Print API's raw JSON response") do
......@@ -78,21 +78,19 @@ module Geocoder
end
if show_url
lookup = Geocoder.send(:lookup, query)
reverse = lookup.send(:coordinates?, query)
out << lookup.send(:query_url, query, reverse) + "\n"
q = Geocoder::Query.new(query)
out << q.url + "\n"
exit 0
end
if show_json
lookup = Geocoder.send(:lookup, query)
reverse = lookup.send(:coordinates?, query)
out << lookup.send(:fetch_raw_data, query, reverse) + "\n"
q = Geocoder::Query.new(query)
out << q.lookup.send(:fetch_raw_data, q) + "\n"
exit 0
end
if (result = Geocoder.search(query).first)
lookup = Geocoder.send(:get_lookup, :google)
google = Geocoder::Lookup.get(:google)
lines = [
["Latitude", result.latitude],
["Longitude", result.longitude],
......@@ -101,7 +99,7 @@ module Geocoder
["State/province", result.state],
["Postal code", result.postal_code],
["Country", result.country],
["Google map", lookup.map_link_url(result.coordinates)],
["Google map", google.map_link_url(result.coordinates)],
]
lines.each do |line|
out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
......
module Geocoder
class Configuration
require 'singleton'
require 'geocoder/configuration_hash'
def self.options_and_defaults
[
# geocoding service timeout (secs)
[:timeout, 3],
module Geocoder
# name of geocoding service (symbol)
[:lookup, :google],
##
# 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 block_given?
warn "WARNING: Passing a block to Geocoder.configure is DEPRECATED. Please pass a hash instead (eg: Geocoder.configure(:units => ..., :api_key => ...))."
block.call(Configuration.instance)
elsif !options.nil?
Configuration.instance.configure(options)
else
warn "WARNING: Use of Geocoder.configure to read or write single config options is DEPRECATED. To write to the config please pass a hash (eg: Geocoder.configure(:units => ...)). To read config options please use the Geocoder.config object (eg: Geocoder.config.units)."
Configuration.instance
end
end
# ISO-639 language code
[:language, :en],
##
# Read-only access to the singleton's config data.
#
def self.config
Configuration.instance.data
end
# use HTTPS for lookup requests? (if supported)
[:use_https, false],
##
# 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
# HTTP proxy server (user:pass@host:port)
[:http_proxy, nil],
class Configuration
include Singleton
# HTTPS proxy server (user:pass@host:port)
[:https_proxy, nil],
OPTIONS = [
:timeout,
:lookup,
:ip_lookup,
:language,
:http_headers,
:use_https,
:http_proxy,
:https_proxy,
:api_key,
:cache,
:cache_prefix,
:always_raise,
:units,
:distances
]
# API key for geocoding service
# for Google Premier use a 3-element array: [key, client, channel]
[:api_key, nil],
attr_accessor :data
# cache object (must respond to #[], #[]=, and #keys)
[:cache, nil],
def self.set_defaults
instance.set_defaults
end
# prefix (string) to use for all cache keys
[:cache_prefix, "geocoder:"],
OPTIONS.each do |o|
define_method o do
@data[o]
end
define_method "#{o}=" do |value|
@data[o] = value
end
end
# exceptions that should not be rescued by default
# (if you want to implement custom error handling);
# supports SocketError and TimeoutError
[:always_raise, []]
]
def configure(options)
@data.rmerge!(options)
end
# define getters and setters for all configuration settings
self.options_and_defaults.each do |option, default|
class_eval(<<-END, __FILE__, __LINE__ + 1)
def initialize # :nodoc
@data = Geocoder::ConfigurationHash.new
set_defaults
end
@@#{option} = default unless defined? @@#{option}
def set_defaults
def self.#{option}
@@#{option}
end
# geocoding options
@data[:timeout] = 3 # geocoding service timeout (secs)
@data[:lookup] = :google # name of street address geocoding service (symbol)
@data[:ip_lookup] = :freegeoip # 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
def self.#{option}=(obj)
@@#{option} = obj
end
# exceptions that should not be rescued by default
# (if you want to implement custom error handling);
# supports SocketError and TimeoutError
@data[:always_raise] = []
END
# 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
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
......@@ -8,4 +8,14 @@ module Geocoder
class OverQueryLimitError < Error
end
class RequestDenied < Error
end
class InvalidRequest < Error
end
class InvalidApiKey < Error
end
end
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
[
:google,
:google_premier,
:yahoo,
:bing,
:geocoder_ca,
:yandex,
:nominatim,
:mapquest,
:test
]
end
##
# All IP address lookup services, default first.
#
def ip_services
[:freegeoip, :maxmind]
end
##
# 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)
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::Lookup.all_services.each do |name|
require "geocoder/lookups/#{name}"
end
require 'net/http'
require 'net/https'
require 'uri'
unless defined?(ActiveSupport::JSON)
......@@ -12,8 +13,24 @@ end
module Geocoder
module Lookup
class Base
##
# 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.
......@@ -22,18 +39,13 @@ module Geocoder
# "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
# for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
#
def search(query)
# if coordinates given as string, turn into array
query = query.split(/\s*,\s*/) if coordinates?(query)
if query.is_a?(Array)
reverse = true
query = query.join(',')
else
reverse = false
end
results(query, reverse).map{ |r| result_class.new(r) }
def search(query, options = {})
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
results(query).map{ |r|
result = result_class.new(r)
result.cache_hit = @cache_hit if cache
result
}
end
##
......@@ -46,16 +58,37 @@ module Geocoder
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.
#
def query_url(query)
fail
end
private # -------------------------------------------------------------
##
# 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
protocol = "http#{'s' if Geocoder::Configuration.use_https}"
protocol = "http#{'s' if configuration.use_https}"
proxy_name = "#{protocol}_proxy"
if proxy = Geocoder::Configuration.send(proxy_name)
if proxy = configuration.send(proxy_name)
proxy_url = protocol + '://' + proxy
begin
uri = URI.parse(proxy_url)
......@@ -72,15 +105,28 @@ module Geocoder
##
# Geocoder::Result object or nil on timeout or other error.
#
def results(query, reverse = false)
def results(query)
fail
end
def query_url_params(query)
query.options[:params] || {}
end
def url_query_string(query)
hash_to_query(
query_url_params(query).reject{ |key,value| value.nil? }
)
end
##
# URL to use for querying the geocoding engine.
# 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 query_url(query, reverse = false)
fail
def cache_key(query)
query_url(query)
end
##
......@@ -95,7 +141,8 @@ module Geocoder
# Return false if exception not raised.
#
def raise_error(error, message = nil)
if Geocoder::Configuration.always_raise.include?(error.class)
exceptions = configuration.always_raise
if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
raise error, message
else
false
......@@ -105,30 +152,26 @@ module Geocoder
##
# Returns a parsed search result (Ruby hash).
#
def fetch_data(query, reverse = false)
begin
parse_raw_data fetch_raw_data(query, reverse)
rescue SocketError => err
raise_error(err) or warn "Geocoding API connection cannot be established."
rescue TimeoutError => err
raise_error(err) or warn "Geocoding API not responding fast enough " +
"(see Geocoder::Configuration.timeout to set limit)."
end
def fetch_data(query)
parse_raw_data fetch_raw_data(query)
rescue SocketError => err
raise_error(err) or warn "Geocoding API connection cannot be established."
rescue TimeoutError => err
raise_error(err) or warn "Geocoding API not responding fast enough " +
"(use Geocoder.configure(:timeout => ...) to set limit)."
end
##
# Parses a raw search result (returns hash or array).
#
def parse_raw_data(raw_data)
begin
if defined?(ActiveSupport::JSON)
ActiveSupport::JSON.decode(raw_data)
else
JSON.parse(raw_data)
end
rescue
warn "Geocoding API's response was not valid JSON."
if defined?(ActiveSupport::JSON)
ActiveSupport::JSON.decode(raw_data)
else
JSON.parse(raw_data)
end
rescue
warn "Geocoding API's response was not valid JSON."
end
##
......@@ -136,48 +179,60 @@ module Geocoder
# Set in configuration but not available for every service.
#
def protocol
"http" + (Geocoder::Configuration.use_https ? "s" : "")
"http" + (configuration.use_https ? "s" : "")
end
##
# Fetches a raw search result (JSON string).
# Fetch a raw geocoding result (JSON string).
# The result might or might not be cached.
#
def fetch_raw_data(query, reverse = false)
timeout(Geocoder::Configuration.timeout) do
url = query_url(query, reverse)
uri = URI.parse(url)
unless cache and body = cache[url]
client = http_client.new(uri.host, uri.port)
client.use_ssl = true if Geocoder::Configuration.use_https
response = client.get(uri.request_uri)
body = response.body
if cache and (200..399).include?(response.code.to_i)
cache[url] = body
end
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)
body = response.body
if cache and (200..399).include?(response.code.to_i)
cache[key] = body
end
body
@cache_hit = false
end
body
end
##
# The working Cache object.
#
def cache
Geocoder.cache
# Make an HTTP(S) request to a geocoding API and
# return the response object.
#
def make_api_request(query)
timeout(configuration.timeout) do
uri = URI.parse(query_url(query))
client = http_client.new(uri.host, uri.port)
client.use_ssl = true if configuration.use_https
client.get(uri.request_uri, configuration.http_headers)
end
end
##
# Is the given string a loopback IP address?
#
def loopback_address?(ip)
!!(ip == "0.0.0.0" or ip.to_s.match(/^127/))
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
##
# Does the given string look like latitude/longitude coordinates?
# The working Cache object.
#
def coordinates?(value)
value.is_a?(String) and !!value.to_s.match(/^-?[0-9\.]+, *-?[0-9\.]+$/)
def cache
if @cache.nil? and store = configuration.cache
@cache = Cache.new(store, configuration.cache_prefix)
end
@cache
end
##
......
......@@ -4,30 +4,44 @@ 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
def query_url(query)
"#{protocol}://dev.virtualearth.net/REST/v1/Locations" +
(query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
url_query_string(query)
end
private # ---------------------------------------------------------------
def results(query, reverse = false)
return [] unless doc = fetch_data(query, reverse)
def results(query)
return [] unless doc = fetch_data(query)
if doc['statusDescription'] == "OK"
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) || warn("Invalid Bing API key.")
else
warn "Bing Geocoding API error: #{doc['statusCode']} (#{doc['statusDescription']})."
return []
end
return []
end
def query_url(query, reverse = false)
params = {:key => Geocoder::Configuration.api_key}
params[:query] = query unless reverse
base_url = "http://dev.virtualearth.net/REST/v1/Locations"
url_tail = reverse ? "/#{query}?" : "?"
base_url + url_tail + hash_to_query(params)
def query_url_params(query)
{
:key => configuration.api_key,
:query => query.reverse_geocode? ? nil : query.sanitized_text
}.merge(super)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment