geocoder 1.6.6 → 1.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -2
  3. data/README.md +355 -211
  4. data/examples/app_defined_lookup_services.rb +22 -0
  5. data/lib/generators/geocoder/config/templates/initializer.rb +6 -1
  6. data/lib/geocoder/cache.rb +14 -35
  7. data/lib/geocoder/cache_stores/base.rb +40 -0
  8. data/lib/geocoder/cache_stores/generic.rb +35 -0
  9. data/lib/geocoder/cache_stores/redis.rb +34 -0
  10. data/lib/geocoder/configuration.rb +17 -4
  11. data/lib/geocoder/ip_address.rb +9 -0
  12. data/lib/geocoder/lookup.rb +37 -5
  13. data/lib/geocoder/lookups/abstract_api.rb +46 -0
  14. data/lib/geocoder/lookups/amap.rb +2 -2
  15. data/lib/geocoder/lookups/amazon_location_service.rb +58 -0
  16. data/lib/geocoder/lookups/azure.rb +56 -0
  17. data/lib/geocoder/lookups/ban_data_gouv_fr.rb +1 -1
  18. data/lib/geocoder/lookups/base.rb +2 -1
  19. data/lib/geocoder/lookups/bing.rb +2 -2
  20. data/lib/geocoder/lookups/esri.rb +18 -5
  21. data/lib/geocoder/lookups/freegeoip.rb +8 -6
  22. data/lib/geocoder/lookups/geoapify.rb +78 -0
  23. data/lib/geocoder/lookups/geoip2.rb +4 -0
  24. data/lib/geocoder/lookups/geoportail_lu.rb +1 -1
  25. data/lib/geocoder/lookups/google_places_details.rb +20 -0
  26. data/lib/geocoder/lookups/google_places_search.rb +21 -5
  27. data/lib/geocoder/lookups/here.rb +25 -20
  28. data/lib/geocoder/lookups/ip2location.rb +10 -6
  29. data/lib/geocoder/lookups/ip2location_io.rb +62 -0
  30. data/lib/geocoder/lookups/ip2location_lite.rb +40 -0
  31. data/lib/geocoder/lookups/ipbase.rb +49 -0
  32. data/lib/geocoder/lookups/ipdata_co.rb +1 -1
  33. data/lib/geocoder/lookups/ipqualityscore.rb +50 -0
  34. data/lib/geocoder/lookups/location_iq.rb +5 -1
  35. data/lib/geocoder/lookups/mapbox.rb +3 -3
  36. data/lib/geocoder/lookups/melissa_street.rb +41 -0
  37. data/lib/geocoder/lookups/pc_miler.rb +85 -0
  38. data/lib/geocoder/lookups/pdok_nl.rb +43 -0
  39. data/lib/geocoder/lookups/photon.rb +89 -0
  40. data/lib/geocoder/lookups/test.rb +1 -0
  41. data/lib/geocoder/lookups/twogis.rb +58 -0
  42. data/lib/geocoder/lookups/uk_ordnance_survey_names.rb +1 -1
  43. data/lib/geocoder/lookups/yandex.rb +3 -3
  44. data/lib/geocoder/query.rb +1 -1
  45. data/lib/geocoder/results/abstract_api.rb +146 -0
  46. data/lib/geocoder/results/amazon_location_service.rb +62 -0
  47. data/lib/geocoder/results/azure.rb +65 -0
  48. data/lib/geocoder/results/ban_data_gouv_fr.rb +1 -1
  49. data/lib/geocoder/results/esri.rb +5 -2
  50. data/lib/geocoder/results/geoapify.rb +179 -0
  51. data/lib/geocoder/results/here.rb +20 -25
  52. data/lib/geocoder/results/ip2location_io.rb +21 -0
  53. data/lib/geocoder/results/ip2location_lite.rb +47 -0
  54. data/lib/geocoder/results/ipbase.rb +40 -0
  55. data/lib/geocoder/results/ipqualityscore.rb +54 -0
  56. data/lib/geocoder/results/mapbox.rb +34 -10
  57. data/lib/geocoder/results/melissa_street.rb +46 -0
  58. data/lib/geocoder/results/nominatim.rb +24 -16
  59. data/lib/geocoder/results/pc_miler.rb +98 -0
  60. data/lib/geocoder/results/pdok_nl.rb +62 -0
  61. data/lib/geocoder/results/photon.rb +119 -0
  62. data/lib/geocoder/results/twogis.rb +76 -0
  63. data/lib/geocoder/version.rb +1 -1
  64. data/lib/maxmind_database.rb +12 -12
  65. data/lib/tasks/maxmind.rake +1 -1
  66. metadata +65 -11
  67. data/examples/autoexpire_cache_dalli.rb +0 -62
  68. data/examples/autoexpire_cache_redis.rb +0 -30
  69. data/lib/geocoder/lookups/dstk.rb +0 -22
  70. data/lib/geocoder/results/dstk.rb +0 -6
