geocoder-1.8.6/0000755000004100000410000000000015066776054013365 5ustar www-datawww-datageocoder-1.8.6/geocoder.gemspec0000644000004100000410000002154215066776054016525 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: geocoder 1.8.6 ruby lib Gem::Specification.new do |s| s.name = "geocoder".freeze s.version = "1.8.6".freeze 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 = "2025-09-21" s.description = "Object geocoding (by street or IP address), reverse geocoding (coordinates to street address), distance queries for ActiveRecord and Mongoid, result caching, and more. Designed for Rails but works with Sinatra and other Rack frameworks too.".freeze s.email = ["alex@alexreisner.com".freeze] s.executables = ["geocode".freeze] s.files = ["CHANGELOG.md".freeze, "LICENSE".freeze, "README.md".freeze, "bin/console".freeze, "bin/geocode".freeze, "examples/app_defined_lookup_services.rb".freeze, "examples/cache_bypass.rb".freeze, "examples/reverse_geocode_job.rb".freeze, "lib/easting_northing.rb".freeze, "lib/generators/geocoder/config/config_generator.rb".freeze, "lib/generators/geocoder/config/templates/initializer.rb".freeze, "lib/generators/geocoder/maxmind/geolite_city_generator.rb".freeze, "lib/generators/geocoder/maxmind/geolite_country_generator.rb".freeze, "lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb".freeze, "lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb".freeze, "lib/generators/geocoder/migration_version.rb".freeze, "lib/geocoder.rb".freeze, "lib/geocoder/cache.rb".freeze, "lib/geocoder/cache_stores/base.rb".freeze, "lib/geocoder/cache_stores/generic.rb".freeze, "lib/geocoder/cache_stores/redis.rb".freeze, "lib/geocoder/calculations.rb".freeze, "lib/geocoder/cli.rb".freeze, "lib/geocoder/configuration.rb".freeze, "lib/geocoder/configuration_hash.rb".freeze, "lib/geocoder/esri_token.rb".freeze, "lib/geocoder/exceptions.rb".freeze, "lib/geocoder/ip_address.rb".freeze, "lib/geocoder/kernel_logger.rb".freeze, "lib/geocoder/logger.rb".freeze, "lib/geocoder/lookup.rb".freeze, "lib/geocoder/lookups/abstract_api.rb".freeze, "lib/geocoder/lookups/amap.rb".freeze, "lib/geocoder/lookups/amazon_location_service.rb".freeze, "lib/geocoder/lookups/azure.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/esri.rb".freeze, "lib/geocoder/lookups/freegeoip.rb".freeze, "lib/geocoder/lookups/geoapify.rb".freeze, "lib/geocoder/lookups/geocoder_ca.rb".freeze, "lib/geocoder/lookups/geocodio.rb".freeze, "lib/geocoder/lookups/geoip2.rb".freeze, "lib/geocoder/lookups/geoportail_lu.rb".freeze, "lib/geocoder/lookups/google.rb".freeze, "lib/geocoder/lookups/google_places_details.rb".freeze, "lib/geocoder/lookups/google_places_search.rb".freeze, "lib/geocoder/lookups/google_premier.rb".freeze, "lib/geocoder/lookups/here.rb".freeze, "lib/geocoder/lookups/ip2location.rb".freeze, "lib/geocoder/lookups/ip2location_io.rb".freeze, "lib/geocoder/lookups/ip2location_lite.rb".freeze, "lib/geocoder/lookups/ipapi_com.rb".freeze, "lib/geocoder/lookups/ipbase.rb".freeze, "lib/geocoder/lookups/ipdata_co.rb".freeze, "lib/geocoder/lookups/ipgeolocation.rb".freeze, "lib/geocoder/lookups/ipinfo_io.rb".freeze, "lib/geocoder/lookups/ipinfo_io_lite.rb".freeze, "lib/geocoder/lookups/ipqualityscore.rb".freeze, "lib/geocoder/lookups/ipregistry.rb".freeze, "lib/geocoder/lookups/ipstack.rb".freeze, "lib/geocoder/lookups/latlon.rb".freeze, "lib/geocoder/lookups/location_iq.rb".freeze, "lib/geocoder/lookups/mapbox.rb".freeze, "lib/geocoder/lookups/mapquest.rb".freeze, "lib/geocoder/lookups/maxmind.rb".freeze, "lib/geocoder/lookups/maxmind_geoip2.rb".freeze, "lib/geocoder/lookups/maxmind_local.rb".freeze, "lib/geocoder/lookups/melissa_street.rb".freeze, "lib/geocoder/lookups/nationaal_georegister_nl.rb".freeze, "lib/geocoder/lookups/nominatim.rb".freeze, "lib/geocoder/lookups/opencagedata.rb".freeze, "lib/geocoder/lookups/osmnames.rb".freeze, "lib/geocoder/lookups/pc_miler.rb".freeze, "lib/geocoder/lookups/pdok_nl.rb".freeze, "lib/geocoder/lookups/pelias.rb".freeze, "lib/geocoder/lookups/photon.rb".freeze, "lib/geocoder/lookups/pickpoint.rb".freeze, "lib/geocoder/lookups/pointpin.rb".freeze, "lib/geocoder/lookups/postcode_anywhere_uk.rb".freeze, "lib/geocoder/lookups/postcodes_io.rb".freeze, "lib/geocoder/lookups/smarty_streets.rb".freeze, "lib/geocoder/lookups/telize.rb".freeze, "lib/geocoder/lookups/tencent.rb".freeze, "lib/geocoder/lookups/test.rb".freeze, "lib/geocoder/lookups/twogis.rb".freeze, "lib/geocoder/lookups/uk_ordnance_survey_names.rb".freeze, "lib/geocoder/lookups/yandex.rb".freeze, "lib/geocoder/models/active_record.rb".freeze, "lib/geocoder/models/base.rb".freeze, "lib/geocoder/models/mongo_base.rb".freeze, "lib/geocoder/models/mongo_mapper.rb".freeze, "lib/geocoder/models/mongoid.rb".freeze, "lib/geocoder/query.rb".freeze, "lib/geocoder/railtie.rb".freeze, "lib/geocoder/request.rb".freeze, "lib/geocoder/results/abstract_api.rb".freeze, "lib/geocoder/results/amap.rb".freeze, "lib/geocoder/results/amazon_location_service.rb".freeze, "lib/geocoder/results/azure.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/esri.rb".freeze, "lib/geocoder/results/freegeoip.rb".freeze, "lib/geocoder/results/geoapify.rb".freeze, "lib/geocoder/results/geocoder_ca.rb".freeze, "lib/geocoder/results/geocodio.rb".freeze, "lib/geocoder/results/geoip2.rb".freeze, "lib/geocoder/results/geoportail_lu.rb".freeze, "lib/geocoder/results/google.rb".freeze, "lib/geocoder/results/google_places_details.rb".freeze, "lib/geocoder/results/google_places_search.rb".freeze, "lib/geocoder/results/google_premier.rb".freeze, "lib/geocoder/results/here.rb".freeze, "lib/geocoder/results/ip2location.rb".freeze, "lib/geocoder/results/ip2location_io.rb".freeze, "lib/geocoder/results/ip2location_lite.rb".freeze, "lib/geocoder/results/ipapi_com.rb".freeze, "lib/geocoder/results/ipbase.rb".freeze, "lib/geocoder/results/ipdata_co.rb".freeze, "lib/geocoder/results/ipgeolocation.rb".freeze, "lib/geocoder/results/ipinfo_io.rb".freeze, "lib/geocoder/results/ipinfo_io_lite.rb".freeze, "lib/geocoder/results/ipqualityscore.rb".freeze, "lib/geocoder/results/ipregistry.rb".freeze, "lib/geocoder/results/ipstack.rb".freeze, "lib/geocoder/results/latlon.rb".freeze, "lib/geocoder/results/location_iq.rb".freeze, "lib/geocoder/results/mapbox.rb".freeze, "lib/geocoder/results/mapquest.rb".freeze, "lib/geocoder/results/maxmind.rb".freeze, "lib/geocoder/results/maxmind_geoip2.rb".freeze, "lib/geocoder/results/maxmind_local.rb".freeze, "lib/geocoder/results/melissa_street.rb".freeze, "lib/geocoder/results/nationaal_georegister_nl.rb".freeze, "lib/geocoder/results/nominatim.rb".freeze, "lib/geocoder/results/opencagedata.rb".freeze, "lib/geocoder/results/osmnames.rb".freeze, "lib/geocoder/results/pc_miler.rb".freeze, "lib/geocoder/results/pdok_nl.rb".freeze, "lib/geocoder/results/pelias.rb".freeze, "lib/geocoder/results/photon.rb".freeze, "lib/geocoder/results/pickpoint.rb".freeze, "lib/geocoder/results/pointpin.rb".freeze, "lib/geocoder/results/postcode_anywhere_uk.rb".freeze, "lib/geocoder/results/postcodes_io.rb".freeze, "lib/geocoder/results/smarty_streets.rb".freeze, "lib/geocoder/results/telize.rb".freeze, "lib/geocoder/results/tencent.rb".freeze, "lib/geocoder/results/test.rb".freeze, "lib/geocoder/results/twogis.rb".freeze, "lib/geocoder/results/uk_ordnance_survey_names.rb".freeze, "lib/geocoder/results/yandex.rb".freeze, "lib/geocoder/sql.rb".freeze, "lib/geocoder/stores/active_record.rb".freeze, "lib/geocoder/stores/base.rb".freeze, "lib/geocoder/stores/mongo_base.rb".freeze, "lib/geocoder/stores/mongo_mapper.rb".freeze, "lib/geocoder/stores/mongoid.rb".freeze, "lib/geocoder/util.rb".freeze, "lib/geocoder/version.rb".freeze, "lib/maxmind_database.rb".freeze, "lib/tasks/geocoder.rake".freeze, "lib/tasks/maxmind.rake".freeze] s.homepage = "http://www.rubygeocoder.com".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze) s.rubygems_version = "3.6.3".freeze s.summary = "Complete geocoding solution for Ruby.".freeze s.specification_version = 4 s.add_runtime_dependency(%q.freeze, [">= 0.1.0".freeze]) s.add_runtime_dependency(%q.freeze, [">= 3.0.0".freeze]) end geocoder-1.8.6/bin/0000755000004100000410000000000015066776054014135 5ustar www-datawww-datageocoder-1.8.6/bin/console0000755000004100000410000000037015066776054015525 0ustar www-datawww-data#!/usr/bin/env ruby require 'bundler/setup' require 'geocoder' if File.exist?("api_keys.yml") require 'yaml' @api_keys = YAML.load(File.read("api_keys.yml"), symbolize_names: true) Geocoder.configure(@api_keys) end require 'irb' IRB.start geocoder-1.8.6/bin/geocode0000755000004100000410000000010415066776054015463 0ustar www-datawww-data#!/usr/bin/env ruby require 'geocoder/cli' Geocoder::Cli.run ARGV geocoder-1.8.6/lib/0000755000004100000410000000000015066776054014133 5ustar www-datawww-datageocoder-1.8.6/lib/generators/0000755000004100000410000000000015066776054016304 5ustar www-datawww-datageocoder-1.8.6/lib/generators/geocoder/0000755000004100000410000000000015066776054020073 5ustar www-datawww-datageocoder-1.8.6/lib/generators/geocoder/migration_version.rb0000644000004100000410000000043115066776054024154 0ustar www-datawww-datamodule 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.8.6/lib/generators/geocoder/config/0000755000004100000410000000000015066776054021340 5ustar www-datawww-datageocoder-1.8.6/lib/generators/geocoder/config/templates/0000755000004100000410000000000015066776054023336 5ustar www-datawww-datageocoder-1.8.6/lib/generators/geocoder/config/templates/initializer.rb0000644000004100000410000000214315066776054026206 0ustar www-datawww-dataGeocoder.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) # 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 # Cache configuration # cache_options: { # expiration: 2.days, # prefix: 'geocoder:' # } ) geocoder-1.8.6/lib/generators/geocoder/config/config_generator.rb0000644000004100000410000000063115066776054025200 0ustar www-datawww-datarequire '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.8.6/lib/generators/geocoder/maxmind/0000755000004100000410000000000015066776054021530 5ustar www-datawww-datageocoder-1.8.6/lib/generators/geocoder/maxmind/geolite_country_generator.rb0000644000004100000410000000172615066776054027344 0ustar www-datawww-datarequire '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.8.6/lib/generators/geocoder/maxmind/templates/0000755000004100000410000000000015066776054023526 5ustar www-datawww-datageocoder-1.8.6/lib/generators/geocoder/maxmind/templates/migration/0000755000004100000410000000000015066776054025517 5ustar www-datawww-datageocoder-1.8.6/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb0000644000004100000410000000216215066776054030525 0ustar www-datawww-dataclass 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.8.6/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb0000644000004100000410000000107415066776054031261 0ustar www-datawww-dataclass 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.8.6/lib/generators/geocoder/maxmind/geolite_city_generator.rb0000644000004100000410000000171515066776054026607 0ustar www-datawww-datarequire '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.8.6/lib/tasks/0000755000004100000410000000000015066776054015260 5ustar www-datawww-datageocoder-1.8.6/lib/tasks/geocoder.rake0000644000004100000410000000304415066776054017714 0ustar www-datawww-datarequire "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.8.6/lib/tasks/maxmind.rake0000644000004100000410000000365215066776054017567 0ustar www-datawww-datarequire '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_edition(p)}.zip" 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.8.6/lib/maxmind_database.rb0000644000004100000410000000663315066776054017751 0ustar www-datawww-datarequire 'csv' require 'net/http' module Geocoder module MaxmindDatabase extend self def download(package, dir = "tmp") filepath = File.expand_path(File.join(dir, "#{archive_edition(package)}.zip")) open(filepath, 'wb') do |file| uri = URI.parse(base_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 def archive_edition(package) { geolite_country_csv: "GeoLite2-Country-CSV", geolite_city_csv: "GeoLite2-City-CSV", geolite_asn_csv: "GeoLite2-ASN-CSV" }[package] 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 base_url(edition) "https://download.maxmind.com/app/geoip_download?edition_id=#{edition}&license_key=#{ENV['LICENSE_KEY']}&suffix=zip" end end end geocoder-1.8.6/lib/easting_northing.rb0000644000004100000410000001143315066776054020024 0ustar www-datawww-datamodule Geocoder class EastingNorthing attr_reader :easting, :northing, :lat_lng def initialize(opts) @easting = opts[:easting] @northing = opts[:northing] @lat_lng = to_WGS84(to_osgb_36) end private def to_osgb_36 osgb_fo = 0.9996012717 northing0 = -100_000.0 easting0 = 400_000.0 phi0 = deg_to_rad(49.0) lambda0 = deg_to_rad(-2.0) a = 6_377_563.396 b = 6_356_256.909 eSquared = ((a * a) - (b * b)) / (a * a) phi = 0.0 lambda = 0.0 n = (a - b) / (a + b) m = 0.0 phiPrime = ((northing - northing0) / (a * osgb_fo)) + phi0 while (northing - northing0 - m) >= 0.001 m = (b * osgb_fo)\ * (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n))\ * (phiPrime - phi0))\ - (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))\ * Math.sin(phiPrime - phi0)\ * Math.cos(phiPrime + phi0))\ + ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))\ * Math.sin(2.0 * (phiPrime - phi0))\ * Math.cos(2.0 * (phiPrime + phi0)))\ - (((35.0 / 24.0) * n * n * n)\ * Math.sin(3.0 * (phiPrime - phi0))\ * Math.cos(3.0 * (phiPrime + phi0)))) phiPrime += (northing - northing0 - m) / (a * osgb_fo) end v = a * osgb_fo * ((1.0 - eSquared * sin_pow_2(phiPrime))**-0.5) rho = a\ * osgb_fo\ * (1.0 - eSquared)\ * ((1.0 - eSquared * sin_pow_2(phiPrime))**-1.5) etaSquared = (v / rho) - 1.0 vii = Math.tan(phiPrime) / (2 * rho * v) viii = (Math.tan(phiPrime) / (24.0 * rho * (v**3.0)))\ * (5.0\ + (3.0 * tan_pow_2(phiPrime))\ + etaSquared\ - (9.0 * tan_pow_2(phiPrime) * etaSquared)) ix = (Math.tan(phiPrime) / (720.0 * rho * (v**5.0)))\ * (61.0\ + (90.0 * tan_pow_2(phiPrime))\ + (45.0 * tan_pow_2(phiPrime) * tan_pow_2(phiPrime))) x = sec(phiPrime) / v xi = (sec(phiPrime) / (6.0 * v * v * v))\ * ((v / rho) + (2 * tan_pow_2(phiPrime))) xiii = (sec(phiPrime) / (120.0 * (v**5.0)))\ * (5.0\ + (28.0 * tan_pow_2(phiPrime))\ + (24.0 * tan_pow_2(phiPrime) * tan_pow_2(phiPrime))) xiia = (sec(phiPrime) / (5040.0 * (v**7.0)))\ * (61.0\ + (662.0 * tan_pow_2(phiPrime))\ + (1320.0 * tan_pow_2(phiPrime) * tan_pow_2(phiPrime))\ + (720.0\ * tan_pow_2(phiPrime)\ * tan_pow_2(phiPrime)\ * tan_pow_2(phiPrime))) phi = phiPrime\ - (vii * ((easting - easting0)**2.0))\ + (viii * ((easting - easting0)**4.0))\ - (ix * ((easting - easting0)**6.0)) lambda = lambda0\ + (x * (easting - easting0))\ - (xi * ((easting - easting0)**3.0))\ + (xiii * ((easting - easting0)**5.0))\ - (xiia * ((easting - easting0)**7.0)) [rad_to_deg(phi), rad_to_deg(lambda)] end def to_WGS84(latlng) latitude = latlng[0] longitude = latlng[1] a = 6_377_563.396 b = 6_356_256.909 eSquared = ((a * a) - (b * b)) / (a * a) phi = deg_to_rad(latitude) lambda = deg_to_rad(longitude) v = a / Math.sqrt(1 - eSquared * sin_pow_2(phi)) h = 0 x = (v + h) * Math.cos(phi) * Math.cos(lambda) y = (v + h) * Math.cos(phi) * Math.sin(lambda) z = ((1 - eSquared) * v + h) * Math.sin(phi) tx = 446.448 ty = -124.157 tz = 542.060 s = -0.0000204894 rx = deg_to_rad(0.00004172222) ry = deg_to_rad(0.00006861111) rz = deg_to_rad(0.00023391666) xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z) yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z) zB = tz + (-ry * x) + (rx * y) + (z * (1 + s)) a = 6_378_137.000 b = 6_356_752.3141 eSquared = ((a * a) - (b * b)) / (a * a) lambdaB = rad_to_deg(Math.atan(yB / xB)) p = Math.sqrt((xB * xB) + (yB * yB)) phiN = Math.atan(zB / (p * (1 - eSquared))) (1..10).each do |_i| v = a / Math.sqrt(1 - eSquared * sin_pow_2(phiN)) phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p) phiN = phiN1 end phiB = rad_to_deg(phiN) [phiB, lambdaB] end def deg_to_rad(degrees) degrees / 180.0 * Math::PI end def rad_to_deg(r) (r / Math::PI) * 180 end def sin_pow_2(x) Math.sin(x) * Math.sin(x) end def cos_pow_2(x) Math.cos(x) * Math.cos(x) end def tan_pow_2(x) Math.tan(x) * Math.tan(x) end def sec(x) 1.0 / Math.cos(x) end end end geocoder-1.8.6/lib/geocoder/0000755000004100000410000000000015066776054015722 5ustar www-datawww-datageocoder-1.8.6/lib/geocoder/request.rb0000644000004100000410000001053015066776054017736 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/sql.rb0000644000004100000410000001072715066776054017055 0ustar www-datawww-datamodule 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.to_f} AND #{ne_lat.to_f} AND " # handle box that spans 180 longitude if sw_lng.to_f > ne_lng.to_f spans + "(#{lon_attr} BETWEEN #{sw_lng.to_f} AND 180 OR " + "#{lon_attr} BETWEEN -180 AND #{ne_lng.to_f})" else spans + "#{lon_attr} BETWEEN #{sw_lng.to_f} AND #{ne_lng.to_f}" 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.8.6/lib/geocoder/logger.rb0000644000004100000410000000214415066776054017527 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/ip_address.rb0000644000004100000410000000135115066776054020364 0ustar www-datawww-datarequire '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 initialize(ip) ip = ip.to_string if ip.is_a?(IPAddr) if ip.is_a?(Hash) super(**ip) else super(ip) end end 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? ip = self[/(?<=\[)(.*?)(?=\])/] || self !!((ip =~ Resolv::IPv4::Regex) || (ip =~ Resolv::IPv6::Regex)) end end end geocoder-1.8.6/lib/geocoder/configuration.rb0000644000004100000410000001000415066776054021111 0ustar www-datawww-datarequire 'singleton' require 'geocoder/configuration_hash' require 'geocoder/util' 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, :host, :http_headers, :use_https, :http_proxy, :https_proxy, :api_key, :cache, :always_raise, :units, :distances, :basic_auth, :logger, :kernel_logger_level, :cache_options ] attr_accessor :data def self.set_defaults instance.set_defaults end def self.initialize instance.send(:initialize) 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) Util.recursive_hash_merge(@data, 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[: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 # Set the default values for the caching mechanism # By default, the cache keys will not expire as IP addresses and phyiscal # addresses will rarely change. @data[:cache] = nil # cache object (must respond to #[], #[]=, and optionally #keys) @data[:cache_prefix] = nil # - DEPRECATED - prefix (string) to use for all cache keys @data[:cache_options] = { prefix: 'geocoder:', expiration: nil } end instance_eval(OPTIONS.map do |option| 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.8.6/lib/geocoder/stores/0000755000004100000410000000000015066776054017241 5ustar www-datawww-datageocoder-1.8.6/lib/geocoder/stores/mongoid.rb0000644000004100000410000000035315066776054021223 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/stores/active_record.rb0000644000004100000410000003026715066776054022407 0ustar www-datawww-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. radius_is_column = radius.is_a?(Symbol) || (defined?(Arel::Nodes::SqlLiteral) && radius.is_a?(Arel::Nodes::SqlLiteral)) bb_radius = radius_is_column ? 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_column 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.8.6/lib/geocoder/stores/base.rb0000644000004100000410000000720115066776054020500 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/stores/mongo_base.rb0000644000004100000410000000276515066776054021711 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/stores/mongo_mapper.rb0000644000004100000410000000035715066776054022256 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/util.rb0000644000004100000410000000200415066776054017220 0ustar www-datawww-data# frozen_string_literal: true module Geocoder module Util # # Recursive version of Hash#merge! # # Adds the contents of +h2+ to +h1+, # merging entries in +h1+ with duplicate keys with those from +h2+. # # Compared with Hash#merge!, this method supports nested hashes. # When both +h1+ and +h2+ contains an entry with the same key, # it merges and returns the values from both hashes. # # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}} # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}} # recursive_hash_merge(h1, h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}} # # Simply using Hash#merge! would return # # h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}} # def self.recursive_hash_merge(h1, h2) h1.merge!(h2) do |_key, oldval, newval| oldval.class == h1.class ? self.recursive_hash_merge(oldval, newval) : newval end end end end geocoder-1.8.6/lib/geocoder/esri_token.rb0000644000004100000410000000171715066776054020417 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/exceptions.rb0000644000004100000410000000102515066776054020426 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookup.rb0000644000004100000410000000706615066776054017571 0ustar www-datawww-datarequire "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 ## # Array of valid Lookup service names, excluding any that do not build their own HTTP requests. # For example, Amazon Location Service uses the AWS gem, not HTTP REST requests, to fetch data. # def all_services_with_http_requests all_services_except_test - [:amazon_location_service, :maxmind_local, :geoip2, :ip2location_lite] end ## # All street address lookup services, default first. # def street_services @street_services ||= [ :location_iq, :azure, :esri, :google, :google_premier, :google_places_details, :google_places_search, :bing, :geocoder_ca, :yandex, :nationaal_georegister_nl, :nominatim, :mapbox, :mapquest, :uk_ordnance_survey_names, :opencagedata, :pelias, :pdok_nl, :pickpoint, :here, :baidu, :tencent, :geocodio, :smarty_streets, :postcode_anywhere_uk, :postcodes_io, :geoportail_lu, :ban_data_gouv_fr, :test, :latlon, :amap, :osmnames, :melissa_street, :amazon_location_service, :geoapify, :photon, :twogis, :pc_miler ] end ## # All IP address lookup services, default first. # def ip_services @ip_services ||= [ :baidu_ip, :abstract_api, :freegeoip, :geoip2, :maxmind, :maxmind_local, :telize, :pointpin, :maxmind_geoip2, :ipinfo_io, :ipinfo_io_lite, :ipregistry, :ipapi_com, :ipdata_co, :db_ip_com, :ipstack, :ip2location, :ipgeolocation, :ipqualityscore, :ipbase, :ip2location_io, :ip2location_lite ] 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 instantiate_lookup(name) 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 ## # Safely instantiate Lookup # def instantiate_lookup(name) class_name = classify_name(name) begin Geocoder::Lookup.const_get(class_name, inherit=false) rescue NameError require "geocoder/lookups/#{name}" end Geocoder::Lookup.const_get(class_name).new end end end geocoder-1.8.6/lib/geocoder/results/0000755000004100000410000000000015066776054017423 5ustar www-datawww-datageocoder-1.8.6/lib/geocoder/results/ipqualityscore.rb0000644000004100000410000000252415066776054023030 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder module Result class Ipqualityscore < Base def self.key_method_mappings { 'request_id' => :request_id, 'success' => :success?, 'message' => :message, 'city' => :city, 'region' => :state, 'country_code' => :country_code, 'mobile' => :mobile?, 'fraud_score' => :fraud_score, 'ISP' => :isp, 'ASN' => :asn, 'organization' => :organization, 'is_crawler' => :crawler?, 'host' => :host, 'proxy' => :proxy?, 'vpn' => :vpn?, 'tor' => :tor?, 'active_vpn' => :active_vpn?, 'active_tor' => :active_tor?, 'recent_abuse' => :recent_abuse?, 'bot_status' => :bot?, 'connection_type' => :connection_type, 'abuse_velocity' => :abuse_velocity, 'timezone' => :timezone, } end key_method_mappings.each_pair do |key, meth| define_method meth do @data[key] end end alias_method :state_code, :state alias_method :country, :country_code def postal_code '' # No suitable fallback end def address [city, state, country_code].compact.reject(&:empty?).join(', ') end end end end geocoder-1.8.6/lib/geocoder/results/melissa_street.rb0000644000004100000410000000136015066776054022773 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class MelissaStreet < Base def address(format = :full) @data['FormattedAddress'] end def street_address @data['AddressLine1'] end def suffix @data['ThoroughfareTrailingType'] end def number @data['PremisesNumber'] end def city @data['Locality'] end def state_code @data['AdministrativeArea'] end alias_method :state, :state_code def country @data['CountryName'] end def country_code @data['CountryISO3166_1_Alpha2'] end def postal_code @data['PostalCode'] end def coordinates [@data['Latitude'].to_f, @data['Longitude'].to_f] end end end geocoder-1.8.6/lib/geocoder/results/uk_ordnance_survey_names.rb0000644000004100000410000000177515066776054025052 0ustar www-datawww-datarequire 'geocoder/results/base' require 'easting_northing' module Geocoder::Result class UkOrdnanceSurveyNames < Base def coordinates @coordinates ||= Geocoder::EastingNorthing.new( easting: data['GEOMETRY_X'], northing: data['GEOMETRY_Y'], ).lat_lng end def city is_postcode? ? data['DISTRICT_BOROUGH'] : data['NAME1'] end def county data['COUNTY_UNITARY'] end alias state county def county_code code_from_uri data['COUNTY_UNITARY_URI'] end alias state_code county_code def province data['REGION'] end def province_code code_from_uri data['REGION_URI'] end def postal_code is_postcode? ? data['NAME1'] : '' end def country 'United Kingdom' end def country_code 'UK' end private def is_postcode? data['LOCAL_TYPE'] == 'Postcode' end def code_from_uri(uri) return '' if uri.nil? uri.split('/').last end end end geocoder-1.8.6/lib/geocoder/results/location_iq.rb0000644000004100000410000000014615066776054022252 0ustar www-datawww-datarequire 'geocoder/results/nominatim' module Geocoder::Result class LocationIq < Nominatim end endgeocoder-1.8.6/lib/geocoder/results/telize.rb0000644000004100000410000000114215066776054021242 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/abstract_api.rb0000644000004100000410000000477215066776054022416 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder module Result class AbstractApi < Base ## # Geolocation def state @data['region'] end def state_code @data['region_iso_code'] end def city @data["city"] end def city_geoname_id @data["city_geoname_id"] end def region_geoname_id @data["region_geoname_id"] end def postal_code @data["postal_code"] end def country @data["country"] end def country_code @data["country_code"] end def country_geoname_id @data["country_geoname_id"] end def country_is_eu @data["country_is_eu"] end def continent @data["continent"] end def continent_code @data["continent_code"] end def continent_geoname_id @data["continent_geoname_id"] end ## # Security def is_vpn? @data.dig "security", "is_vpn" end ## # Timezone def timezone_name @data.dig "timezone", "name" end def timezone_abbreviation @data.dig "timezone", "abbreviation" end def timezone_gmt_offset @data.dig "timezone", "gmt_offset" end def timezone_current_time @data.dig "timezone", "current_time" end def timezone_is_dst @data.dig "timezone", "is_dst" end ## # Flag def flag_emoji @data.dig "flag", "emoji" end def flag_unicode @data.dig "flag", "unicode" end def flag_png @data.dig "flag", "png" end def flag_svg @data.dig "flag", "svg" end ## # Currency def currency_currency_name @data.dig "currency", "currency_name" end def currency_currency_code @data.dig "currency", "currency_code" end ## # Connection def connection_autonomous_system_number @data.dig "connection", "autonomous_system_number" end def connection_autonomous_system_organization @data.dig "connection", "autonomous_system_organization" end def connection_connection_type @data.dig "connection", "connection_type" end def connection_isp_name @data.dig "connection", "isp_name" end def connection_organization_name @data.dig "connection", "organization_name" end end end endgeocoder-1.8.6/lib/geocoder/results/ipinfo_io_lite.rb0000644000004100000410000000047315066776054022744 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class IpinfoIoLite < Base def self.response_attributes %w(ip asn as_name as_domain country country_code continent continent_code) end response_attributes.each do |a| define_method a do @data[a] end end end end geocoder-1.8.6/lib/geocoder/results/smarty_streets.rb0000644000004100000410000000537015066776054023045 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/baidu.rb0000644000004100000410000000263515066776054021042 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Baidu < Base def coordinates ['lat', 'lng'].map{ |i| @data['location'][i] } 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.8.6/lib/geocoder/results/pickpoint.rb0000644000004100000410000000014515066776054021750 0ustar www-datawww-datarequire 'geocoder/results/nominatim' module Geocoder::Result class Pickpoint < Nominatim end endgeocoder-1.8.6/lib/geocoder/results/pointpin.rb0000644000004100000410000000132415066776054021610 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/azure.rb0000644000004100000410000000234315066776054021100 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Azure < Base def address @data['address']['freeformAddress'] end def building_number @data['address']['buildingNumber'] end def city @data['address']['municipality'] end def coordinates if @data['position'].is_a?(String) # reverse geocoding result @data['position'].split(',').map(&:to_f) elsif @data['position'].is_a?(Hash) # forward geocoding result [@data['position']['lat'], @data['position']['lon']] end end def country @data['address']['country'] end def country_code @data['address']['countryCode'] end def district @data['address']['municipalitySubdivision'] end def postal_code @data['address']['postalCode'] end def province @data['address']['countrySubdivision'] end def state @data['address']['countrySubdivision'] end def state_code @data['address']['countrySubdivisionCode'] end def street_name @data['address']['streetName'] end def street_number @data['address']['streetNumber'] end def viewport @data['viewport'] || {} end end endgeocoder-1.8.6/lib/geocoder/results/geocoder_ca.rb0000644000004100000410000000264015066776054022204 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/ipinfo_io.rb0000644000004100000410000000135515066776054021727 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/esri.rb0000644000004100000410000000306415066776054020715 0ustar www-datawww-datarequire '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 attributes['Region'] end def state_code abbr = attributes['RegionAbbr'] abbr.to_s == "" ? state : abbr end 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.8.6/lib/geocoder/results/mapquest.rb0000644000004100000410000000145315066776054021612 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/maxmind_geoip2.rb0000644000004100000410000000063315066776054022654 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/pdok_nl.rb0000644000004100000410000000157115066776054021402 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class PdokNl < Base def response_attributes @data end def coordinates @data['centroide_ll'][6..-2].split(' ').map(&:to_f).reverse end def formatted_address @data['weergavenaam'] end alias_method :address, :formatted_address def province @data['provincienaam'] end alias_method :state, :province def city @data['woonplaatsnaam'] end def district @data['gemeentenaam'] end def street @data['straatnaam'] end def street_number @data['huis_nlt'] end def address_components @data end def state_code @data['provinciecode'] end def postal_code @data['postcode'] end def country "Netherlands" end def country_code "NL" end end end geocoder-1.8.6/lib/geocoder/results/google.rb0000644000004100000410000000625115066776054021230 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/osmnames.rb0000644000004100000410000000171415066776054021575 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Osmnames < Base def address @data['display_name'] end def coordinates [@data['lat'].to_f, @data['lon'].to_f] end def viewport west, south, east, north = @data['boundingbox'].map(&:to_f) [south, west, north, east] end def state @data['state'] end alias_method :state_code, :state def place_class @data['class'] end def place_type @data['type'] end def postal_code '' end def country_code @data['country_code'] end def country @data['country'] end def self.response_attributes %w[house_number street city name osm_id osm_type boundingbox place_rank importance county rank name_suffix] end response_attributes.each do |a| unless method_defined?(a) define_method a do @data[a] end end end end end geocoder-1.8.6/lib/geocoder/results/tencent.rb0000644000004100000410000000333115066776054021410 0ustar www-datawww-datarequire '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 endgeocoder-1.8.6/lib/geocoder/results/pelias.rb0000644000004100000410000000157515066776054021235 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/pc_miler.rb0000644000004100000410000000435215066776054021546 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class PcMiler < Base # sample response: # https://singlesearch.alk.com/na/api/search?authToken=&include=Meta&query=Feasterville # # { # "Err": 0, # "ErrString": "OK", # "QueryConfidence": 1, # "TimeInMilliseconds": 93, # "GridDataVersion": "GRD_ALK.NA.2023.01.18.29.1.1", # "CommitID": "pcmws-22.08.11.0-1778-g586da49bd1b: 05/30/2023 20:14", # "Locations": [ # { # "Address": { # "StreetAddress": "", # "LocalArea": "", # "City": "Feasterville", # "State": "PA", # "StateName": "Pennsylvania", # "Zip": "19053", # "County": "Bucks", # "Country": "US", # "CountryFullName": "United States", # "SPLC": null # }, # "Coords": { # "Lat": "40.150025", # "Lon": "-75.002511" # }, # "StreetCoords": { # "Lat": "40.150098", # "Lon": "-75.002827" # }, # "Region": 4, # "POITypeID": 0, # "PersistentPOIID": -1, # "SiteID": -1, # "ResultType": 4, # "ShortString": "Feasterville", # "GridID": 37172748, # "LinkID": 188, # "Percent": 6291, # "TimeZone": "GMT-4:00 EDT" # } # ] # } def address(format=:unused) [street, city, state, postal_code, country] .map { |i| i == '' ? nil : i } .compact .join(', ') end def coordinates coords = data["Coords"] || {} [coords["Lat"].to_f, coords["Lon"].to_f] end def street address_data["StreetAddress"] end def city address_data["City"] end def state address_data["StateName"] end def state_code address_data["State"] end def postal_code address_data["Zip"] end def country address_data["CountryFullName"] end def country_code address_data["Country"] end private def address_data data["Address"] || {} end end end geocoder-1.8.6/lib/geocoder/results/mapbox.rb0000644000004100000410000000306115066776054021236 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Mapbox < Base def coordinates data['geometry']['coordinates'].reverse.map(&:to_f) end def place_name data['place_name'] end def street data['properties']['address'] end def city data_part('place') || context_part('place') end def state data_part('region') || context_part('region') end def state_code if id_matches_name?(data['id'], 'region') value = data['properties']['short_code'] else value = context_part('region', 'short_code') end value.split('-').last unless value.nil? end def postal_code data_part('postcode') || context_part('postcode') end def country data_part('country') || context_part('country') end def country_code if id_matches_name?(data['id'], 'country') value = data['properties']['short_code'] else value = context_part('country', 'short_code') end value.upcase unless value.nil? end def neighborhood data_part('neighborhood') || context_part('neighborhood') end def address data['place_name'] end private def id_matches_name?(id, name) id =~ Regexp.new(name) end def data_part(name) data['text'] if id_matches_name?(data['id'], name) end def context_part(name, key = 'text') (context.detect { |c| id_matches_name?(c['id'], name) } || {})[key] end def context Array(data['context']) end end end geocoder-1.8.6/lib/geocoder/results/db_ip_com.rb0000644000004100000410000000170015066776054021661 0ustar www-datawww-datarequire '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['stateProvCode'] 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.8.6/lib/geocoder/results/base.rb0000644000004100000410000000307415066776054020666 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/results/ipdata_co.rb0000644000004100000410000000115715066776054021677 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/geoportail_lu.rb0000644000004100000410000000232115066776054022613 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/latlon.rb0000644000004100000410000000227315066776054021245 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/ip2location_io.rb0000644000004100000410000000074215066776054022665 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Ip2locationIo < Base def address(format = :full) "#{city_name} #{zip_code}, #{country_name}".sub(/^[ ,]*/, '') end def self.response_attributes %w[ip country_code country_name region_name city_name latitude longitude zip_code time_zone asn as is_proxy] end response_attributes.each do |attr| define_method attr do @data[attr] || "" end end end end geocoder-1.8.6/lib/geocoder/results/yandex.rb0000644000004100000410000001744715066776054021255 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Yandex < Base # Yandex result has difficult tree structure, # and presence of some nodes depends on exact search case. # Also Yandex lacks documentation about it. # See https://tech.yandex.com/maps/doc/geocoder/desc/concepts/response_structure-docpage/ # Ultimatly, we need to find Locality and/or Thoroughfare data. # It may resides on the top (ADDRESS_DETAILS) level. # example: 'Baltic Sea' # "AddressDetails": { # "Locality": { # "Premise": { # "PremiseName": "Baltic Sea" # } # } # } ADDRESS_DETAILS = %w[ GeoObject metaDataProperty GeocoderMetaData AddressDetails ].freeze # On COUNTRY_LEVEL. # example: 'Potomak' # "AddressDetails": { # "Country": { # "AddressLine": "reka Potomak", # "CountryNameCode": "US", # "CountryName": "United States of America", # "Locality": { # "Premise": { # "PremiseName": "reka Potomak" # } # } # } # } COUNTRY_LEVEL = %w[ GeoObject metaDataProperty GeocoderMetaData AddressDetails Country ].freeze # On ADMIN_LEVEL (usually state or city) # example: 'Moscow, Tverskaya' # "AddressDetails": { # "Country": { # "AddressLine": "Moscow, Tverskaya Street", # "CountryNameCode": "RU", # "CountryName": "Russia", # "AdministrativeArea": { # "AdministrativeAreaName": "Moscow", # "Locality": { # "LocalityName": "Moscow", # "Thoroughfare": { # "ThoroughfareName": "Tverskaya Street" # } # } # } # } # } ADMIN_LEVEL = %w[ GeoObject metaDataProperty GeocoderMetaData AddressDetails Country AdministrativeArea ].freeze # On SUBADMIN_LEVEL (may refer to urban district) # example: 'Moscow Region, Krasnogorsk' # "AddressDetails": { # "Country": { # "AddressLine": "Moscow Region, Krasnogorsk", # "CountryNameCode": "RU", # "CountryName": "Russia", # "AdministrativeArea": { # "AdministrativeAreaName": "Moscow Region", # "SubAdministrativeArea": { # "SubAdministrativeAreaName": "gorodskoy okrug Krasnogorsk", # "Locality": { # "LocalityName": "Krasnogorsk" # } # } # } # } # } SUBADMIN_LEVEL = %w[ GeoObject metaDataProperty GeocoderMetaData AddressDetails Country AdministrativeArea SubAdministrativeArea ].freeze # On DEPENDENT_LOCALITY_1 (may refer to district of city) # example: 'Paris, Etienne Marcel' # "AddressDetails": { # "Country": { # "AddressLine": "Île-de-France, Paris, 1er Arrondissement, Rue Étienne Marcel", # "CountryNameCode": "FR", # "CountryName": "France", # "AdministrativeArea": { # "AdministrativeAreaName": "Île-de-France", # "Locality": { # "LocalityName": "Paris", # "DependentLocality": { # "DependentLocalityName": "1er Arrondissement", # "Thoroughfare": { # "ThoroughfareName": "Rue Étienne Marcel" # } # } # } # } # } # } DEPENDENT_LOCALITY_1 = %w[ GeoObject metaDataProperty GeocoderMetaData AddressDetails Country AdministrativeArea Locality DependentLocality ].freeze # On DEPENDENT_LOCALITY_2 (for special cases like turkish "mahalle") # https://en.wikipedia.org/wiki/Mahalle # example: 'Istanbul Mabeyinci Yokuşu 17' # "AddressDetails": { # "Country": { # "AddressLine": "İstanbul, Fatih, Saraç İshak Mah., Mabeyinci Yokuşu, 17", # "CountryNameCode": "TR", # "CountryName": "Turkey", # "AdministrativeArea": { # "AdministrativeAreaName": "İstanbul", # "SubAdministrativeArea": { # "SubAdministrativeAreaName": "Fatih", # "Locality": { # "DependentLocality": { # "DependentLocalityName": "Saraç İshak Mah.", # "Thoroughfare": { # "ThoroughfareName": "Mabeyinci Yokuşu", # "Premise": { # "PremiseNumber": "17" # } # } # } # } # } # } # } # } DEPENDENT_LOCALITY_2 = %w[ GeoObject metaDataProperty GeocoderMetaData AddressDetails Country AdministrativeArea SubAdministrativeArea Locality DependentLocality ].freeze def coordinates @data['GeoObject']['Point']['pos'].split(' ').reverse.map(&:to_f) end def address(_format = :full) @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['text'] end def city result = if state.empty? find_in_hash(@data, *COUNTRY_LEVEL, 'Locality', 'LocalityName') elsif sub_state.empty? find_in_hash(@data, *ADMIN_LEVEL, 'Locality', 'LocalityName') else find_in_hash(@data, *SUBADMIN_LEVEL, 'Locality', 'LocalityName') end result || "" end def country find_in_hash(@data, *COUNTRY_LEVEL, 'CountryName') || "" end def country_code find_in_hash(@data, *COUNTRY_LEVEL, 'CountryNameCode') || "" end def state find_in_hash(@data, *ADMIN_LEVEL, 'AdministrativeAreaName') || "" end def sub_state return "" if state.empty? find_in_hash(@data, *SUBADMIN_LEVEL, 'SubAdministrativeAreaName') || "" end def state_code "" end def street thoroughfare_data.is_a?(Hash) ? thoroughfare_data['ThoroughfareName'] : "" end def street_number premise.is_a?(Hash) ? premise.fetch('PremiseNumber', "") : "" end def premise_name premise.is_a?(Hash) ? premise.fetch('PremiseName', "") : "" end def postal_code return "" unless premise.is_a?(Hash) find_in_hash(premise, 'PostalCode', 'PostalCodeNumber') || "" 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 top_level_locality find_in_hash(@data, *ADDRESS_DETAILS, 'Locality') end def country_level_locality find_in_hash(@data, *COUNTRY_LEVEL, 'Locality') end def admin_locality find_in_hash(@data, *ADMIN_LEVEL, 'Locality') end def subadmin_locality find_in_hash(@data, *SUBADMIN_LEVEL, 'Locality') end def dependent_locality find_in_hash(@data, *DEPENDENT_LOCALITY_1) || find_in_hash(@data, *DEPENDENT_LOCALITY_2) end def locality_data dependent_locality || subadmin_locality || admin_locality || country_level_locality || top_level_locality end def thoroughfare_data locality_data['Thoroughfare'] if locality_data.is_a?(Hash) end def premise if thoroughfare_data.is_a?(Hash) thoroughfare_data['Premise'] elsif locality_data.is_a?(Hash) locality_data['Premise'] end end def find_in_hash(source, *keys) key = keys.shift result = source[key] if keys.empty? return result elsif !result.is_a?(Hash) return nil end find_in_hash(result, *keys) end end end geocoder-1.8.6/lib/geocoder/results/ip2location_lite.rb0000644000004100000410000000157415066776054023217 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Ip2locationLite < Base def coordinates [@data[:latitude], @data[:longitude]] end def city @data[:city] end def state @data[:region] end def state_code "" # Not available in Maxmind's database end def country @data[:country_long] end def country_code @data[:country_short] end def postal_code @data[:zipcode] end def self.response_attributes %w[country_short country_long region latitude longitude isp domain netspeed areacode iddcode timezone zipcode weatherstationname weatherstationcode mcc mnc mobilebrand elevation usagetype addresstype category district asn as] end response_attributes.each do |a| define_method a do @data[a] || "" end end end endgeocoder-1.8.6/lib/geocoder/results/amap.rb0000644000004100000410000000335415066776054020673 0ustar www-datawww-datarequire '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 endgeocoder-1.8.6/lib/geocoder/results/test.rb0000644000004100000410000000142615066776054020732 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/geocodio.rb0000644000004100000410000000270615066776054021545 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/google_places_details.rb0000644000004100000410000000113115066776054024254 0ustar www-datawww-datarequire "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.8.6/lib/geocoder/results/nominatim.rb0000644000004100000410000000406415066776054021747 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Nominatim < Base def poi return address_data[place_type] if address_data.key?(place_type) return nil end def house_number address_data['house_number'] end def address @data['display_name'] end def street %w[road pedestrian highway].each do |key| return address_data[key] if address_data.key?(key) end return nil end def city %w[city town village hamlet].each do |key| return address_data[key] if address_data.key?(key) end return nil end def village address_data['village'] end def town address_data['town'] end def state address_data['state'] end alias_method :state_code, :state def postal_code address_data['postcode'] end def county address_data['county'] end def country address_data['country'] end def country_code address_data['country_code'] end def suburb address_data['suburb'] end def city_district address_data['city_district'] end def state_district address_data['state_district'] end def neighbourhood address_data['neighbourhood'] end def municipality address_data['municipality'] end def coordinates return [] unless @data['lat'] && @data['lon'] [@data['lat'].to_f, @data['lon'].to_f] end 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 private def address_data @data['address'] || {} end end end geocoder-1.8.6/lib/geocoder/results/nationaal_georegister_nl.rb0000644000004100000410000000161115066776054025005 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class NationaalGeoregisterNl < Base def response_attributes @data end def coordinates @data['centroide_ll'][6..-2].split(' ').map(&:to_f).reverse end def formatted_address @data['weergavenaam'] end alias_method :address, :formatted_address def province @data['provincienaam'] end alias_method :state, :province def city @data['woonplaatsnaam'] end def district @data['gemeentenaam'] end def street @data['straatnaam'] end def street_number @data['huis_nlt'] end def address_components @data end def state_code @data['provinciecode'] end def postal_code @data['postcode'] end def country "Netherlands" end def country_code "NL" end end end geocoder-1.8.6/lib/geocoder/results/ipgeolocation.rb0000644000004100000410000000221215066776054022601 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Ipgeolocation < Base def coordinates [@data['latitude'].to_f, @data['longitude'].to_f] end def address(format = :full) "#{city}, #{state} #{postal_code}, #{country_name}".sub(/^[ ,]*/, "") end def state @data['state_prov'] end def state_code @data['state_prov'] end def country @data['country_name'] end def country_code @data['country_code2'] end def postal_code @data['zipcode'] end def self.response_attributes [ ['ip', ''], ['hostname', ''], ['continent_code', ''], ['continent_name', ''], ['country_code2', ''], ['country_code3', ''], ['country_name', ''], ['country_capital',''], ['district',''], ['state_prov',''], ['city', ''], ['zipcode', ''], ['time_zone', {}], ['currency', {}] ] end response_attributes.each do |attr, default| define_method attr do @data[attr] || default end end end end geocoder-1.8.6/lib/geocoder/results/google_premier.rb0000644000004100000410000000014315066776054022745 0ustar www-datawww-datarequire 'geocoder/results/google' module Geocoder::Result class GooglePremier < Google end endgeocoder-1.8.6/lib/geocoder/results/ip2location.rb0000644000004100000410000000112115066776054022166 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/postcodes_io.rb0000644000004100000410000000111315066776054022436 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/ipapi_com.rb0000644000004100000410000000134315066776054021711 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/opencagedata.rb0000644000004100000410000000407015066776054022364 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/baidu_ip.rb0000644000004100000410000000145315066776054021527 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/maxmind.rb0000644000004100000410000000521415066776054021407 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/twogis.rb0000644000004100000410000000324415066776054021267 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Twogis < Base def coordinates ['lat', 'lon'].map{ |i| @data['point'][i] } if @data['point'] end def address(_format = :full) @data['full_address_name'] || '' end def city return '' unless @data['adm_div'] @data['adm_div'].select{|u| u["type"] == "city"}.first.try(:[], 'name') || '' end def region return '' unless @data['adm_div'] @data['adm_div'].select{|u| u["type"] == "region"}.first.try(:[], 'name') || '' end def country return '' unless @data['adm_div'] @data['adm_div'].select{|u| u["type"] == "country"}.first.try(:[], 'name') || '' end def district return '' unless @data['adm_div'] @data['adm_div'].select{|u| u["type"] == "district"}.first.try(:[], 'name') || '' end def district_area return '' unless @data['adm_div'] @data['adm_div'].select{|u| u["type"] == "district_area"}.first.try(:[], 'name') || '' end def street_address @data['address_name'] || '' end def street return '' unless @data['address_name'] @data['address_name'].split(', ').first end def street_number return '' unless @data['address_name'] @data['address_name'].split(', ')[1] || '' end def type @data['type'] || '' end def purpose_name @data['purpose_name'] || '' end def building_name @data['building_name'] || '' end def subtype @data['subtype'] || '' end def subtype_specification @data['subtype_specification'] || '' end def name @data['name'] || '' end end end geocoder-1.8.6/lib/geocoder/results/photon.rb0000644000004100000410000000377415066776054021272 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Photon < Base def name properties['name'] end def address(_format = :full) parts = [] parts << name if name parts << street_address if street_address parts << city parts << state if state parts << postal_code parts << country parts.join(', ') end def street_address return unless street return street unless house_number "#{house_number} #{street}" end def house_number properties['housenumber'] end def street properties['street'] end def postal_code properties['postcode'] end def city properties['city'] end def state properties['state'] end def state_code '' end def country properties['country'] end def country_code '' end def coordinates return unless geometry return unless geometry[:coordinates] geometry[:coordinates].reverse end def geometry return unless data['geometry'] symbol_hash data['geometry'] end def bounds properties['extent'] end # Type of the result (OSM object type), one of: # # :node # :way # :relation # def type { 'N' => :node, 'W' => :way, 'R' => :relation }[properties['osm_type']] end def osm_id properties['osm_id'] end # See: https://wiki.openstreetmap.org/wiki/Tags def osm_tag return unless properties['osm_key'] return properties['osm_key'] unless properties['osm_value'] "#{properties['osm_key']}=#{properties['osm_value']}" end private def properties @properties ||= data['properties'] || {} end def symbol_hash(orig_hash) {}.tap do |result| orig_hash.each_key do |key| next unless orig_hash[key] result[key.to_sym] = orig_hash[key] end end end end end geocoder-1.8.6/lib/geocoder/results/ipbase.rb0000644000004100000410000000133515066776054021215 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Ipbase < Base def ip @data["data"]['ip'] end def country_code @data["data"]["location"]["country"]["alpha2"] end def country @data["data"]["location"]["country"]["name"] end def state_code @data["data"]["location"]["region"]["alpha2"] end def state @data["data"]["location"]["region"]["name"] end def city @data["data"]["location"]["city"]["name"] end def postal_code @data["data"]["location"]["zip"] end def coordinates [ @data["data"]["location"]["latitude"].to_f, @data["data"]["location"]["longitude"].to_f ] end end end geocoder-1.8.6/lib/geocoder/results/google_places_search.rb0000644000004100000410000000115115066776054024076 0ustar www-datawww-datarequire "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.8.6/lib/geocoder/results/geoip2.rb0000644000004100000410000000263015066776054021136 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/freegeoip.rb0000644000004100000410000000111215066776054021710 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/ban_data_gouv_fr.rb0000644000004100000410000001620515066776054023234 0ustar www-datawww-data# encoding: utf-8 require 'geocoder/results/base' module Geocoder::Result class BanDataGouvFr < Base STATE_CODE_MAPPINGS = { "Guadeloupe" => "01", "Martinique" => "02", "Guyane" => "03", "La Réunion" => "04", "Mayotte" => "06", "Île-de-France" => "11", "Centre-Val de Loire" => "24", "Bourgogne-Franche-Comté" => "27", "Normandie" => "28", "Hauts-de-France" => "32", "Grand Est" => "44", "Pays de la Loire" => "52", "Bretagne" => "53", "Nouvelle-Aquitaine" => "75", "Occitanie" => "76", "Auvergne-Rhône-Alpes" => "84", "Provence-Alpes-Côte d'Azur" => "93", "Corse" => "94" }.freeze #### BASE METHODS #### def self.response_attributes %w[limit attribution version licence type features center] 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 region_code STATE_CODE_MAPPINGS[region_name] 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, :region_code #### 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) result_type == 'municipality' end end end geocoder-1.8.6/lib/geocoder/results/postcode_anywhere_uk.rb0000644000004100000410000000152315066776054024172 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/ipregistry.rb0000644000004100000410000001273415066776054022160 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class Ipregistry < Base def initialize(data) super @data = flatten_hash(data) end def coordinates [@data['location_latitude'], @data['location_longitude']] end def flatten_hash(hash) hash.each_with_object({}) do |(k, v), h| if v.is_a? Hash flatten_hash(v).map do |h_k, h_v| h["#{k}_#{h_k}".to_s] = h_v end else h[k] = v end end end private :flatten_hash def city @data['location_city'] end def country @data['location_country_name'] end def country_code @data['location_country_code'] end def postal_code @data['location_postal'] end def state @data['location_region_name'] end def state_code @data['location_region_code'] end # methods for fields specific to Ipregistry def ip @data["ip"] end def type @data["type"] end def hostname @data["hostname"] end def carrier_name @data["carrier_name"] end def carrier_mcc @data["carrier_mcc"] end def carrier_mnc @data["carrier_mnc"] end def connection_asn @data["connection_asn"] end def connection_domain @data["connection_domain"] end def connection_organization @data["connection_organization"] end def connection_type @data["connection_type"] end def currency_code @data["currency_code"] end def currency_name @data["currency_name"] end def currency_plural @data["currency_plural"] end def currency_symbol @data["currency_symbol"] end def currency_symbol_native @data["currency_symbol_native"] end def currency_format_negative_prefix @data["currency_format_negative_prefix"] end def currency_format_negative_suffix @data["currency_format_negative_suffix"] end def currency_format_positive_prefix @data["currency_format_positive_prefix"] end def currency_format_positive_suffix @data["currency_format_positive_suffix"] end def location_continent_code @data["location_continent_code"] end def location_continent_name @data["location_continent_name"] end def location_country_area @data["location_country_area"] end def location_country_borders @data["location_country_borders"] end def location_country_calling_code @data["location_country_calling_code"] end def location_country_capital @data["location_country_capital"] end def location_country_code @data["location_country_code"] end def location_country_name @data["location_country_name"] end def location_country_population @data["location_country_population"] end def location_country_population_density @data["location_country_population_density"] end def location_country_flag_emoji @data["location_country_flag_emoji"] end def location_country_flag_emoji_unicode @data["location_country_flag_emoji_unicode"] end def location_country_flag_emojitwo @data["location_country_flag_emojitwo"] end def location_country_flag_noto @data["location_country_flag_noto"] end def location_country_flag_twemoji @data["location_country_flag_twemoji"] end def location_country_flag_wikimedia @data["location_country_flag_wikimedia"] end def location_country_languages @data["location_country_languages"] end def location_country_tld @data["location_country_tld"] end def location_region_code @data["location_region_code"] end def location_region_name @data["location_region_name"] end def location_city @data["location_city"] end def location_postal @data["location_postal"] end def location_latitude @data["location_latitude"] end def location_longitude @data["location_longitude"] end def location_language_code @data["location_language_code"] end def location_language_name @data["location_language_name"] end def location_language_native @data["location_language_native"] end def location_in_eu @data["location_in_eu"] end def security_is_bogon @data["security_is_bogon"] end def security_is_cloud_provider @data["security_is_cloud_provider"] end def security_is_tor @data["security_is_tor"] end def security_is_tor_exit @data["security_is_tor_exit"] end def security_is_proxy @data["security_is_proxy"] end def security_is_anonymous @data["security_is_anonymous"] end def security_is_abuser @data["security_is_abuser"] end def security_is_attacker @data["security_is_attacker"] end def security_is_threat @data["security_is_threat"] end def time_zone_id @data["time_zone_id"] end def time_zone_abbreviation @data["time_zone_abbreviation"] end def time_zone_current_time @data["time_zone_current_time"] end def time_zone_name @data["time_zone_name"] end def time_zone_offset @data["time_zone_offset"] end def time_zone_in_daylight_saving @data["time_zone_in_daylight_saving"] end end end geocoder-1.8.6/lib/geocoder/results/amazon_location_service.rb0000644000004100000410000000157715066776054024657 0ustar www-datawww-datarequire 'geocoder/results/base' module Geocoder::Result class AmazonLocationService < Base def initialize(result) @place = result.place super end def coordinates [@place.geometry.point[1], @place.geometry.point[0]] end def address @place.label end def neighborhood @place.neighborhood end def route @place.street end def city @place.municipality || @place.sub_region end def state @place.region end def state_code @place.region end def province @place.region end def province_code @place.region end def postal_code @place.postal_code end def country @place.country end def country_code @place.country end def place_id data.place_id if data.respond_to?(:place_id) end end end geocoder-1.8.6/lib/geocoder/results/geoapify.rb0000644000004100000410000000753615066776054021566 0ustar www-datawww-data# frozen_string_literal: true require 'geocoder/results/base' module Geocoder module Result # https://apidocs.geoapify.com/docs/geocoding/api class Geoapify < Base def address(_format = :full) properties['formatted'] end def address_line1 properties['address_line1'] end def address_line2 properties['address_line2'] end def house_number properties['housenumber'] end def street properties['street'] end def postal_code properties['postcode'] end def district properties['district'] end def suburb properties['suburb'] end def city properties['city'] end def county properties['county'] end def state properties['state'] end # Not currently available in the API def state_code '' end def country properties['country'] end def country_code return unless properties['country_code'] properties['country_code'].upcase end def coordinates return unless properties['lat'] return unless properties['lon'] [properties['lat'], properties['lon']] end # See: https://tools.ietf.org/html/rfc7946#section-3.1 # # Each feature has a "Point" type in the Geoapify API. def geometry return unless data['geometry'] symbol_hash data['geometry'] end # See: https://tools.ietf.org/html/rfc7946#section-5 def bounds data['bbox'] end # Type of the result, one of: # # * :unknown # * :amenity # * :building # * :street # * :suburb # * :district # * :postcode # * :city # * :county # * :state # * :country # def type return :unknown unless properties['result_type'] properties['result_type'].to_sym end # Distance in meters to given bias:proximity or to given coordinates for # reverse geocoding def distance properties['distance'] end # Calculated rank for the result, containing the following keys: # # * `popularity` - The popularity score of the result # * `confidence` - The confidence value of the result (0-1) # * `match_type` - The result's match type, one of following: # * full_match # * inner_part # * match_by_building # * match_by_street # * match_by_postcode # * match_by_city_or_disrict # * match_by_country_or_state # # Example: # { # popularity: 8.615793062435909, # confidence: 0.88, # match_type: :full_match # } def rank return unless properties['rank'] r = symbol_hash(properties['rank']) r[:match_type] = r[:match_type].to_sym if r[:match_type] r end # Examples: # # Open # { # sourcename: 'openstreetmap', # wheelchair: 'limited', # wikidata: 'Q186125', # wikipedia: 'en:Madison Square Garden', # website: 'http://www.thegarden.com/', # phone: '12124656741', # osm_type: 'W', # osm_id: 138141251, # continent: 'North America', # } def datasource return unless properties['datasource'] symbol_hash properties['datasource'] end private def properties @properties ||= data['properties'] || {} end def symbol_hash(orig_hash) {}.tap do |result| orig_hash.each_key do |key| next unless orig_hash[key] result[key.to_sym] = orig_hash[key] end end end end end end geocoder-1.8.6/lib/geocoder/results/maxmind_local.rb0000644000004100000410000000121715066776054022560 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/ipstack.rb0000644000004100000410000000244315066776054021411 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/results/here.rb0000644000004100000410000000251315066776054020674 0ustar www-datawww-datarequire '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["position"] [d["lat"].to_f, d["lng"].to_f] end def route address_data["street"] end def street_number address_data["houseNumber"] end def state address_data["state"] 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["stateCode"] end def province_code address_data["state"] end def country address_data["countryName"] end def country_code address_data["countryCode"] end def viewport return [] if data["resultType"] == "place" map_view = data["mapView"] south = map_view["south"] west = map_view["west"] north = map_view["north"] east = map_view["east"] [south, west, north, east] end private # ---------------------------------------------------------------- def address_data @data["address"] || fail end end end geocoder-1.8.6/lib/geocoder/results/bing.rb0000644000004100000410000000153115066776054020667 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/models/0000755000004100000410000000000015066776054017205 5ustar www-datawww-datageocoder-1.8.6/lib/geocoder/models/mongoid.rb0000644000004100000410000000155515066776054021174 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/models/active_record.rb0000644000004100000410000000313315066776054022343 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/models/base.rb0000644000004100000410000000151515066776054020446 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/models/mongo_base.rb0000644000004100000410000000354315066776054021650 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/models/mongo_mapper.rb0000644000004100000410000000124215066776054022214 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/calculations.rb0000644000004100000410000003306115066776054020733 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/cli.rb0000644000004100000410000000756215066776054017030 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/kernel_logger.rb0000644000004100000410000000110715066776054021065 0ustar www-datawww-datamodule 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.8.6/lib/geocoder/lookups/0000755000004100000410000000000015066776054017416 5ustar www-datawww-datageocoder-1.8.6/lib/geocoder/lookups/ipqualityscore.rb0000644000004100000410000000250515066776054023022 0ustar www-datawww-data# encoding: utf-8 require 'geocoder/lookups/base' require 'geocoder/results/ipqualityscore' module Geocoder::Lookup class Ipqualityscore < Base def name "IPQualityScore" end def required_api_key_parts ['api_key'] end private # --------------------------------------------------------------- def base_query_url(query) "#{protocol}://ipqualityscore.com/api/json/ip/#{configuration.api_key}/#{query.sanitized_text}?" end def valid_response?(response) if (json = parse_json(response.body)) success = json['success'] end super && success == true end def results(query, reverse = false) return [] unless doc = fetch_data(query) return [doc] if doc['success'] case doc['message'] when /invalid (.*) key/i raise_error Geocoder::InvalidApiKey || Geocoder.log(:warn, "#{name} API error: invalid api key.") when /insufficient credits/, /exceeded your request quota/ raise_error Geocoder::OverQueryLimitError || Geocoder.log(:warn, "#{name} API error: query limit exceeded.") when /invalid (.*) address/i raise_error Geocoder::InvalidRequest || Geocoder.log(:warn, "#{name} API error: invalid request.") end [doc] end end end geocoder-1.8.6/lib/geocoder/lookups/melissa_street.rb0000644000004100000410000000176015066776054022772 0ustar www-datawww-datarequire 'geocoder/lookups/base' require "geocoder/results/melissa_street" module Geocoder::Lookup class MelissaStreet < Base def name "MelissaStreet" end def results(query) return [] unless doc = fetch_data(query) if doc["TransmissionResults"] == "GE05" raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Melissa service error: invalid API key.") end return doc["Records"] end private # --------------------------------------------------------------- def base_query_url(query) "#{protocol}://address.melissadata.net/v3/WEB/GlobalAddress/doGlobalAddress?" end def query_url_params(query) params = { id: configuration.api_key, format: "JSON", a1: query.sanitized_text, loc: query.options[:city], admarea: query.options[:state], postal: query.options[:postal], ctry: query.options[:country] } params.merge(super) end end end geocoder-1.8.6/lib/geocoder/lookups/uk_ordnance_survey_names.rb0000644000004100000410000000220515066776054025032 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/uk_ordnance_survey_names' module Geocoder::Lookup class UkOrdnanceSurveyNames < Base def name 'Ordance Survey Names' end def supported_protocols [:https] end def base_query_url(query) "#{protocol}://api.os.uk/search/names/v1/find?" end def required_api_key_parts ["key"] end def query_url(query) base_query_url(query) + url_query_string(query) end private # ------------------------------------------------------------- def results(query) return [] unless doc = fetch_data(query) return [] if doc['header']['totalresults'].zero? return doc['results'].map { |r| r['GAZETTEER_ENTRY'] } end def query_url_params(query) { query: query.sanitized_text, key: configuration.api_key, fq: filter }.merge(super) end def local_types %w[ City Hamlet Other_Settlement Town Village Postcode ] end def filter local_types.map { |t| "local_type:#{t}" }.join(' ') end end end geocoder-1.8.6/lib/geocoder/lookups/location_iq.rb0000644000004100000410000000251315066776054022245 0ustar www-datawww-datarequire '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 def supported_protocols [:https] 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] || "us1.locationiq.com" 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.8.6/lib/geocoder/lookups/telize.rb0000644000004100000410000000325015066776054021237 0ustar www-datawww-datarequire '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.rapidapi.com/location/#{query.sanitized_text}?rapidapi-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.8.6/lib/geocoder/lookups/abstract_api.rb0000644000004100000410000000155415066776054022404 0ustar www-datawww-data# encoding: utf-8 require 'geocoder/lookups/base' require 'geocoder/results/abstract_api' module Geocoder::Lookup class AbstractApi < Base def name "Abstract API" end def required_api_key_parts ['api_key'] end def supported_protocols [:https] end private # --------------------------------------------------------------- def base_query_url(query) "#{protocol}://ipgeolocation.abstractapi.com/v1/?" end def query_url_params(query) params = {api_key: configuration.api_key} ip_address = query.sanitized_text if ip_address.is_a?(String) && ip_address.length > 0 params[:ip_address] = ip_address end params.merge(super) end def results(query, reverse = false) if doc = fetch_data(query) [doc] else [] end end end end geocoder-1.8.6/lib/geocoder/lookups/ipinfo_io_lite.rb0000644000004100000410000000163515066776054022740 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/ipinfo_io_lite' module Geocoder::Lookup class IpinfoIoLite < Base def name 'Ipinfo.io Lite' end private # --------------------------------------------------------------- def base_query_url(query) url = "#{protocol}://api.ipinfo.io/lite/#{query.sanitized_text}" 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.8.6/lib/geocoder/lookups/smarty_streets.rb0000644000004100000410000000336015066776054023035 0ustar www-datawww-datarequire '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) doc = fetch_data(query) || [] if doc.is_a?(Hash) and doc.key?('status') # implies there's an error return [] else return doc end end end end geocoder-1.8.6/lib/geocoder/lookups/baidu.rb0000644000004100000410000000333415066776054021032 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/pickpoint.rb0000644000004100000410000000155715066776054021753 0ustar www-datawww-datarequire "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) { 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.8.6/lib/geocoder/lookups/pointpin.rb0000644000004100000410000000356115066776054021610 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/azure.rb0000644000004100000410000000207615066776054021076 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/azure' module Geocoder::Lookup class Azure < Base def name 'Azure' end def required_api_key_parts ['api_key'] end def supported_protocols [:https] end private def base_query_url(query) host = 'atlas.microsoft.com/search/address' if query.reverse_geocode? "#{protocol}://#{host}/reverse/json?" else "#{protocol}://#{host}/json?" end end def query_url_params(query) params = { 'api-version' => 1.0, 'language' => query.options[:language] || 'en', 'limit' => configuration[:limit] || 10, 'query' => query.sanitized_text, 'subscription-key' => configuration.api_key } params.merge(super) end def results(query) return [] unless (doc = fetch_data(query)) return doc if doc['error'] if doc['results']&.any? doc['results'] elsif doc['addresses']&.any? doc['addresses'] else [] end end end endgeocoder-1.8.6/lib/geocoder/lookups/geocoder_ca.rb0000644000004100000410000000244515066776054022202 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/ipinfo_io.rb0000644000004100000410000000161415066776054021720 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/esri.rb0000644000004100000410000000643315066776054020713 0ustar www-datawww-datarequire 'geocoder/lookups/base' require "geocoder/results/esri" require 'geocoder/esri_token' module Geocoder::Lookup class Esri < Base def name "Esri" end def supported_protocols [:https] 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 (doc['error'].nil?) if (!query.reverse_geocode?) return [] if !doc['locations'] || doc['locations'].empty? end return [ doc ] else case [ doc['error']['code'] ] when [498] raise_error(Geocoder::InvalidApiKey, doc['error']['message']) || Geocoder.log(:warn, "#{self.name} Geocoding API error: #{doc['error']['message']}") when [ 403 ] raise_error(Geocoder::RequestDenied, 'ESRI request denied') || Geocoder.log(:warn, "#{self.name} ESRI request denied: #{doc['error']['message']}") when [ 500 ], [501] raise_error(Geocoder::ServiceUnavailable, 'ESRI service unavailable') || Geocoder.log(:warn, "#{self.name} ESRI service error: #{doc['error']['message']}") else raise_error(Geocoder::Error, doc['error']['message']) || Geocoder.log(:warn, "#{self.name} Geocoding error: #{doc['error']['message']}") end end return [] 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[:preferredLabelValues] = configuration[:preferred_label_values] if configuration[:preferred_label_values] 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.8.6/lib/geocoder/lookups/mapquest.rb0000644000004100000410000000323315066776054021603 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/maxmind_geoip2.rb0000644000004100000410000000350415066776054022647 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/pdok_nl.rb0000644000004100000410000000154115066776054021372 0ustar www-datawww-datarequire 'geocoder/lookups/base' require "geocoder/results/pdok_nl" module Geocoder::Lookup class PdokNl < Base def name 'pdok NL' end def supported_protocols [:https] end private # --------------------------------------------------------------- def cache_key(query) base_query_url(query) + hash_to_query(query_url_params(query)) end def base_query_url(query) "#{protocol}://api.pdok.nl/bzk/locatieserver/search/v3_1/free?" end def valid_response?(response) json = parse_json(response.body) super(response) if json end def results(query) return [] unless doc = fetch_data(query) return doc['response']['docs'] end def query_url_params(query) { fl: '*', q: query.text, wt: 'json' }.merge(super) end end end geocoder-1.8.6/lib/geocoder/lookups/google.rb0000644000004100000410000000600415066776054021217 0ustar www-datawww-datarequire '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 result_root_attr 'results' end def results(query) return [] unless doc = fetch_data(query) case doc['status'] when "OK" # OK status implies >0 results return doc[result_root_attr] 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.8.6/lib/geocoder/lookups/osmnames.rb0000644000004100000410000000244515066776054021572 0ustar www-datawww-datarequire 'cgi' require 'geocoder/lookups/base' require 'geocoder/results/osmnames' module Geocoder::Lookup class Osmnames < Base def name 'OSM Names' end def required_api_key_parts configuration[:host] ? [] : ['key'] end def supported_protocols [:https] end private def base_query_url(query) "#{base_url(query)}/#{params_url(query)}.js?" end def base_url(query) host = configuration[:host] || 'geocoder.tilehosting.com' "#{protocol}://#{host}" end def params_url(query) method, args = 'q', CGI.escape(query.sanitized_text) method, args = 'r', query.coordinates.join('/') if query.reverse_geocode? "#{country_limited(query)}#{method}/#{args}" end def results(query) return [] unless doc = fetch_data(query) if (error = doc['message']) raise_error(Geocoder::InvalidRequest, error) || Geocoder.log(:warn, "OSMNames Geocoding API error: #{error}") else return doc['results'] end end def query_url_params(query) { key: configuration.api_key }.merge(super) end def country_limited(query) "#{query.options[:country_code].downcase}/" if query.options[:country_code] && !query.reverse_geocode? end end end geocoder-1.8.6/lib/geocoder/lookups/tencent.rb0000644000004100000410000000311415066776054021402 0ustar www-datawww-datarequire '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 311 raise_error(Geocoder::InvalidApiKey, "invalid api key") || Geocoder.log(:warn, "#{name} Geocoding API error: invalid api key.") when 310 raise_error(Geocoder::InvalidRequest, "invalid request.") || Geocoder.log(:warn, "#{name} Geocoding API error: invalid request, invalid parameters.") when 306 raise_error(Geocoder::InvalidRequest, "invalid request.") || Geocoder.log(:warn, "#{name} Geocoding API error: invalid request, check response for more info.") when 110 raise_error(Geocoder::RequestDenied, "request denied.") || Geocoder.log(:warn, "#{name} Geocoding API error: request source is not authorized.") end return [] end 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.8.6/lib/geocoder/lookups/pelias.rb0000644000004100000410000000325215066776054021222 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/pc_miler.rb0000644000004100000410000000446115066776054021542 0ustar www-datawww-datarequire 'geocoder/lookups/base' require "geocoder/results/pc_miler" require 'cgi' unless defined?(CGI) && defined?(CGI.escape) module Geocoder::Lookup class PcMiler < Base # https://developer.trimblemaps.com/restful-apis/location/single-search/single-search-api/#test-the-api-now def valid_region_codes # AF: Africa # AS: Asia # EU: Europe # ME: Middle East # MX: Mexico # NA: North America # OC: Oceania # SA: South America %w[AF AS EU ME MX NA OC SA] end def name "PCMiler" end private # --------------------------------------------------------------- def base_query_url(query) region_code = region(query) if !valid_region_codes.include?(region_code) raise "region_code '#{region_code}' is invalid. use one of #{valid_region_codes}." \ "https://developer.trimblemaps.com/restful-apis/location/single-search/single-search-api/#test-the-api-now" end "#{protocol}://singlesearch.alk.com/#{region_code}/api/search?" end def results(query) return [] unless data = fetch_data(query) if data['Locations'] add_metadata_to_locations!(data) data['Locations'] else [] end end def add_metadata_to_locations!(data) confidence = data['QueryConfidence'] data['Locations'].each do |location| location['QueryConfidence'] = confidence end end def query_url_params(query) if query.reverse_geocode? lat,lon = query.coordinates formatted_query = "#{CGI.escape(lat)},#{CGI.escape(lon)}" else formatted_query = query.text.to_s end { authToken: configuration.api_key, query: formatted_query, # to add additional metadata to response such as QueryConfidence include: 'Meta' }.merge(super(query)) end def region(query) query.options[:region] || query.options['region'] || configuration[:region] || "NA" 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 def supported_protocols [:https] end end end geocoder-1.8.6/lib/geocoder/lookups/mapbox.rb0000644000004100000410000000314215066776054021231 0ustar www-datawww-datarequire '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 'erb' unless defined?(ERB) && defined?(ERB::Util.url_encode) if query.reverse_geocode? lat,lon = query.coordinates "#{ERB::Util.url_encode lon},#{ERB::Util.url_encode lat}" else # truncate at first semicolon so Mapbox doesn't go into batch mode # (see Github issue #1299) ERB::Util.url_encode 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.8.6/lib/geocoder/lookups/db_ip_com.rb0000644000004100000410000000241015066776054021653 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/base.rb0000644000004100000410000002516615066776054020667 0ustar www-datawww-datarequire '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_options = configuration.cache_options @cache = Cache.new(store, cache_options) 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 Geocoder::NetworkError => err raise_error(err) or Geocoder.log(:warn, "Geocoding API connection is either unreacheable or reset by the peer") rescue Timeout::Error => err raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " + "(use Geocoder.configure(:timeout => ...) to set limit).") 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.8.6/lib/geocoder/lookups/ipdata_co.rb0000644000004100000410000000307115066776054021667 0ustar www-datawww-datarequire '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 configuration[: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.8.6/lib/geocoder/lookups/geoportail_lu.rb0000644000004100000410000000277215066776054022620 0ustar www-datawww-datarequire '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) || Geocoder.log(:warn, "Geportail.lu Geocoding API error") end result end end end end geocoder-1.8.6/lib/geocoder/lookups/latlon.rb0000644000004100000410000000256115066776054021240 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/ip2location_io.rb0000644000004100000410000000266015066776054022661 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/ip2location_io' module Geocoder::Lookup class Ip2locationIo < Base def name "IP2LocationIOApi" end def required_api_key_parts ['key'] end def supported_protocols [:http, :https] end private # ---------------------------------------------------------------- def base_query_url(query) "#{protocol}://api.ip2location.io/?" end def query_url_params(query) super.merge( key: configuration.api_key, ip: query.sanitized_text, ) 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) { "ip" => "-", "country_code" => "-", "country_name" => "-", "region_name" => "-", "city_name" => "-", "latitude" => nil, "longitude" => nil, "zip_code" => "-", "time_zone" => "-", "asn" => "-", "as" => "-", "is_proxy" => false } end end end geocoder-1.8.6/lib/geocoder/lookups/yandex.rb0000644000004100000410000000325415066776054021237 0ustar www-datawww-datarequire '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 [400, 403].include? doc['statusCode'] if doc['statusCode'] == 403 and doc['message'] == 'Invalid key' raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid API key.") else Geocoder.log(:warn, "Yandex Geocoding API error: #{doc['statusCode']} (#{doc['message']}).") end return [] end if doc = doc['response']['GeoObjectCollection'] return doc['featureMember'].to_a 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", :lang => "#{query.language || configuration.language}", # supports ru, uk, be, default -> ru :apikey => configuration.api_key } unless (bounds = query.options[:bounds]).nil? params[:bbox] = bounds.map{ |point| "%f,%f" % point }.join('~') end params.merge(super) end end end geocoder-1.8.6/lib/geocoder/lookups/ip2location_lite.rb0000644000004100000410000000165715066776054023214 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/ip2location_lite' module Geocoder module Lookup class Ip2locationLite < Base attr_reader :gem_name def initialize unless configuration[:file].nil? begin @gem_name = 'ip2location_ruby' require @gem_name rescue LoadError raise "Could not load IP2Location DB dependency. To use the IP2LocationLite lookup you must add the #{@gem_name} gem to your Gemfile or have it installed in your system." end end super end def name 'IP2LocationLite' end def required_api_key_parts [] end private def results(query) return [] unless configuration[:file] i2l = Ip2location.new.open(configuration[:file].to_s) result = i2l.get_all(query.to_s) result.nil? ? [] : [result] end end end endgeocoder-1.8.6/lib/geocoder/lookups/amap.rb0000644000004100000410000000321615066776054020663 0ustar www-datawww-datarequire '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") || Geocoder.log(:warn, "#{self.name} Geocoding API error: invalid api key.") else raise_error(Geocoder::Error, "server error.") || Geocoder.log(: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.8.6/lib/geocoder/lookups/test.rb0000644000004100000410000000163315066776054020725 0ustar www-datawww-datarequire '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) @default_stub ||= nil 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.delete_stub(query_text) stubs.delete(query_text) 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.8.6/lib/geocoder/lookups/geocodio.rb0000644000004100000410000000230515066776054021533 0ustar www-datawww-datarequire '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.6/#{path}?" end def query_url_params(query) { :api_key => configuration.api_key, :q => query.sanitized_text }.merge(super) end end end geocoder-1.8.6/lib/geocoder/lookups/google_places_details.rb0000644000004100000410000000244115066776054024254 0ustar www-datawww-datarequire "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 result_root_attr 'result' end def results(query) result = super(query) return [result] unless result.is_a? Array result end def fields(query) if query.options.has_key?(:fields) return format_fields(query.options[:fields]) end if configuration.has_key?(:fields) return format_fields(configuration[:fields]) end nil # use Google Places defaults end def format_fields(*fields) flattened = fields.flatten.compact return if flattened.empty? flattened.join(',') end def query_url_google_params(query) { placeid: query.text, fields: fields(query), language: query.language || configuration.language } end end end end geocoder-1.8.6/lib/geocoder/lookups/nominatim.rb0000644000004100000410000000310315066776054021733 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/nationaal_georegister_nl.rb0000644000004100000410000000153115066776054025001 0ustar www-datawww-datarequire 'geocoder/lookups/base' require "geocoder/results/nationaal_georegister_nl" module Geocoder::Lookup class NationaalGeoregisterNl < Base def name 'Nationaal Georegister Nederland' end private # --------------------------------------------------------------- def cache_key(query) base_query_url(query) + hash_to_query(query_url_params(query)) end def base_query_url(query) "#{protocol}://geodata.nationaalgeoregister.nl/locatieserver/v3/free?" end def valid_response?(response) json = parse_json(response.body) super(response) if json end def results(query) return [] unless doc = fetch_data(query) return doc['response']['docs'] end def query_url_params(query) { fl: '*', q: query.text }.merge(super) end end end geocoder-1.8.6/lib/geocoder/lookups/ipgeolocation.rb0000644000004100000410000000241615066776054022602 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/ipgeolocation' module Geocoder::Lookup class Ipgeolocation < Base ERROR_CODES = { 400 => Geocoder::RequestDenied, # subscription is paused 401 => Geocoder::InvalidApiKey, # missing/invalid API key 403 => Geocoder::InvalidRequest, # invalid IP address 404 => Geocoder::InvalidRequest, # not found 423 => Geocoder::InvalidRequest # bogon/reserved IP address } ERROR_CODES.default = Geocoder::Error def name "Ipgeolocation" end def supported_protocols [:https] end private # ---------------------------------------------------------------- def base_query_url(query) "#{protocol}://api.ipgeolocation.io/ipgeo?" end def query_url_params(query) { ip: query.sanitized_text, apiKey: configuration.api_key }.merge(super) end def results(query) # don't look up a loopback or private address, just return the stored result return [reserved_result(query.text)] if query.internal_ip_address? [fetch_data(query)] end def reserved_result(ip) { "ip" => ip, "country_name" => "Reserved", "country_code2" => "RD" } end end end geocoder-1.8.6/lib/geocoder/lookups/google_premier.rb0000644000004100000410000000312215066776054022740 0ustar www-datawww-datarequire '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 result_root_attr 'results' end def cache_key(query) "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + hash_to_query(cache_key_params(query)) end 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.8.6/lib/geocoder/lookups/ip2location.rb0000644000004100000410000000430115066776054022164 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/ip2location' module Geocoder::Lookup class Ip2location < Base def name "IP2LocationApi" end def required_api_key_parts ['key'] end def supported_protocols [:http, :https] end private # ---------------------------------------------------------------- def base_query_url(query) "#{protocol}://api.ip2location.com/v2/?" end def query_url_params(query) super.merge( key: configuration.api_key, ip: query.sanitized_text, package: configuration[:package], ) 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 end end geocoder-1.8.6/lib/geocoder/lookups/postcodes_io.rb0000644000004100000410000000122715066776054022437 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/ipapi_com.rb0000644000004100000410000000374415066776054021713 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/opencagedata.rb0000644000004100000410000000360015066776054022355 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/baidu_ip.rb0000644000004100000410000000105315066776054021516 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/maxmind.rb0000644000004100000410000000450015066776054021377 0ustar www-datawww-datarequire '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.8.6/lib/geocoder/lookups/twogis.rb0000644000004100000410000000267215066776054021266 0ustar www-datawww-datarequire 'geocoder/lookups/base' require "geocoder/results/twogis" module Geocoder::Lookup class Twogis < Base def name "2gis" end def required_api_key_parts ["key"] end def map_link_url(coordinates) "https://2gis.ru/?m=#{coordinates.join(',')}" end def supported_protocols [:https] end private # --------------------------------------------------------------- def base_query_url(query) "#{protocol}://catalog.api.2gis.com/3.0/items/geocode?" end def results(query) return [] unless doc = fetch_data(query) if doc['meta'] && doc['meta']['error'] Geocoder.log(:warn, "2gis Geocoding API error: #{doc['meta']["code"]} (#{doc['meta']['error']["message"]}).") return [] end if doc['result'] && doc = doc['result']['items'] return doc.to_a else Geocoder.log(:warn, "2gis Geocoding API error: unexpected response format.") return [] end end def query_url_params(query) if query.reverse_geocode? q = query.coordinates.reverse.join(",") else q = query.sanitized_text end params = { :q => q, :lang => "#{query.language || configuration.language}", :key => configuration.api_key, :fields => 'items.street,items.adm_div,items.full_address_name,items.point,items.geometry.centroid' } params.merge(super) end end end geocoder-1.8.6/lib/geocoder/lookups/photon.rb0000644000004100000410000000431315066776054021253 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/photon' module Geocoder::Lookup class Photon < Base def name 'Photon' end private # --------------------------------------------------------------- def supported_protocols [:http, :https] end def base_query_url(query) host = configuration[:host] || 'photon.komoot.io' method = query.reverse_geocode? ? 'reverse' : 'api' "#{protocol}://#{host}/#{method}?" end def results(query) return [] unless (doc = fetch_data(query)) return [] unless doc['type'] == 'FeatureCollection' return [] unless doc['features'] || doc['features'].present? doc['features'] end def query_url_params(query) lang = query.language || configuration.language params = { lang: lang, limit: query.options[:limit] } if query.reverse_geocode? params.merge!(query_url_params_reverse(query)) else params.merge!(query_url_params_coordinates(query)) end params.merge!(super) end def query_url_params_coordinates(query) params = { q: query.sanitized_text } if (bias = query.options[:bias]) params.merge!(lat: bias[:latitude], lon: bias[:longitude], location_bias_scale: bias[:scale]) end if (filter = query_url_params_coordinates_filter(query)) params.merge!(filter) end params end def query_url_params_coordinates_filter(query) filter = query.options[:filter] return unless filter bbox = filter[:bbox] { bbox: bbox.is_a?(Array) ? bbox.join(',') : bbox, osm_tag: filter[:osm_tag] } end def query_url_params_reverse(query) params = { lat: query.coordinates[0], lon: query.coordinates[1], radius: query.options[:radius] } if (dsort = query.options[:distance_sort]) params[:distance_sort] = dsort ? 'true' : 'false' end if (filter = query_url_params_reverse_filter(query)) params.merge!(filter) end params end def query_url_params_reverse_filter(query) filter = query.options[:filter] return unless filter { query_string_filter: filter[:string] } end end end geocoder-1.8.6/lib/geocoder/lookups/ipbase.rb0000644000004100000410000000214015066776054021203 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/ipbase' module Geocoder::Lookup class Ipbase < Base def name "ipbase.com" end def supported_protocols [:https] end private # --------------------------------------------------------------- def base_query_url(query) "https://api.ipbase.com/v2/info?" end def query_url_params(query) { :ip => query.sanitized_text, :apikey => configuration.api_key } end def results(query) # don't look up a loopback or private address, just return the stored result return [reserved_result(query.text)] if query.internal_ip_address? doc = fetch_data(query) || {} doc.fetch("data", {})["location"] ? [doc] : [] end def reserved_result(ip) { "data" => { "ip" => ip, "location" => { "city" => { "name" => "" }, "country" => { "alpha2" => "RD", "name" => "Reserved" }, "region" => { "alpha2" => "", "name" => "" }, "zip" => "" } } } end end end geocoder-1.8.6/lib/geocoder/lookups/google_places_search.rb0000644000004100000410000000332015066776054024071 0ustar www-datawww-datarequire "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 result_root_attr 'candidates' end def base_query_url(query) "#{protocol}://maps.googleapis.com/maps/api/place/findplacefromtext/json?" end def query_url_google_params(query) { input: query.text, inputtype: 'textquery', fields: fields(query), locationbias: locationbias(query), language: query.language || configuration.language } end def fields(query) if query.options.has_key?(:fields) return format_fields(query.options[:fields]) end if configuration.has_key?(:fields) return format_fields(configuration[:fields]) end default_fields end def default_fields basic = %w[business_status formatted_address geometry icon name photos place_id plus_code types] contact = %w[opening_hours] atmosphere = %W[price_level rating user_ratings_total] format_fields(basic, contact, atmosphere) end def format_fields(*fields) flattened = fields.flatten.compact return if flattened.empty? flattened.join(',') end def locationbias(query) if query.options.has_key?(:locationbias) query.options[:locationbias] else configuration[:locationbias] end end end end end geocoder-1.8.6/lib/geocoder/lookups/geoip2.rb0000644000004100000410000000222315066776054021127 0ustar www-datawww-datarequire '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] if @mmdb.respond_to?(:local_ip_alias) && !configuration[:local_ip_alias].nil? @mmdb.local_ip_alias = configuration[:local_ip_alias] end result = @mmdb.lookup(query.to_s) result.nil? ? [] : [result] end end end end geocoder-1.8.6/lib/geocoder/lookups/freegeoip.rb0000644000004100000410000000267215066776054021717 0ustar www-datawww-datarequire 'geocoder/lookups/base' require 'geocoder/results/freegeoip' module Geocoder::Lookup class Freegeoip < Base def name "FreeGeoIP" end def supported_protocols if configuration[:host] [:https] else # use https for default host [:https] end end private # --------------------------------------------------------------- def base_query_url(query) "#{protocol}://#{host}/json/#{query.sanitized_text}?" end def query_url_params(query) { :apikey => configuration.api_key }.merge(super) end def parse_raw_data(raw_data) 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" => "", "metro_code" => "", "zip_code" => "", "latitude" => "0", "longitude" => "0", "country_name" => "Reserved", "country_code" => "RD" } end def host configuration[:host] || "freegeoip.app" end end end ����������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/lookups/ban_data_gouv_fr.rb���������������������������������������������0000644�0000041�0000041�00000010223�15066776054�023221� 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}://data.geopf.fr/geocodage/#{method}/?" end def any_result?(doc) doc['features'] and 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 unless (lat = query.options[:lat]).nil? || !latitude_is_valid?(lat) params[:lat] = lat end unless (lon = query.options[:lon]).nil? || !longitude_is_valid?(lon) params[:lon] = lon end params end #### 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 municipality).include?(param.downcase) end def code_param_is_valid?(param) (1..99999).include?(param.to_i) end def latitude_is_valid?(param) param.to_f <= 90 && param.to_f >= -90 end def longitude_is_valid?(param) param.to_f <= 180 && param.to_f >= -180 end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/lookups/postcode_anywhere_uk.rb�����������������������������������������0000644�0000041�0000041�00000003160�15066776054�024164� 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.8.6/lib/geocoder/lookups/ipregistry.rb���������������������������������������������������0000644�0000041�0000041�00000002723�15066776054�022150� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'geocoder/lookups/base' require 'geocoder/results/ipregistry' module Geocoder::Lookup class Ipregistry < Base ERROR_CODES = { 400 => Geocoder::InvalidRequest, 401 => Geocoder::InvalidRequest, 402 => Geocoder::OverQueryLimitError, 403 => Geocoder::InvalidApiKey, 451 => Geocoder::RequestDenied, 500 => Geocoder::Error } ERROR_CODES.default = Geocoder::Error def name "Ipregistry" end def supported_protocols [:https, :http] end private def base_query_url(query) "#{protocol}://#{host}/#{query.sanitized_text}?" end def cache_key(query) query_url(query) end def host configuration[:host] || "api.ipregistry.co" end def query_url_params(query) { key: configuration.api_key }.merge(super) end def results(query) # don't look up a loopback or private address, just return the stored result return [reserved_result(query.text)] if query.internal_ip_address? return [] unless (doc = fetch_data(query)) if (error = doc['error']) code = error['code'] msg = error['message'] raise_error(ERROR_CODES[code], msg ) || Geocoder.log(:warn, "Ipregistry API error: #{msg}") return [] end [doc] end def reserved_result(ip) { "ip" => ip, "country_name" => "Reserved", "country_code" => "RD" } end end end ���������������������������������������������geocoder-1.8.6/lib/geocoder/lookups/amazon_location_service.rb��������������������������������������0000644�0000041�0000041�00000003126�15066776054�024642� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'geocoder/lookups/base' require 'geocoder/results/amazon_location_service' module Geocoder::Lookup class AmazonLocationService < Base def results(query) params = query.options.dup # index_name is required # Aws::ParamValidator raises ArgumentError on missing required keys params.merge!(index_name: configuration[:index_name]) # Aws::ParamValidator raises ArgumentError on unexpected keys params.delete(:lookup) # Inherit language from configuration params.merge!(language: configuration[:language]) resp = if query.reverse_geocode? client.search_place_index_for_position(params.merge(position: query.coordinates.reverse)) else client.search_place_index_for_text(params.merge(text: query.text)) end resp.results end private def client return @client if @client require_sdk keys = configuration.api_key if keys @client = Aws::LocationService::Client.new(**{ region: keys[:region], access_key_id: keys[:access_key_id], secret_access_key: keys[:secret_access_key] }.compact) else @client = Aws::LocationService::Client.new end end def require_sdk begin require 'aws-sdk-locationservice' rescue LoadError raise_error(Geocoder::ConfigurationError) || Geocoder.log( :error, "Couldn't load the Amazon Location Service SDK. " + "Install it with: gem install aws-sdk-locationservice -v '~> 1.4'" ) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/lookups/geoapify.rb�����������������������������������������������������0000644�0000041�0000041�00000003762�15066776054�021556� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require 'geocoder/lookups/base' require 'geocoder/results/geoapify' module Geocoder module Lookup # https://apidocs.geoapify.com/docs/geocoding/api class Geoapify < Base def name 'Geoapify' end def required_api_key_parts ['api_key'] end def supported_protocols [:https] end private def base_query_url(query) method = if query.reverse_geocode? 'reverse' elsif query.options[:autocomplete] 'autocomplete' else 'search' end "https://api.geoapify.com/v1/geocode/#{method}?" end def results(query) return [] unless (doc = fetch_data(query)) # The rest of the status codes should be already handled by the default # functionality as the API returns correct HTTP response codes in most # cases. There may be some unhandled cases still (such as over query # limit reached) but there is not enough documentation to cover them. case doc['statusCode'] when 500 raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, doc['message']) end return [] unless doc['type'] == 'FeatureCollection' return [] unless doc['features'] || doc['features'].present? doc['features'] end def query_url_params(query) lang = query.language || configuration.language params = { apiKey: configuration.api_key, lang: lang, limit: query.options[:limit] } if query.reverse_geocode? params.merge!(query_url_params_reverse(query)) else params.merge!(query_url_params_coordinates(query)) end params.merge!(super) end def query_url_params_coordinates(query) { text: query.sanitized_text } end def query_url_params_reverse(query) { lat: query.coordinates[0], lon: query.coordinates[1] } end end end end ��������������geocoder-1.8.6/lib/geocoder/lookups/maxmind_local.rb������������������������������������������������0000644�0000041�0000041�00000004313�15066776054�022553� 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 geoip_instance = geoip_class.new(configuration[:file]) result = if configuration[:package] == :country geoip_instance.country(query.to_s) else geoip_instance.city(query.to_s) end result.nil? ? [] : [encode_hash(result.to_hash)] elsif configuration[:package] == :city addr = IPAddr.new(query.text).to_i 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.8.6/lib/geocoder/lookups/ipstack.rb������������������������������������������������������0000644�0000041�0000041�00000003023�15066776054�021377� 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.8.6/lib/geocoder/lookups/here.rb���������������������������������������������������������0000644�0000041�0000041�00000003414�15066776054�020670� 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 ['api_key'] end def supported_protocols [:https] end private # --------------------------------------------------------------- def base_query_url(query) service = query.reverse_geocode? ? "revgeocode" : "geocode" "#{protocol}://#{service}.search.hereapi.com/v1/#{service}?" end def results(query) unless configuration.api_key.is_a?(String) api_key_not_string! return [] end return [] unless doc = fetch_data(query) return [] if doc["items"].nil? doc["items"] end def query_url_here_options(query, reverse_geocode) options = { apiKey: configuration.api_key, lang: (query.language || configuration.language) } return options if reverse_geocode unless (country = query.options[:country]).nil? options[:in] = "countryCode:#{country}" end options end def query_url_params(query) if query.reverse_geocode? super.merge(query_url_here_options(query, true)).merge( at: query.sanitized_text ) else super.merge(query_url_here_options(query, false)).merge( q: query.sanitized_text ) end end def api_key_not_string! msg = <<~MSG API key for HERE Geocoding and Search API should be a string. For more info on how to obtain it, please see https://developer.here.com/documentation/identity-access-management/dev_guide/topics/plat-using-apikeys.html MSG raise_error(Geocoder::ConfigurationError, msg) || Geocoder.log(:warn, msg) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/lookups/bing.rb���������������������������������������������������������0000644�0000041�0000041�00000005314�15066776054�020665� 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 = ERB::Util.url_encode(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, culture: (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.8.6/lib/geocoder/configuration_hash.rb���������������������������������������������������0000644�0000041�0000041�00000000373�15066776054�022124� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Geocoder class ConfigurationHash < Hash def method_missing(meth, *args, &block) has_key?(meth) ? self[meth] : super end def respond_to_missing?(meth, include_private = false) has_key?(meth) || super end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/query.rb����������������������������������������������������������������0000644�0000041�0000041�00000005416�15066776054�017422� 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.to_s.strip 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.8.6/lib/geocoder/version.rb��������������������������������������������������������������0000644�0000041�0000041�00000000050�15066776054�017727� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Geocoder VERSION = "1.8.6" end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/cache_stores/�����������������������������������������������������������0000755�0000041�0000041�00000000000�15066776054�020364� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/cache_stores/base.rb����������������������������������������������������0000644�0000041�0000041�00000001375�15066776054�021631� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Geocoder::CacheStore class Base def initialize(store, options) @store = store @config = options @prefix = config[:prefix] end ## # Array of keys with the currently configured prefix # that have non-nil values. def keys store.keys.select { |k| k.match(/^#{prefix}/) and self[k] } end ## # Array of cached URLs. # def urls keys end protected # ---------------------------------------------------------------- def prefix; @prefix; end def store; @store; end def config; @config; end ## # Cache key for a given URL. # def key_for(url) if url.match(/^#{prefix}/) url else [prefix, url].join end end end end�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/cache_stores/redis.rb���������������������������������������������������0000644�0000041�0000041�00000001221�15066776054�022013� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'geocoder/cache_stores/base' module Geocoder::CacheStore class Redis < Base def initialize(store, options) super @expiration = options[:expiration] end def write(url, value, expire = @expiration) if expire.present? store.set key_for(url), value, ex: expire else store.set key_for(url), value end end def read(url) store.get key_for(url) end def keys store.keys("#{prefix}*") end def remove(key) store.del(key) end private # ---------------------------------------------------------------- def expire; @expiration; end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/cache_stores/generic.rb�������������������������������������������������0000644�0000041�0000041�00000001307�15066776054�022326� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'geocoder/cache_stores/base' module Geocoder::CacheStore class Generic < Base def write(url, value) case when store.respond_to?(:[]=) store[key_for(url)] = value when store.respond_to?(:set) store.set key_for(url), value when store.respond_to?(:write) store.write key_for(url), value end end def read(url) case when store.respond_to?(:[]) store[key_for(url)] when store.respond_to?(:get) store.get key_for(url) when store.respond_to?(:read) store.read key_for(url) end end def keys store.keys end def remove(key) store.delete(key) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder/railtie.rb��������������������������������������������������������������0000644�0000041�0000041�00000001140�15066776054�017674� 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.8.6/lib/geocoder/cache.rb����������������������������������������������������������������0000644�0000041�0000041�00000003325�15066776054�017315� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Dir["#{__dir__}/cache_stores/*.rb"].each {|file| require file } module Geocoder class Cache def initialize(store, config) @class = (Geocoder::CacheStore.const_get("#{store.class}", false) rescue Geocoder::CacheStore::Generic) @store_service = @class.new(store, config) end ## # Read from the Cache. # def [](url) interpret store_service.read(url) rescue => e Geocoder.log(:warn, "Geocoder cache read error: #{e}") end ## # Write to the Cache. # def []=(url, value) store_service.write(url, value) rescue => e Geocoder.log(:warn, "Geocoder cache write error: #{e}") end ## # Delete cache entry for given URL, # or pass <tt>:all</tt> to clear all URLs. # def expire(url) if url == :all if store_service.respond_to?(:keys) urls.each{ |u| expire(u) } else raise(NoMethodError, "The Geocoder cache store must implement `#keys` for `expire(:all)` to work") end else expire_single_url(url) end end private # ---------------------------------------------------------------- def store_service; @store_service; end ## # Array of keys with the currently configured prefix # that have non-nil values. # def keys store_service.keys end ## # Array of cached URLs. # def urls store_service.urls 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) store_service.remove(url) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/lib/geocoder.rb����������������������������������������������������������������������0000644�0000041�0000041�00000002377�15066776054�016260� 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.8.6/LICENSE������������������������������������������������������������������������������0000644�0000041�0000041�00000002045�15066776054�014373� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Copyright (c) 2009-2021 Alex Reisner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "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.8.6/README.md����������������������������������������������������������������������������0000644�0000041�0000041�00000103367�15066776054�014656� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Geocoder ======== **Complete geocoding solution for Ruby.** [![Gem Version](https://badge.fury.io/rb/geocoder.svg)](http://badge.fury.io/rb/geocoder) [![Code Climate](https://codeclimate.com/github/alexreisner/geocoder/badges/gpa.svg)](https://codeclimate.com/github/alexreisner/geocoder) Key features: * Forward and reverse geocoding. * IP address geocoding. * Connects to more than 40 APIs worldwide. * Performance-enhancing features like caching. * Integrates with ActiveRecord and Mongoid. * Basic geospatial queries: search within radius (or rectangle, or ring). Compatibility: * Ruby versions: 2.1+, and JRuby. * Databases: MySQL, PostgreSQL, SQLite, and MongoDB. * Rails: 5.x, 6.x, and 7.x. * Works outside of Rails with the `json` (for MRI) or `json_pure` (for JRuby) gem. Table of Contents ----------------- 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](https://github.com/alexreisner/geocoder/blob/master/CONTRIBUTING.md#reporting-bugs) * [Contributing](https://github.com/alexreisner/geocoder/blob/master/CONTRIBUTING.md#making-changes) See Also: * [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: ```ruby results = Geocoder.search("Paris") results.first.coordinates # => [48.856614, 2.3522219] # latitude and longitude ``` The reverse is possible too. Given coordinates, it finds an address: ```ruby results = Geocoder.search([48.856614, 2.3522219]) results.first.address # => "Hôtel de Ville, 75004 Paris, France" ``` You can also look up the location of an IP address: ```ruby results = Geocoder.search("172.56.21.89") results.first.coordinates # => [30.267153, -97.7430608] results.first.country # => "United States" ``` **The success and accuracy of geocoding depends entirely on the API being used to do these lookups.** Most queries work fairly well with the default configuration, but every application has different needs and every API has its particular strengths and weaknesses. If you need better coverage for your application you'll want to get familiar with the large number of supported APIs, listed in the [API Guide](https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md). 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: ```ruby def address [street, city, state, country].compact.join(', ') end ``` **2.** Your model must have a way to store latitude/longitude coordinates. With ActiveRecord, add two attributes/columns (of type float or decimal) called `latitude` and `longitude`. For MongoDB, use a single field (of type Array) called `coordinates` (i.e., `field :coordinates, type: Array`). (See [Advanced Model Configuration](#advanced-model-configuration) for using different attribute names.) **3.** In your model, tell geocoder where to find the object's address: ```ruby geocoded_by :address ``` This adds a `geocode` method which you can invoke via callback: ```ruby after_validation :geocode ``` Reverse geocoding (given lat/lon coordinates, find an address) is similar: ```ruby reverse_geocoded_by :latitude, :longitude after_validation :reverse_geocode ``` With any geocoded objects, you can do the following: ```ruby obj.distance_to([43.9,-98.6]) # distance from obj to point obj.bearing_to([43.9,-98.6]) # bearing from obj to point obj.bearing_from(obj2) # bearing from obj2 to obj ``` The `bearing_from/to` methods take a single argument which can be: a `[lat,lon]` array, a geocoded object, or a geocodable address (string). The `distance_from/to` methods also take a units argument (`:mi`, `:km`, or `:nm` for nautical miles). See [Distance and Bearing](#distance-and-bearing) below for more info. ### One More Thing for MongoDB! Before you can call `geocoded_by` you'll need to include the necessary module using one of the following: ```ruby include Geocoder::Model::Mongoid include Geocoder::Model::MongoMapper ``` ### Latitude/Longitude Order in MongoDB Everywhere coordinates are passed to methods as two-element arrays, Geocoder expects them to be in the order: `[lat, lon]`. However, as per [the GeoJSON spec](http://geojson.org/geojson-spec.html#positions), MongoDB requires that coordinates be stored longitude-first (`[lon, lat]`), so internally they are stored "backwards." Geocoder's methods attempt to hide this, so calling `obj.to_coordinates` (a method added to the object by Geocoder via `geocoded_by`) returns coordinates in the conventional order: ```ruby obj.to_coordinates # => [37.7941013, -122.3951096] # [lat, lon] ``` whereas calling the object's coordinates attribute directly (`obj.coordinates` by default) returns the internal representation which is probably the reverse of what you want: ```ruby 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: ```ruby extend Geocoder::Model::ActiveRecord ``` Geospatial Database Queries --------------------------- ### For ActiveRecord models: To find objects by location, use the following scopes: ```ruby Venue.near('Omaha, NE, US') # venues within 20 miles of Omaha Venue.near([40.71, -100.23], 50) # venues within 50 miles of a point Venue.near([40.71, -100.23], 50, units: :km) # venues within 50 kilometres of a point Venue.geocoded # venues with coordinates Venue.not_geocoded # venues without coordinates ``` With geocoded objects you can do things like this: ```ruby if obj.geocoded? obj.nearbys(30) # other objects within 30 miles obj.distance_from([40.714,-100.234]) # distance from arbitrary point to object obj.bearing_to("Paris, France") # direction from object to arbitrary point end ``` ### For MongoDB-backed models: 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: ```ruby # returns Geocoder::Result object result = request.location ``` **The `location` method is vulnerable to trivial IP address spoofing via HTTP headers.** If that's a problem for your application, use `safe_location` instead, but be aware that `safe_location` will *not* try to trace a request's originating IP through proxy headers; you will instead get the location of the last proxy the request passed through, if any (excepting any proxies you have explicitly whitelisted in your Rack config). 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: ```sh rails generate geocoder:config ``` Some common options are: ```ruby # 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 Caching section below for details): cache: Redis.new, cache_options: { expiration: 1.day, # Defaults to `nil` prefix: "another_key:" # Defaults to `geocoder:` } ) ``` Please see [`lib/geocoder/configuration.rb`](https://github.com/alexreisner/geocoder/blob/master/lib/geocoder/configuration.rb) for a complete list of configuration options. Additionally, some lookups have their own special configuration options which are directly supported by Geocoder. For example, to specify a value for Google's `bounds` parameter: ```ruby # with Google: Geocoder.search("Middletown", bounds: [[40.6,-77.9], [39.9,-75.9]]) ``` Please see the [source code for each lookup](https://github.com/alexreisner/geocoder/tree/master/lib/geocoder/lookups) to learn about directly supported parameters. Parameters which are not directly supported can be specified using the `:params` option, which appends options to the query string of the geocoding request. For example: ```ruby # 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: ```ruby 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: ```ruby add_index :table, [:latitude, :longitude] ``` In MongoDB, by default, the methods `geocoded_by` and `reverse_geocoded_by` create a geospatial index. You can avoid index creation with the `:skip_index option`, for example: ```ruby 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: ```ruby after_validation :geocode, if: ->(obj){ obj.address.present? and obj.address_changed? } ``` ### Caching When relying on any external service, it's always a good idea to cache retrieved data. When implemented correctly, it improves your app's response time and stability. It's easy to cache geocoding results with Geocoder -- just configure a cache store: ```ruby 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). When using Rails use the Generic cache store as an adapter around `Rails.cache`: ```ruby Geocoder.configure(cache: Geocoder::CacheStore::Generic.new(Rails.cache, {})) ``` You can also set a custom prefix to be used for cache keys: ```ruby Geocoder.configure(cache_options: { prefix: "..." }) ``` By default the prefix is `geocoder:` If you need to expire cached content: ```ruby Geocoder::Lookup.get(Geocoder.config[:lookup]).cache.expire(:all) # expire cached results for current Lookup Geocoder::Lookup.get(:nominatim).cache.expire("http://...") # expire cached result for a specific URL Geocoder::Lookup.get(:nominatim).cache.expire(:all) # expire cached results for Nominatim # 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. In addition to conventional cache stores like Redis, it's possible to keep your cache in the database using `ActiveRecord`. For example see [this gist](https://gist.github.com/shqear93/4b07153b4ca7e4e4a41da492679f6c0e). _Before you implement caching in your app please be sure that doing so does not violate the Terms of Service for your geocoding service._ Not all services support caching, [check the service limitations in the API guide for more information](https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md). 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: ```ruby geocoded_by :address, latitude: :lat, longitude: :lon # ActiveRecord geocoded_by :address, coordinates: :coords # MongoDB ``` For reverse geocoding, you can specify the attribute where the address will be stored. For example: ```ruby reverse_geocoded_by :latitude, :longitude, address: :loc # ActiveRecord reverse_geocoded_by :coordinates, address: :street_address # MongoDB ``` To specify geocoding parameters in your model: ```ruby geocoded_by :address, params: {region: "..."} ``` Supported parameters: `:lookup`, `:ip_lookup`, `:language`, and `:params`. You can specify an anonymous function if you want to set these on a per-request basis. For example, to use different lookups for objects in different regions: ```ruby geocoded_by :address, lookup: lambda{ |obj| obj.geocoder_lookup } def geocoder_lookup if country_code == "RU" :yandex elsif country_code == "CN" :baidu else :nominatim end end ``` ### 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: ```ruby reverse_geocoded_by :latitude, :longitude do |obj,results| if geo = results.first obj.city = geo.city obj.zipcode = geo.postal_code obj.country = geo.country_code end end after_validation :reverse_geocode ``` Every `Geocoder::Result` object, `result`, provides the following data: * `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: ```ruby class Venue # build an address from street, city, and state attributes geocoded_by :address_from_components # store the fetched address in the full_address attribute reverse_geocoded_by :latitude, :longitude, address: :full_address end ``` 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: ```ruby 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: ```ruby Venue.near("Austin, TX", 200, min_radius: 40) ``` To search within a rectangle (note that results will *not* include `distance` and `bearing` attributes): ```ruby sw_corner = [40.71, 100.23] ne_corner = [36.12, 88.65] Venue.within_bounding_box(sw_corner, ne_corner) ``` To search for objects near a certain point where each object has a different distance requirement (which is defined in the database), you can pass a column name for the radius: ```ruby Venue.near([40.71, 99.23], :effective_radius) ``` If you store multiple sets of coordinates for each object, you can specify latitude and longitude columns to use for a search: ```ruby 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: ```ruby Geocoder::Calculations.compass_point(355) # => "N" Geocoder::Calculations.compass_point(45) # => "NE" Geocoder::Calculations.compass_point(208) # => "SW" ``` _Note: when running queries on SQLite, `distance` and `bearing` are provided for consistency only. They are not very accurate._ 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: ```ruby # find the distance between two arbitrary points Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655]) => 3619.77359999382 # in configured units (default miles) # find the geographic center (aka center of gravity) of objects or points Geocoder::Calculations.geographic_center([city1, city2, [40.22,-73.99], city4]) => [35.14968, -90.048929] ``` 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: ```sh rake geocode:all CLASS=YourModel ``` If you need reverse geocoding instead, call the task with REVERSE=true: ```sh rake geocode:all CLASS=YourModel REVERSE=true ``` In either case, it won't try to geocode objects that are already geocoded. The task will print warnings if you exceed the rate limit for your geocoding service. Some services enforce a per-second limit in addition to a per-day limit. To avoid exceeding the per-second limit, you can add a `SLEEP` option to pause between requests for a given amount of time. You can also load objects in batches to save memory, for example: ```sh rake geocode:all CLASS=YourModel SLEEP=0.25 BATCH=100 ``` To avoid exceeding per-day limits you can add a `LIMIT` option. However, this will ignore the `BATCH` value, if provided. ```sh 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` ```ruby Geocoder.configure(lookup: :test, ip_lookup: :test) ``` Add stubs to define the results that will be returned: ```ruby Geocoder::Lookup::Test.add_stub( "New York, NY", [ { 'coordinates' => [40.7143528, -74.0059731], 'address' => 'New York, NY, USA', 'state' => 'New York', 'state_code' => 'NY', 'country' => 'United States', 'country_code' => 'US' } ] ) ``` With the above stub defined, any query for "New York, NY" will return the results array that follows. You can also set a default stub, to be returned when no other stub matches a given query: ```ruby Geocoder::Lookup::Test.set_default_stub( [ { 'coordinates' => [40.7143528, -74.0059731], 'address' => 'New York, NY, USA', 'state' => 'New York', 'state_code' => 'NY', 'country' => 'United States', 'country_code' => 'US' } ] ) ``` You may also delete a single stub, or reset all stubs _including the default stub_: ```ruby Geocoder::Lookup::Test.delete_stub('New York, NY') Geocoder::Lookup::Test.reset ``` Notes: - Keys must be strings (not symbols) when calling `add_stub` or `set_default_stub`. For example `'country' =>` not `:country =>`. - 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: ```ruby Geocoder.configure(always_raise: [SocketError, Timeout::Error]) ``` You can also do this to raise all exceptions: ```ruby Geocoder.configure(always_raise: :all) ``` The raise-able exceptions are: ```ruby SocketError Timeout::Error Geocoder::OverQueryLimitError Geocoder::RequestDenied Geocoder::InvalidRequest Geocoder::InvalidApiKey Geocoder::ServiceUnavailable ``` Note that only a few of the above exceptions are raised by any given lookup, so there's no guarantee if you configure Geocoder to raise `ServiceUnavailable` that it will actually be raised under those conditions (because most APIs don't return 503 when they should; you may get a `Timeout::Error` instead). Please see the source code for your particular lookup for details. 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: ```sh $ geocode 29.951,-90.081 Latitude: 29.952211 Longitude: -90.080563 Full address: 1500 Sugar Bowl Dr, New Orleans, LA 70112, USA City: New Orleans State/province: Louisiana Postal code: 70112 Country: United States Map: http://maps.google.com/maps?q=29.952211,-90.080563 ``` There are also a number of options for setting the geocoding API, key, and language, viewing the raw JSON response, and more. Please run `geocode -h` for details. 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: ```ruby uninitialized constant Geocoder::Model::Mongoid uninitialized constant Geocoder::Model::Mongoid::Mongo ``` you should check your Gemfile to make sure the Mongoid gem is listed _before_ Geocoder. If Mongoid isn't loaded when Geocoder is initialized, Geocoder will not load support for Mongoid. ### 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 [`select` method (selects one or more columns)](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-select) * using the [`pluck` method (gets an array with selecting one or more columns)](https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck) * The same problem will appear with [ActiveRecord's `ids` method](https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-ids). * specifying another model through [`includes` (selects columns from other tables)](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes) * See also Known Issues [using-near-with-includes](#using-near-with-select) section. If you get an error in the above cases, try the following: ```ruby # Use the :select option with the near scope to get the columns you want. # Instead of City.near(...).select(:id, :name), try: City.near("Omaha, NE", 20, select: "id, name") # Pass a :select option to the near scope to get the columns you want. # Then, Ruby's built-in pluck method gets arrays you want. # Instead of City.near(...).pluck(:id) or City.near(...).ids,, try: City.near("Omaha, NE", 20, select: "id, name").to_a.pluck(:id, :name) City.near("Omaha, NE", 20, select: "id").to_a.pluck(:id) # 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) ``` ### 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 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: ```ruby Geocoder::Lookup.get(:nominatim).query_url(Geocoder::Query.new("...")) ``` Replace `:nominatim` with the lookup you are using and replace `...` with the address you are trying to geocode. Then visit the returned URL in your web browser. Often the API will return an error message that helps you resolve the problem. If, after reading the raw response, you believe there is a problem with Geocoder, please post an issue and include both the URL and raw response body. You can also fetch the response in the console: ```ruby 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: ```ruby # Pass a :select option to the near scope to get the columns you want. # Instead of City.near(...).includes(:venues), try: City.near("Omaha, NE", 20, select: "cities.*, venues.*").joins(:venues) # This preload call will normally trigger two queries regardless of the # number of results; one query on hotels, and one query on administrators. # Instead of Hotel.near(...).includes(:administrator), try: Hotel.near("London, UK", 50).joins(:administrator).preload(:administrator) ``` 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. Copyright :copyright: 2009-2021 Alex Reisner, released under the MIT license. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/examples/����������������������������������������������������������������������������0000755�0000041�0000041�00000000000�15066776054�015203� 5����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������geocoder-1.8.6/examples/reverse_geocode_job.rb������������������������������������������������������0000644�0000041�0000041�00000001750�15066776054�021525� 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.8.6/examples/cache_bypass.rb�������������������������������������������������������������0000644�0000041�0000041�00000001750�15066776054�020157� 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.8.6/examples/app_defined_lookup_services.rb����������������������������������������������0000644�0000041�0000041�00000001726�15066776054�023270� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# To extend the Geocoder with additional lookups that come from the application, # not shipped with the gem, define a "child" lookup in your application, based on existing one. # This is required because the Geocoder::Configuration is a Singleton and stores one api key per lookup. # in app/libs/geocoder/lookup/my_preciousss.rb module Geocoder::Lookup class MyPreciousss < Google end end # Update Geocoder's street_services on initialize: # config/initializers/geocoder.rb Geocoder::Lookup.street_services << :my_preciousss # Override the configuration when necessary (e.g. provide separate Google API key for the account): Geocoder.configure(my_preciousss: { api_key: 'abcdef' }) # Lastly, search using your custom lookup service/api keys Geocoder.search("Paris", lookup: :my_preciousss) # This is useful when we have groups of users in the application who use Google paid services # and we want to properly separate them and allow using individual API KEYS or timeouts. ������������������������������������������geocoder-1.8.6/CHANGELOG.md�������������������������������������������������������������������������0000644�0000041�0000041�00000071317�15066776054�015207� 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.8.6 (2025 Sep 21) ------------------- * Add support for IPInfo Lite lookup (thanks github.com/iltempo). * Various minor fixes and improvements. 1.8.5 (2024 Dec 18) ------------------- * Fix bug when working with IPAddr objects (thanks github.com/ledermann and github.com/mattlemx). 1.8.4 (2024 Dec 4) ------------------- * Add support for Azure lookup (thanks github.com/AhlOct). * Several fixes for Mapbox and Bing lookups (thanks github.com/tmh-dev and github.com/iBlackShadow). 1.8.3 (2024 May 2) ------------------- * Add support for IP2Location LITE lookup (thanks github.com/ip2location). * Add support for PDOK NL lookup (thanks github.com/opensourceame). * Remove support for Data Science Toolkit lookup (defunct). 1.8.2 (2023 Jul 4) ------------------- * Add support for PC Miler lookup (thanks github.com/alexdean). * Minor fixes for :maxmind_local, :esri, and :ban_data_gouv_fr lookups. 1.8.1 (2022 Sep 23) ------------------- * Add support for IPBase lookup (thanks github.com/jonallured). * Test cleanup (thanks github.com/jonallured). * Prevent errors when existing constant name shadows a lookup class (thanks github.com/avram-twitch). 1.8.0 (2022 May 17) ------------------- * Add support for 2GIS lookup (thanks github.com/ggrikgg). * Change cache configuration structure and add an expiration option. Cache prefix is now set via {cache_options: {prefix: ...}} instead of {cache_prefix: ...}. See README for details. * Add `:fields` parameter for :google_places_details and :google_places_search lookups. If you haven't been requesting specific fields, you may start getting different data (defaults are now the APIs' defaults). See for details: https://github.com/alexreisner/geocoder/pull/1572 (thanks github.com/czlee). * Update :here lookup to use API version 7. Query options are different, API key must be a string (not an array). See API docs at https://developer.here.com/documentation/geocoding-search-api/api-reference-swagger.html (thanks github.com/Pritilender). 1.7.5 (2022 Mar 14) ------------------- * Avoid lookup naming collisions in some environments. 1.7.4 (2022 Mar 14) ------------------- * Add ability to use app-defined lookups (thanks github.com/januszm). * Updates to LocationIQ and FreeGeoIP lookups. 1.7.3 (2022 Jan 17) ------------------- * Get rid of unnecessary cache_prefix deprecation warnings. 1.7.2 (2022 Jan 2) ------------------- * Fix uninitialized constant error (occurring on some systems with v1.7.1). 1.7.1 (2022 Jan 1) ------------------- * Various bugfixes and refactorings. 1.7.0 (2021 Oct 11) ------------------- * Add support for Geoapify and Photo lookups (thanks github.com/ahukkanen). * Add support for IPQualityScore IP lookup (thanks github.com/jamesbebbington). * Add support for Amazon Location Service lookup (thanks github.com/mplewis). * Add support for Melissa lookup (thanks github.com/ALacker). * Drop official support for Ruby 2.0.x and Rails 4.x. 1.6.7 (2021 Apr 17) ------------------- * Add support for Abstract API lookup (thanks github.com/randoum). 1.6.6 (2021 Mar 4) ------------------- * Rescue from exception on cache read/write error. Issue warning instead. 1.6.5 (2021 Feb 10) ------------------- * Fix backward coordinates bug in NationaalregisterNl lookup (thanks github.com/Marthyn). * Allow removal of single stubs in test mode (thanks github.com/jmmastey). * Improve results for :ban_data_gouv_fr lookup (thanks github.com/Intrepidd). 1.6.4 (2020 Oct 6) ------------------- * Various updates in response to geocoding API changes. * Refactor of Google Places Search lookup (thanks github.com/maximilientyc). 1.6.3 (2020 Apr 30) ------------------- * Update URL for :telize lookup (thanks github.com/alexwalling). * Fix bug parsing IPv6 with port (thanks github.com/gdomingu). 1.6.2 (2020 Mar 16) ------------------- * Add support for :nationaal_georegister_nl lookup (thanks github.com/opensourceame). * Add support for :uk_ordnance_survey_names lookup (thanks github.com/pezholio). * Refactor and fix bugs in Yandex lookup (thanks github.com/iarie and stereodenis). 1.6.1 (2020 Jan 23) ------------------- * Sanitize lat/lon values passed to within_bounding_box to prevent SQL injection. 1.6.0 (2020 Jan 6) ------------------- * Drop support for Rails 3.x. * Add support for :osmnames lookup (thanks github.com/zacviandier). * Add support for :ipgeolocation IP lookup (thanks github.com/ahsannawaz111). 1.5.2 (2019 Oct 3) ------------------- * Add support for :ipregistry lookup (thanks github.com/ipregistry). * Various fixes for Yandex lookup. 1.5.1 (2019 Jan 23) ------------------- * Add support for :tencent lookup (thanks github.com/Anders-E). * 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 <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) -------------------- * 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: <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) ------------------- * 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 <tt>Rack::Request#location</tt> 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: <tt>Geocoder.search</tt> now returns a single result instead of an array. * DEPRECATION: <tt>fetch_coordinates!</tt> has been superceded by +geocode+ (then save your object manually). * 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) ------------------ * Include <tt>geocode:all</tt> Rake task in gem (was missing!). * Add <tt>Geocoder.search</tt> 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 <tt>find_near</tt> 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 <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) ------------------ First release. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������