geocoder 1.6.4 → 1.8.2

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/LICENSE +1 -1
  4. data/README.md +329 -235
  5. data/examples/app_defined_lookup_services.rb +22 -0
  6. data/lib/generators/geocoder/config/templates/initializer.rb +6 -1
  7. data/lib/geocoder/cache.rb +16 -33
  8. data/lib/geocoder/cache_stores/base.rb +40 -0
  9. data/lib/geocoder/cache_stores/generic.rb +35 -0
  10. data/lib/geocoder/cache_stores/redis.rb +34 -0
  11. data/lib/geocoder/configuration.rb +17 -4
  12. data/lib/geocoder/ip_address.rb +9 -0
  13. data/lib/geocoder/lookup.rb +33 -4
  14. data/lib/geocoder/lookups/abstract_api.rb +46 -0
  15. data/lib/geocoder/lookups/amap.rb +2 -2
  16. data/lib/geocoder/lookups/amazon_location_service.rb +55 -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 +1 -1
  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 +20 -3
  27. data/lib/geocoder/lookups/here.rb +25 -20
  28. data/lib/geocoder/lookups/ip2location.rb +10 -6
  29. data/lib/geocoder/lookups/ipbase.rb +49 -0
  30. data/lib/geocoder/lookups/ipdata_co.rb +1 -1
  31. data/lib/geocoder/lookups/ipqualityscore.rb +50 -0
  32. data/lib/geocoder/lookups/location_iq.rb +5 -1
  33. data/lib/geocoder/lookups/maxmind_local.rb +7 -1
  34. data/lib/geocoder/lookups/melissa_street.rb +41 -0
  35. data/lib/geocoder/lookups/pc_miler.rb +85 -0
  36. data/lib/geocoder/lookups/photon.rb +89 -0
  37. data/lib/geocoder/lookups/test.rb +5 -0
  38. data/lib/geocoder/lookups/twogis.rb +58 -0
  39. data/lib/geocoder/lookups/uk_ordnance_survey_names.rb +1 -1
  40. data/lib/geocoder/lookups/yandex.rb +3 -3
  41. data/lib/geocoder/results/abstract_api.rb +146 -0
  42. data/lib/geocoder/results/amazon_location_service.rb +57 -0
  43. data/lib/geocoder/results/ban_data_gouv_fr.rb +27 -2
  44. data/lib/geocoder/results/esri.rb +5 -2
  45. data/lib/geocoder/results/geoapify.rb +179 -0
  46. data/lib/geocoder/results/here.rb +20 -25
  47. data/lib/geocoder/results/ipbase.rb +40 -0
  48. data/lib/geocoder/results/ipqualityscore.rb +54 -0
  49. data/lib/geocoder/results/ipregistry.rb +4 -8
  50. data/lib/geocoder/results/mapbox.rb +10 -4
  51. data/lib/geocoder/results/melissa_street.rb +46 -0
  52. data/lib/geocoder/results/nationaal_georegister_nl.rb +1 -1
  53. data/lib/geocoder/results/nominatim.rb +24 -16
  54. data/lib/geocoder/results/pc_miler.rb +98 -0
  55. data/lib/geocoder/results/photon.rb +119 -0
  56. data/lib/geocoder/results/twogis.rb +76 -0
  57. data/lib/geocoder/version.rb +1 -1
  58. data/lib/maxmind_database.rb +8 -8
  59. data/lib/tasks/maxmind.rake +1 -1
  60. metadata +28 -8
  61. data/examples/autoexpire_cache_dalli.rb +0 -62
  62. data/examples/autoexpire_cache_redis.rb +0 -30