@@ -17,14 +17,16 @@ module Geocoder::Lookup
17
17
  end
18
18
  end
19
19
 
20
- def query_url(query)
21
- "#{protocol}://#{host}/json/#{query.sanitized_text}"
22
- end
23
-
24
20
  private # ---------------------------------------------------------------
25
21
 
26
- def cache_key(query)
27
- query_url(query)
22
+ def base_query_url(query)
23
+ "#{protocol}://#{host}/json/#{query.sanitized_text}?"
24
+ end
25
+
26
+ def query_url_params(query)
27
+ {
28
+ :apikey => configuration.api_key
29
+ }.merge(super)
28
30
  end
29
31
 
30
32
  def parse_raw_data(raw_data)
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'geocoder/lookups/base'
4
+ require 'geocoder/results/geoapify'
5
+
6
+ module Geocoder
7
+ module Lookup
8
+ # https://apidocs.geoapify.com/docs/geocoding/api
9
+ class Geoapify < Base
10
+ def name
11
+ 'Geoapify'
12
+ end
13
+
14
+ def required_api_key_parts
15
+ ['api_key']
16
+ end
17
+
18
+ def supported_protocols
19
+ [:https]
20
+ end
21
+
22
+ private
23
+
24
+ def base_query_url(query)
25
+ method = if query.reverse_geocode?
26
+ 'reverse'
27
+ elsif query.options[:autocomplete]
28
+ 'autocomplete'
29
+ else
30
+ 'search'
31
+ end
32
+ "https://api.geoapify.com/v1/geocode/#{method}?"
33
+ end
34
+
35
+ def results(query)
36
+ return [] unless (doc = fetch_data(query))
37
+
38
+ # The rest of the status codes should be already handled by the default
39
+ # functionality as the API returns correct HTTP response codes in most
40
+ # cases. There may be some unhandled cases still (such as over query
41
+ # limit reached) but there is not enough documentation to cover them.
42
+ case doc['statusCode']
43
+ when 500
44
+ raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, doc['message'])
45
+ end
46
+
47
+ return [] unless doc['type'] == 'FeatureCollection'
48
+ return [] unless doc['features'] || doc['features'].present?
49
+
50
+ doc['features']
51
+ end
52
+
53
+ def query_url_params(query)
54
+ lang = query.language || configuration.language
55
+ params = { apiKey: configuration.api_key, lang: lang, limit: query.options[:limit] }
56
+
57
+ if query.reverse_geocode?
58
+ params.merge!(query_url_params_reverse(query))
59
+ else
60
+ params.merge!(query_url_params_coordinates(query))
61
+ end
62
+
63
+ params.merge!(super)
64
+ end
65
+
66
+ def query_url_params_coordinates(query)
67
+ { text: query.sanitized_text }
68
+ end
69
+
70
+ def query_url_params_reverse(query)
71
+ {
72
+ lat: query.coordinates[0],
73
+ lon: query.coordinates[1]
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -37,6 +37,10 @@ module Geocoder
37
37
  def results(query)
38
38
  return [] unless configuration[:file]
39
39
 
40
+ if @mmdb.respond_to?(:local_ip_alias) && !configuration[:local_ip_alias].nil?
41
+ @mmdb.local_ip_alias = configuration[:local_ip_alias]
42
+ end
43
+
40
44
  result = @mmdb.lookup(query.to_s)
41
45
  result.nil? ? [] : [result]
42
46
  end
@@ -56,7 +56,7 @@ module Geocoder
56
56
  else
57
57
  result = []
58
58
  raise_error(Geocoder::Error) ||
59
- warn("Geportail.lu Geocoding API error")
59
+ Geocoder.log(:warn, "Geportail.lu Geocoding API error")
60
60
  end
61
61
  result
62
62
  end
@@ -33,9 +33,29 @@ module Geocoder
33
33
  result
34
34
  end
35
35
 
36
+ def fields(query)
37
+ if query.options.has_key?(:fields)
38
+ return format_fields(query.options[:fields])
39
+ end
40
+
41
+ if configuration.has_key?(:fields)
42
+ return format_fields(configuration[:fields])
43
+ end
44
+
45
+ nil # use Google Places defaults
46
+ end
47
+
48
+ def format_fields(*fields)
49
+ flattened = fields.flatten.compact
50
+ return if flattened.empty?
51
+
52
+ flattened.join(',')
53
+ end
54
+
36
55
  def query_url_google_params(query)
37
56
  {
38
57
  placeid: query.text,
58
+ fields: fields(query),
39
59
  language: query.language || configuration.language
40
60
  }
41
61
  end
@@ -31,28 +31,44 @@ module Geocoder
31
31
  input: query.text,
32
32
  inputtype: 'textquery',
33
33
  fields: fields(query),
34
+ locationbias: locationbias(query),
34
35
  language: query.language || configuration.language
35
36
  }
36
37
  end
37
38
 
38
39
  def fields(query)
39
- query_fields = query.options[:fields]
40
- return format_fields(query_fields) if query_fields
40
+ if query.options.has_key?(:fields)
41
+ return format_fields(query.options[:fields])
42
+ end
43
+
44
+ if configuration.has_key?(:fields)
45
+ return format_fields(configuration[:fields])
46
+ end
41
47
 
42
48
  default_fields
43
49
  end
44
50
 
45
51
  def default_fields
46
- legacy = %w[id reference]
47
52
  basic = %w[business_status formatted_address geometry icon name
48
53
  photos place_id plus_code types]
49
54
  contact = %w[opening_hours]
50
55
  atmosphere = %W[price_level rating user_ratings_total]
51
- format_fields(legacy, basic, contact, atmosphere)
56
+ format_fields(basic, contact, atmosphere)
52
57
  end
53
58
 
54
59
  def format_fields(*fields)
55
- fields.flatten.join(',')
60
+ flattened = fields.flatten.compact
61
+ return if flattened.empty?
62
+
63
+ flattened.join(',')
64
+ end
65
+
66
+ def locationbias(query)
67
+ if query.options.has_key?(:locationbias)
68
+ query.options[:locationbias]
69
+ else
70
+ configuration[:locationbias]
71
+ end
56
72
  end
57
73
  end
58
74
  end
@@ -19,50 +19,55 @@ module Geocoder::Lookup
19
19
  private # ---------------------------------------------------------------
20
20
 
21
21
  def base_query_url(query)
22
- "#{protocol}://#{if query.reverse_geocode? then 'reverse.' end}geocoder.ls.hereapi.com/6.2/#{if query.reverse_geocode? then 'reverse' end}geocode.json?"
22
+ service = query.reverse_geocode? ? "revgeocode" : "geocode"
23
+
24
+ "#{protocol}://#{service}.search.hereapi.com/v1/#{service}?"
23
25
  end
24
26
 
25
27
  def results(query)