@@ -0,0 +1,22 @@
1
+ # To extend the Geocoder with additional lookups that come from the application,
2
+ # not shipped with the gem, define a "child" lookup in your application, based on existing one.
3
+ # This is required because the Geocoder::Configuration is a Singleton and stores one api key per lookup.
4
+
5
+ # in app/libs/geocoder/lookup/my_preciousss.rb
6
+ module Geocoder::Lookup
7
+ class MyPreciousss < Google
8
+ end
9
+ end
10
+
11
+ # Update Geocoder's street_services on initialize:
12
+ # config/initializers/geocoder.rb
13
+ Geocoder::Lookup.street_services << :my_preciousss
14
+
15
+ # Override the configuration when necessary (e.g. provide separate Google API key for the account):
16
+ Geocoder.configure(my_preciousss: { api_key: 'abcdef' })
17
+
18
+ # Lastly, search using your custom lookup service/api keys
19
+ Geocoder.search("Paris", lookup: :my_preciousss)
20
+
21
+ # This is useful when we have groups of users in the application who use Google paid services
22
+ # and we want to properly separate them and allow using individual API KEYS or timeouts.
@@ -9,7 +9,6 @@ Geocoder.configure(
9
9
  # https_proxy: nil, # HTTPS proxy server (user:pass@host:port)
10
10
  # api_key: nil, # API key for geocoding service
11
11
  # cache: nil, # cache object (must respond to #[], #[]=, and #del)
12
- # cache_prefix: 'geocoder:', # prefix (string) to use for all cache keys
13
12
 
14
13
  # Exceptions that should not be rescued by default
15
14
  # (if you want to implement custom error handling);
@@ -19,4 +18,10 @@ Geocoder.configure(
19
18
  # Calculation options
20
19
  # units: :mi, # :km for kilometers or :mi for miles
21
20
  # distances: :linear # :spherical or :linear
21
+
22
+ # Cache configuration
23
+ # cache_options: {
24
+ # expiration: 2.days,
25
+ # prefix: 'geocoder:'
26
+ # }
22
27
  )
@@ -1,37 +1,29 @@
1
+ Dir["#{__dir__}/cache_stores/*.rb"].each {|file| require file }
2
+
1
3
  module Geocoder
2
4
  class Cache
3
5
 
4
- def initialize(store, prefix)
5
- @store = store
6
- @prefix = prefix
6
+ def initialize(store, config)
7
+ @class = (Geocoder::CacheStore.const_get("#{store.class}", false) rescue Geocoder::CacheStore::Generic)
8
+ @store_service = @class.new(store, config)
7
9
  end
8
10
 
9
11
  ##
10
12
  # Read from the Cache.
11
13
  #
12
14
  def [](url)
13
- interpret case
14
- when store.respond_to?(:[])
15
- store[key_for(url)]
16
- when store.respond_to?(:get)
17
- store.get key_for(url)
18
- when store.respond_to?(:read)
19
- store.read key_for(url)
20
- end
15
+ interpret store_service.read(url)
16
+ rescue => e
17
+ Geocoder.log(:warn, "Geocoder cache read error: #{e}")
21
18
  end
22
19
 
23
20
  ##
24
21
  # Write to the Cache.
25
22
  #
26
23
  def []=(url, value)
27
- case
28
- when store.respond_to?(:[]=)
29
- store[key_for(url)] = value
30
- when store.respond_to?(:set)
31
- store.set key_for(url), value
32
- when store.respond_to?(:write)
33
- store.write key_for(url), value
34
- end
24
+ store_service.write(url, value)
25
+ rescue => e
26
+ Geocoder.log(:warn, "Geocoder cache write error: #{e}")
35
27
  end
36
28
 
37
29
  ##
@@ -40,7 +32,7 @@ module Geocoder
40
32
  #
41
33
  def expire(url)
42
34
  if url == :all
43
- if store.respond_to?(:keys)
35
+ if store_service.respond_to?(:keys)
44
36
  urls.each{ |u| expire(u) }
45
37
  else
46
38
  raise(NoMethodError, "The Geocoder cache store must implement `#keys` for `expire(:all)` to work")
@@ -53,29 +45,21 @@ module Geocoder
53
45
 
54
46
  private # ----------------------------------------------------------------
55
47
 
56
- def prefix; @prefix; end
57
- def store; @store; end
58
-
59
- ##
60
- # Cache key for a given URL.
61
- #
62
- def key_for(url)
63
- [prefix, url].join
64
- end
48
+ def store_service; @store_service; end
65
49
 
66
50
  ##
67
51
  # Array of keys with the currently configured prefix
68
52
  # that have non-nil values.
69
53
  #
70
54
  def keys
71
- store.keys.select{ |k| k.match(/^#{prefix}/) and self[k] }
55
+ store_service.keys
72
56
  end
73
57
 
74
58
  ##
75
59
  # Array of cached URLs.
76
60
  #
77
61
  def urls
78
- keys.map{ |k| k[/^#{prefix}(.*)/, 1] }
62
+ store_service.urls
79
63
  end
80
64
 
81
65
  ##
@@ -87,8 +71,7 @@ module Geocoder
87
71
  end
88
72
 
89
73
  def expire_single_url(url)
90
- key = key_for(url)
91
- store.respond_to?(:del) ? store.del(key) : store.delete(key)
74
+ store_service.remove(url)
92
75
  end
93
76
  end
94
77
  end
@@ -0,0 +1,40 @@
1
+ module Geocoder::CacheStore
2
+ class Base
3
+ def initialize(store, options)
4
+ @store = store
5
+ @config = options
6
+ @prefix = config[:prefix]
7
+ end
8
+
9
+ ##
10
+ # Array of keys with the currently configured prefix
11
+ # that have non-nil values.
12
+ def keys
13
+ store.keys.select { |k| k.match(/^#{prefix}/) and self[k] }
14
+ end
15
+
16
+ ##
17
+ # Array of cached URLs.
18
+ #
19
+ def urls
20
+ keys
21
+ end
22
+
23
+ protected # ----------------------------------------------------------------
24
+
25
+ def prefix; @prefix; end
26
+ def store; @store; end
27
+ def config; @config; end
28
+
29
+ ##
30
+ # Cache key for a given URL.
31
+ #
32
+ def key_for(url)
33
+ if url.match(/^#{prefix}/)
34
+ url
35
+ else
36
+ [prefix, url].join
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ require 'geocoder/cache_stores/base'
2
+
3
+ module Geocoder::CacheStore
4
+ class Generic < Base
5
+ def write(url, value)
6
+ case
7
+ when store.respond_to?(:[]=)
8
+ store[key_for(url)] = value
9
+ when store.respond_to?(:set)
10
+ store.set key_for(url), value
11
+ when store.respond_to?(:write)
12
+ store.write key_for(url), value
13
+ end
14
+ end
15
+
16
+ def read(url)
17
+ case
18
+ when store.respond_to?(:[])
19
+ store[key_for(url)]
20
+ when store.respond_to?(:get)
21
+ store.get key_for(url)
22
+ when store.respond_to?(:read)
23
+ store.read key_for(url)
24
+ end
25
+ end
26
+
27
+ def keys
28
+ store.keys
29
+ end
30
+
31
+ def remove(key)
32
+ store.delete(key)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ require 'geocoder/cache_stores/base'
2
+
3
+ module Geocoder::CacheStore
4
+ class Redis < Base
5
+ def initialize(store, options)
6
+ super
7
+ @expiration = options[:expiration]
8
+ end
9
+
10
+ def write(url, value, expire = @expiration)
11
+ if expire.present?
12
+ store.set key_for(url), value, ex: expire
13
+ else
14
+ store.set key_for(url), value
15
+ end
16
+ end
17
+
18
+ def read(url)
19
+ store.get key_for(url)
20
+ end
21
+
22
+ def keys
23
+ store.keys("#{prefix}*")
24
+ end
25
+
26
+ def remove(key)
27
+ store.del(key)
28
+ end
29
+
30
+ private # ----------------------------------------------------------------
31
+
32
+ def expire; @expiration; end
33
+ end
34
+ end
@@ -55,19 +55,20 @@ module Geocoder
55
55
  :lookup,
56
56
  :ip_lookup,
57
57
  :language,
58
+ :host,
58
59
  :http_headers,
59
60
  :use_https,
60
61
  :http_proxy,
61
62
  :https_proxy,
62
63
  :api_key,
63
64
  :cache,
64
- :cache_prefix,
65
65
  :always_raise,
66
66
  :units,
67
67
  :distances,
68
68
  :basic_auth,
69
69
  :logger,
70
- :kernel_logger_level
70
+ :kernel_logger_level,
71
+ :cache_options
71
72
  ]
72
73
 
73
74
  attr_accessor :data
@@ -76,6 +77,10 @@ module Geocoder
76
77
  instance.set_defaults
77
78
  end
78
79
 
80
+ def self.initialize
81
+ instance.send(:initialize)
82
+ end
83
+
79
84
  OPTIONS.each do |o|
80
85
  define_method o do
81
86
  @data[o]
@@ -106,8 +111,6 @@ module Geocoder
106
111
  @data[:http_proxy] = nil # HTTP proxy server (user:pass@host:port)
107
112
  @data[:https_proxy] = nil # HTTPS proxy server (user:pass@host:port)
108
113
  @data[:api_key] = nil # API key for geocoding service
109
- @data[:cache] = nil # cache object (must respond to #[], #[]=, and #keys)
110
- @data[:cache_prefix] = "geocoder:" # prefix (string) to use for all cache keys
111
114
  @data[:basic_auth] = {} # user and password for basic auth ({:user => "user", :password => "password"})
112
115
  @data[:logger] = :kernel # :kernel or Logger instance
113
116
  @data[:kernel_logger_level] = ::Logger::WARN # log level, if kernel logger is used
@@ -120,6 +123,16 @@ module Geocoder
120
123
  # calculation options
121
124
  @data[:units] = :mi # :mi or :km
122
125
  @data[:distances] = :linear # :linear or :spherical
126
+
127
+ # Set the default values for the caching mechanism
128
+ # By default, the cache keys will not expire as IP addresses and phyiscal
129
+ # addresses will rarely change.
130
+ @data[:cache] = nil # cache object (must respond to #[], #[]=, and optionally #keys)
131
+ @data[:cache_prefix] = nil # - DEPRECATED - prefix (string) to use for all cache keys
132
+ @data[:cache_options] = {
133
+ prefix: 'geocoder:',
134
+ expiration: nil
135
+ }
123
136
  end
124
137
 
125
138
  instance_eval(OPTIONS.map do |option|
@@ -7,6 +7,15 @@ module Geocoder
7
7
  '192.168.0.0/16',
8
8
  ].map { |ip| IPAddr.new(ip) }.freeze
9
9
 
10
+ def initialize(ip)
11
+ ip = ip.to_string if ip.is_a?(IPAddr)
12
+ if ip.is_a?(Hash)
13
+ super(**ip)
14
+ else
15
+ super(ip)
16
+ end
17
+ end
18
+
10
19
  def internal?
11
20
  loopback? || private?
12
21
  end
@@ -18,6 +18,14 @@ module Geocoder
18
18
  all_services - [:test]
19
19
  end
20
20
 
21
+ ##
22
+ # Array of valid Lookup service names, excluding any that do not build their own HTTP requests.
23
+ # For example, Amazon Location Service uses the AWS gem, not HTTP REST requests, to fetch data.
24
+ #
25
+ def all_services_with_http_requests
26
+ all_services_except_test - [:amazon_location_service]
27
+ end
28
+
21
29
  ##
22
30
  # All street address lookup services, default first.
23
31
  #
@@ -53,7 +61,13 @@ module Geocoder
53
61
  :test,
54
62
  :latlon,
55
63
  :amap,
56
- :osmnames
64
+ :osmnames,
65
+ :melissa_street,
66
+ :amazon_location_service,
67
+ :geoapify,
68
+ :photon,
69
+ :twogis,
70
+ :pc_miler
57
71
  ]
58
72
  end
59
73
 
@@ -63,6 +77,7 @@ module Geocoder
63
77
  def ip_services
64
78
  @ip_services ||= [
65
79
  :baidu_ip,
80
+ :abstract_api,
66
81
  :freegeoip,
67
82
  :geoip2,
68
83
  :maxmind,
@@ -77,7 +92,9 @@ module Geocoder
77
92
  :db_ip_com,
78
93
  :ipstack,
79
94
  :ip2location,
80
- :ipgeolocation
95
+ :ipgeolocation,
96
+ :ipqualityscore,
97
+ :ipbase
81
98
  ]
82
99
  end
83
100
 
@@ -103,8 +120,7 @@ module Geocoder
103
120
  def spawn(name)
104
121
  if all_services.include?(name)
105
122
  name = name.to_s
106
- require "geocoder/lookups/#{name}"
107
- Geocoder::Lookup.const_get(classify_name(name)).new
123
+ instantiate_lookup(name)
108
124
  else
109
125
  valids = all_services.map(&:inspect).join(", ")
110
126
  raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
@@ -118,5 +134,18 @@ module Geocoder
118
134
  def classify_name(filename)
119
135
  filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
120
136
  end
137
+
138
+ ##
139
+ # Safely instantiate Lookup
140
+ #
141
+ def instantiate_lookup(name)
142
+ class_name = classify_name(name)
143
+ begin
144
+ Geocoder::Lookup.const_get(class_name, inherit=false)
145
+ rescue NameError
146
+ require "geocoder/lookups/#{name}"
147
+ end
148
+ Geocoder::Lookup.const_get(class_name).new
149
+ end
121
150
  end
122
151
  end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'geocoder/lookups/base'
4
+ require 'geocoder/results/abstract_api'
5
+
6
+ module Geocoder::Lookup
7
+ class AbstractApi < Base
8
+
9
+ def name
10
+ "Abstract API"
11
+ end
12
+
13
+ def required_api_key_parts
14
+ ['api_key']
15
+ end
16
+
17
+ def supported_protocols
18
+ [:https]
19
+ end
20
+
21
+ private # ---------------------------------------------------------------
22
+
23
+ def base_query_url(query)
24
+ "#{protocol}://ipgeolocation.abstractapi.com/v1/?"
25
+ end
26
+
27
+ def query_url_params(query)
28
+ params = {api_key: configuration.api_key}
29
+
30
+ ip_address = query.sanitized_text
31
+ if ip_address.is_a?(String) && ip_address.length > 0
32
+ params[:ip_address] = ip_address
33
+ end
34
+
35
+ params.merge(super)
36
+ end
37
+
38
+ def results(query, reverse = false)
39
+ if doc = fetch_data(query)
40
+ [doc]
41
+ else
42
+ []
43
+ end
44
+ end
45
+ end
46
+ end
@@ -32,10 +32,10 @@ module Geocoder::Lookup
32
32
  return doc['geocodes'] unless doc['geocodes'].blank?
33
33
  when ['0', 'INVALID_USER_KEY']
34
34
  raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
35
- warn("#{self.name} Geocoding API error: invalid api key.")
35
+ Geocoder.log(:warn, "#{self.name} Geocoding API error: invalid api key.")
36
36
  else
37
37
  raise_error(Geocoder::Error, "server error.") ||
38
- warn("#{self.name} Geocoding API error: server error - [#{doc['info']}]")
38
+ Geocoder.log(:warn, "#{self.name} Geocoding API error: server error - [#{doc['info']}]")
39
39
  end
40
40
  return []
41
41
  end
@@ -0,0 +1,55 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/amazon_location_service'
3
+
4
+ module Geocoder::Lookup
5
+ class AmazonLocationService < Base
6
+ def results(query)
7
+ params = query.options.dup
8
+
9
+ # index_name is required
10
+ # Aws::ParamValidator raises ArgumentError on missing required keys
11
+ params.merge!(index_name: configuration[:index_name])
12
+
13
+ # Aws::ParamValidator raises ArgumentError on unexpected keys
14
+ params.delete(:lookup)
15
+
16
+ resp = if query.reverse_geocode?
17
+ client.search_place_index_for_position(params.merge(position: query.coordinates.reverse))
18
+ else
19
+ client.search_place_index_for_text(params.merge(text: query.text))
20
+ end
21
+
22
+ resp.results.map(&:place)
23
+ end
24
+
25
+ private
26
+
27
+ def client
28
+ return @client if @client
29
+ require_sdk
30
+ keys = configuration.api_key
31
+ if keys
32
+ @client = Aws::LocationService::Client.new(**{
33
+ region: keys[:region],
34
+ access_key_id: keys[:access_key_id],
35
+ secret_access_key: keys[:secret_access_key]
36
+ }.compact)
37
+ else
38
+ @client = Aws::LocationService::Client.new
39
+ end
40
+ end
41
+
42
+ def require_sdk
43
+ begin
44
+ require 'aws-sdk-locationservice'
45
+ rescue LoadError
46
+ raise_error(Geocoder::ConfigurationError) ||
47
+ Geocoder.log(
48
+ :error,
49
+ "Couldn't load the Amazon Location Service SDK. " +
50
+ "Install it with: gem install aws-sdk-locationservice -v '~> 1.4'"
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
@@ -125,7 +125,7 @@ module Geocoder::Lookup
125
125
  end
126
126
 
127
127
  def type_param_is_valid?(param)
128
- %w(housenumber street locality village town city).include?(param.downcase)
128
+ %w(housenumber street locality municipality).include?(param.downcase)
129
129
  end
130
130
 
131
131
  def code_param_is_valid?(param)
@@ -84,7 +84,8 @@ module Geocoder
84
84
  #
85
85
  def cache
86
86
  if @cache.nil? and store = configuration.cache
87
- @cache = Cache.new(store, configuration.cache_prefix)
87
+ cache_options = configuration.cache_options
88
+ @cache = Cache.new(store, cache_options)
88
89
  end
89
90
  @cache
90
91
  end
@@ -54,7 +54,7 @@ module Geocoder::Lookup
54
54
  def query_url_params(query)
55
55
  {
56
56
  key: configuration.api_key,
57
- language: (query.language || configuration.language)
57
+ culture: (query.language || configuration.language)
58
58
  }.merge(super)
59
59
  end
60
60
 
@@ -23,15 +23,28 @@ module Geocoder::Lookup
23
23
  def results(query)
24
24
  return [] unless doc = fetch_data(query)
25
25
 
26
- if (!query.reverse_geocode?)
27
- return [] if !doc['locations'] || doc['locations'].empty?
28
- end
29
-
30
26
  if (doc['error'].nil?)
27
+ if (!query.reverse_geocode?)
28
+ return [] if !doc['locations'] || doc['locations'].empty?
29
+ end
31
30
  return [ doc ]
32
31
  else
33
- return []
32
+ case [ doc['error']['code'] ]
33
+ when [498]
34
+ raise_error(Geocoder::InvalidApiKey, doc['error']['message']) ||
35
+ Geocoder.log(:warn, "#{self.name} Geocoding API error: #{doc['error']['message']}")
36
+ when [ 403 ]
37
+ raise_error(Geocoder::RequestDenied, 'ESRI request denied') ||
38
+ Geocoder.log(:warn, "#{self.name} ESRI request denied: #{doc['error']['message']}")
39
+ when [ 500 ], [501]
40
+ raise_error(Geocoder::ServiceUnavailable, 'ESRI service unavailable') ||
41
+ Geocoder.log(:warn, "#{self.name} ESRI service error: #{doc['error']['message']}")
42
+ else
43
+ raise_error(Geocoder::Error, doc['error']['message']) ||
44
+ Geocoder.log(:warn, "#{self.name} Geocoding error: #{doc['error']['message']}")
45
+ end
34
46
  end
47
+ return []
35
48
  end
36
49
 
37
50
  def query_url_params(query)
@@ -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