26
- return [] unless doc = fetch_data(query)
27
- return [] unless doc['Response'] && doc['Response']['View']
28
- if r=doc['Response']['View']
29
- return [] if r.nil? || !r.is_a?(Array) || r.empty?
30
- return r.first['Result']
28
+ unless configuration.api_key.is_a?(String)
29
+ api_key_not_string!
30
+ return []
31
31
  end
32
- []
32
+ return [] unless doc = fetch_data(query)
33
+ return [] if doc["items"].nil?
34
+
35
+ doc["items"]
33
36
  end
34
37
 
35
38
  def query_url_here_options(query, reverse_geocode)
36
39
  options = {
37
- gen: 9,
38
- apikey: configuration.api_key,
39
- language: (query.language || configuration.language)
40
+ apiKey: configuration.api_key,
41
+ lang: (query.language || configuration.language)
40
42
  }
41
- if reverse_geocode
42
- options[:mode] = :retrieveAddresses
43
- return options
44
- end
43
+ return options if reverse_geocode
45
44
 
46
45
  unless (country = query.options[:country]).nil?
47
- options[:country] = country
46
+ options[:in] = "countryCode:#{country}"
48
47
  end
49
48
 
50
- unless (mapview = query.options[:bounds]).nil?
51
- options[:mapview] = mapview.map{ |point| "%f,%f" % point }.join(';')
52
- end
53
49
  options
54
50
  end
55
51
 
56
52
  def query_url_params(query)
57
53
  if query.reverse_geocode?
58
54
  super.merge(query_url_here_options(query, true)).merge(
59
- prox: query.sanitized_text
55
+ at: query.sanitized_text
60
56
  )
61
57
  else
62
58
  super.merge(query_url_here_options(query, false)).merge(
63
- searchtext: query.sanitized_text
59
+ q: query.sanitized_text
64
60
  )
65
61
  end
66
62
  end
63
+
64
+ def api_key_not_string!
65
+ msg = <<~MSG
66
+ API key for HERE Geocoding and Search API should be a string.
67
+ 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
68
+ MSG
69
+
70
+ raise_error(Geocoder::ConfigurationError, msg) || Geocoder.log(:warn, msg)
71
+ end
67
72
  end
68
73
  end
@@ -8,6 +8,10 @@ module Geocoder::Lookup
8
8
  "IP2LocationApi"
9
9
  end
10
10
 
11
+ def required_api_key_parts
12
+ ['key']
13
+ end
14
+
11
15
  def supported_protocols
12
16
  [:http, :https]
13
17
  end
@@ -15,15 +19,15 @@ module Geocoder::Lookup
15
19
  private # ----------------------------------------------------------------
16
20
 
17
21
  def base_query_url(query)
18
- "#{protocol}://api.ip2location.com/?"
22
+ "#{protocol}://api.ip2location.com/v2/?"
19
23
  end
20
24
 
21
25
  def query_url_params(query)
22
- params = super
23
- if configuration.has_key?(:package)
24
- params.merge!(package: configuration[:package])
25
- end
26
- params
26
+ super.merge(
27
+ key: configuration.api_key,
28
+ ip: query.sanitized_text,
29
+ package: configuration[:package],
30
+ )
27
31
  end
28
32
 
29
33
  def results(query)
@@ -0,0 +1,62 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/ip2location_io'
3
+
4
+ module Geocoder::Lookup
5
+ class Ip2locationIo < Base
6
+
7
+ def name
8
+ "IP2LocationIOApi"
9
+ end
10
+
11
+ def required_api_key_parts
12
+ ['key']
13
+ end
14
+
15
+ def supported_protocols
16
+ [:http, :https]
17
+ end
18
+
19
+ private # ----------------------------------------------------------------
20
+
21
+ def base_query_url(query)
22
+ "#{protocol}://api.ip2location.io/?"
23
+ end
24
+
25
+ def query_url_params(query)
26
+ super.merge(
27
+ key: configuration.api_key,
28
+ ip: query.sanitized_text,
29
+ )
30
+ end
31
+
32
+ def results(query)
33
+ # don't look up a loopback or private address, just return the stored result
34
+ return [reserved_result(query.text)] if query.internal_ip_address?
35
+ return [] unless doc = fetch_data(query)
36
+ if doc["response"] == "INVALID ACCOUNT"
37
+ raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "INVALID ACCOUNT")
38
+ return []
39
+ else
40
+ return [doc]
41
+ end
42
+ end
43
+
44
+ def reserved_result(query)
45
+ {
46
+ "ip" => "-",
47
+ "country_code" => "-",
48
+ "country_name" => "-",
49
+ "region_name" => "-",
50
+ "city_name" => "-",
51
+ "latitude" => null,
52
+ "longitude" => null,
53
+ "zip_code" => "-",
54
+ "time_zone" => "-",
55
+ "asn" => "-",
56
+ "as" => "-",
57
+ "is_proxy" => false
58
+ }
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,40 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/ip2location_lite'
3
+
4
+ module Geocoder
5
+ module Lookup
6
+ class Ip2locationLite < Base
7
+ attr_reader :gem_name
8
+
9
+ def initialize
10
+ unless configuration[:file].nil?
11
+ begin
12
+ @gem_name = 'ip2location_ruby'
13
+ require @gem_name
14
+ rescue LoadError
15
+ 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."
16
+ end
17
+ end
18
+ super
19
+ end
20
+
21
+ def name
22
+ 'IP2LocationLite'
23
+ end
24
+
25
+ def required_api_key_parts
26
+ []
27
+ end
28
+
29
+ private
30
+
31
+ def results(query)
32
+ return [] unless configuration[:file]
33
+
34
+ i2l = Ip2location.new.open(configuration[:file].to_s)
35
+ result = i2l.get_all(query.to_s)
36
+ result.nil? ? [] : [result]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/ipbase'
3
+
4
+ module Geocoder::Lookup
5
+ class Ipbase < Base
6
+
7
+ def name
8
+ "ipbase.com"
9
+ end
10
+
11
+ def supported_protocols
12
+ [:https]
13
+ end
14
+
15
+ private # ---------------------------------------------------------------
16
+
17
+ def base_query_url(query)
18
+ "https://api.ipbase.com/v2/info?"
19
+ end
20
+
21
+ def query_url_params(query)
22
+ {
23
+ :ip => query.sanitized_text,
24
+ :apikey => configuration.api_key
25
+ }
26
+ end
27
+
28
+ def results(query)
29
+ # don't look up a loopback or private address, just return the stored result
30
+ return [reserved_result(query.text)] if query.internal_ip_address?
31
+ doc = fetch_data(query) || {}
32
+ doc.fetch("data", {})["location"] ? [doc] : []
33
+ end
34
+
35
+ def reserved_result(ip)
36
+ {
37
+ "data" => {
38
+ "ip" => ip,
39
+ "location" => {
40
+ "city" => { "name" => "" },
41
+ "country" => { "alpha2" => "RD", "name" => "Reserved" },
42
+ "region" => { "alpha2" => "", "name" => "" },
43
+ "zip" => ""
44
+ }
45
+ }
46
+ }
47
+ end
48
+ end
49
+ end
@@ -47,7 +47,7 @@ module Geocoder::Lookup
47
47
  end
48
48
 
49
49
  def host
50
- "api.ipdata.co"
50
+ configuration[:host] || "api.ipdata.co"
51
51
  end
52
52
 
53
53
  def check_response_for_errors!(response)
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'geocoder/lookups/base'
4
+ require 'geocoder/results/ipqualityscore'
5
+
6
+ module Geocoder::Lookup
7
+ class Ipqualityscore < Base
8
+
9
+ def name
10
+ "IPQualityScore"
11
+ end
12
+
13
+ def required_api_key_parts
14
+ ['api_key']
15
+ end
16
+
17
+ private # ---------------------------------------------------------------
18
+
19
+ def base_query_url(query)
20
+ "#{protocol}://ipqualityscore.com/api/json/ip/#{configuration.api_key}/#{query.sanitized_text}?"
21
+ end
22
+
23
+ def valid_response?(response)
24
+ if (json = parse_json(response.body))
25
+ success = json['success']
26
+ end
27
+ super && success == true
28
+ end
29
+
30
+ def results(query, reverse = false)
31
+ return [] unless doc = fetch_data(query)
32
+
33
+ return [doc] if doc['success']
34
+
35
+ case doc['message']
36
+ when /invalid (.*) key/i
37
+ raise_error Geocoder::InvalidApiKey ||
38
+ Geocoder.log(:warn, "#{name} API error: invalid api key.")
39
+ when /insufficient credits/, /exceeded your request quota/
40
+ raise_error Geocoder::OverQueryLimitError ||
41
+ Geocoder.log(:warn, "#{name} API error: query limit exceeded.")
42
+ when /invalid (.*) address/i
43
+ raise_error Geocoder::InvalidRequest ||
44
+ Geocoder.log(:warn, "#{name} API error: invalid request.")
45
+ end
46
+
47
+ [doc]
48
+ end
49
+ end
50
+ end
@@ -11,6 +11,10 @@ module Geocoder::Lookup
11
11
  ["api_key"]
12
12
  end
13
13
 
14
+ def supported_protocols
15
+ [:https]
16
+ end
17
+
14
18
  private # ----------------------------------------------------------------
15
19
 
16
20
  def base_query_url(query)
@@ -25,7 +29,7 @@ module Geocoder::Lookup
25
29
  end
26
30
 
27
31
  def configured_host
28
- configuration[:host] || "locationiq.org"
32
+ configuration[:host] || "us1.locationiq.com"
29
33
  end
30
34
 
31
35
  def results(query)
@@ -30,14 +30,14 @@ module Geocoder::Lookup
30
30
  end
31
31
 
32
32
  def mapbox_search_term(query)
33
- require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
33
+ require 'erb' unless defined?(ERB) && defined?(ERB::Util.url_encode)
34
34
  if query.reverse_geocode?
35
35
  lat,lon = query.coordinates
36
- "#{CGI.escape lon},#{CGI.escape lat}"
36
+ "#{ERB::Util.url_encode lon},#{ERB::Util.url_encode lat}"
37
37
  else
38
38
  # truncate at first semicolon so Mapbox doesn't go into batch mode
39
39
  # (see Github issue #1299)
40
- CGI.escape query.text.to_s.split(';').first.to_s
40
+ ERB::Util.url_encode query.text.to_s.split(';').first.to_s
41
41
  end
42
42
  end
43
43
 
@@ -0,0 +1,41 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/melissa_street"
3
+
4
+ module Geocoder::Lookup
5
+ class MelissaStreet < Base
6
+
7
+ def name
8
+ "MelissaStreet"
9
+ end
10
+
11
+ def results(query)
12
+ return [] unless doc = fetch_data(query)
13
+
14
+ if doc["TransmissionResults"] == "GE05"
15
+ raise_error(Geocoder::InvalidApiKey) ||
16
+ Geocoder.log(:warn, "Melissa service error: invalid API key.")
17
+ end
18
+
19
+ return doc["Records"]
20
+ end
21
+
22
+ private # ---------------------------------------------------------------
23
+
24
+ def base_query_url(query)
25
+ "#{protocol}://address.melissadata.net/v3/WEB/GlobalAddress/doGlobalAddress?"
26
+ end
27
+
28
+ def query_url_params(query)
29
+ params = {
30
+ id: configuration.api_key,
31
+ format: "JSON",
32
+ a1: query.sanitized_text,
33
+ loc: query.options[:city],
34
+ admarea: query.options[:state],
35
+ postal: query.options[:postal],
36
+ ctry: query.options[:country]
37
+ }
38
+ params.merge(super)
39
+ end
40
+ end
41
+ end