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
@@ -0,0 +1,85 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/pc_miler"
3
+ require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
4
+
5
+ module Geocoder::Lookup
6
+ class PcMiler < Base
7
+
8
+ # https://developer.trimblemaps.com/restful-apis/location/single-search/single-search-api/#test-the-api-now
9
+ def valid_region_codes
10
+ # AF: Africa
11
+ # AS: Asia
12
+ # EU: Europe
13
+ # NA: North America
14
+ # OC: Oceania
15
+ # SA: South America
16
+ %w[AF AS EU NA OC SA]
17
+ end
18
+
19
+ def name
20
+ "PCMiler"
21
+ end
22
+
23
+ private # ---------------------------------------------------------------
24
+
25
+ def base_query_url(query)
26
+ region_code = region(query)
27
+ if !valid_region_codes.include?(region_code)
28
+ raise "region_code '#{region_code}' is invalid. use one of #{valid_region_codes}." \
29
+ "https://developer.trimblemaps.com/restful-apis/location/single-search/single-search-api/#test-the-api-now"
30
+ end
31
+
32
+ "#{protocol}://singlesearch.alk.com/#{region_code}/api/search?"
33
+ end
34
+
35
+ def results(query)
36
+ return [] unless data = fetch_data(query)
37
+ if data['Locations']
38
+ add_metadata_to_locations!(data)
39
+ data['Locations']
40
+ else
41
+ []
42
+ end
43
+ end
44
+
45
+ def add_metadata_to_locations!(data)
46
+ confidence = data['QueryConfidence']
47
+ data['Locations'].each do |location|
48
+ location['QueryConfidence'] = confidence
49
+ end
50
+ end
51
+
52
+ def query_url_params(query)
53
+ if query.reverse_geocode?
54
+ lat,lon = query.coordinates
55
+ formatted_query = "#{CGI.escape(lat)},#{CGI.escape(lon)}"
56
+ else
57
+ formatted_query = query.text.to_s
58
+ end
59
+
60
+ {
61
+ authToken: configuration.api_key,
62
+ query: formatted_query,
63
+ # to add additional metadata to response such as QueryConfidence
64
+ include: 'Meta'
65
+ }.merge(super(query))
66
+ end
67
+
68
+ def region(query)
69
+ query.options[:region] || query.options['region'] || configuration[:region] || "NA"
70
+ end
71
+
72
+ def check_response_for_errors!(response)
73
+ if response.code.to_i == 403
74
+ raise_error(Geocoder::RequestDenied) ||
75
+ Geocoder.log(:warn, "Geocoding API error: 403 API key does not exist")
76
+ else
77
+ super(response)
78
+ end
79
+ end
80
+
81
+ def supported_protocols
82
+ [:https]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,43 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/pdok_nl"
3
+
4
+ module Geocoder::Lookup
5
+ class PdokNl < Base
6
+
7
+ def name
8
+ 'pdok NL'
9
+ end
10
+
11
+ def supported_protocols
12
+ [:https]
13
+ end
14
+
15
+ private # ---------------------------------------------------------------
16
+
17
+ def cache_key(query)
18
+ base_query_url(query) + hash_to_query(query_url_params(query))
19
+ end
20
+
21
+ def base_query_url(query)
22
+ "#{protocol}://api.pdok.nl/bzk/locatieserver/search/v3_1/free?"
23
+ end
24
+
25
+ def valid_response?(response)
26
+ json = parse_json(response.body)
27
+ super(response) if json
28
+ end
29
+
30
+ def results(query)
31
+ return [] unless doc = fetch_data(query)
32
+ return doc['response']['docs']
33
+ end
34
+
35
+ def query_url_params(query)
36
+ {
37
+ fl: '*',
38
+ q: query.text,
39
+ wt: 'json'
40
+ }.merge(super)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,89 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/photon'
3
+
4
+ module Geocoder::Lookup
5
+ class Photon < Base
6
+ def name
7
+ 'Photon'
8
+ end
9
+
10
+ private # ---------------------------------------------------------------
11
+
12
+ def supported_protocols
13
+ [:http, :https]
14
+ end
15
+
16
+ def base_query_url(query)
17
+ host = configuration[:host] || 'photon.komoot.io'
18
+ method = query.reverse_geocode? ? 'reverse' : 'api'
19
+ "#{protocol}://#{host}/#{method}?"
20
+ end
21
+
22
+ def results(query)
23
+ return [] unless (doc = fetch_data(query))
24
+ return [] unless doc['type'] == 'FeatureCollection'
25
+ return [] unless doc['features'] || doc['features'].present?
26
+
27
+ doc['features']
28
+ end
29
+
30
+ def query_url_params(query)
31
+ lang = query.language || configuration.language
32
+ params = { lang: lang, limit: query.options[:limit] }
33
+
34
+ if query.reverse_geocode?
35
+ params.merge!(query_url_params_reverse(query))
36
+ else
37
+ params.merge!(query_url_params_coordinates(query))
38
+ end
39
+
40
+ params.merge!(super)
41
+ end
42
+
43
+ def query_url_params_coordinates(query)
44
+ params = { q: query.sanitized_text }
45
+
46
+ if (bias = query.options[:bias])
47
+ params.merge!(lat: bias[:latitude], lon: bias[:longitude], location_bias_scale: bias[:scale])
48
+ end
49
+
50
+ if (filter = query_url_params_coordinates_filter(query))
51
+ params.merge!(filter)
52
+ end
53
+
54
+ params
55
+ end
56
+
57
+ def query_url_params_coordinates_filter(query)
58
+ filter = query.options[:filter]
59
+ return unless filter
60
+
61
+ bbox = filter[:bbox]
62
+ {
63
+ bbox: bbox.is_a?(Array) ? bbox.join(',') : bbox,
64
+ osm_tag: filter[:osm_tag]
65
+ }
66
+ end
67
+
68
+ def query_url_params_reverse(query)
69
+ params = { lat: query.coordinates[0], lon: query.coordinates[1], radius: query.options[:radius] }
70
+
71
+ if (dsort = query.options[:distance_sort])
72
+ params[:distance_sort] = dsort ? 'true' : 'false'
73
+ end
74
+
75
+ if (filter = query_url_params_reverse_filter(query))
76
+ params.merge!(filter)
77
+ end
78
+
79
+ params
80
+ end
81
+
82
+ def query_url_params_reverse_filter(query)
83
+ filter = query.options[:filter]
84
+ return unless filter
85
+
86
+ { query_string_filter: filter[:string] }
87
+ end
88
+ end
89
+ end
@@ -18,6 +18,7 @@ module Geocoder
18
18
  end
19
19
 
20
20
  def self.read_stub(query_text)
21
+ @default_stub ||= nil
21
22
  stubs.fetch(query_text) {
22
23
  return @default_stub unless @default_stub.nil?
23
24
  raise ArgumentError, "unknown stub request #{query_text}"
@@ -0,0 +1,58 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/twogis"
3
+
4
+ module Geocoder::Lookup
5
+ class Twogis < Base
6
+
7
+ def name
8
+ "2gis"
9
+ end
10
+
11
+ def required_api_key_parts
12
+ ["key"]
13
+ end
14
+
15
+ def map_link_url(coordinates)
16
+ "https://2gis.ru/?m=#{coordinates.join(',')}"
17
+ end
18
+
19
+ def supported_protocols
20
+ [:https]
21
+ end
22
+
23
+ private # ---------------------------------------------------------------
24
+
25
+ def base_query_url(query)
26
+ "#{protocol}://catalog.api.2gis.com/3.0/items/geocode?"
27
+ end
28
+
29
+ def results(query)
30
+ return [] unless doc = fetch_data(query)
31
+ if doc['meta'] && doc['meta']['error']
32
+ Geocoder.log(:warn, "2gis Geocoding API error: #{doc['meta']["code"]} (#{doc['meta']['error']["message"]}).")
33
+ return []
34
+ end
35
+ if doc['result'] && doc = doc['result']['items']
36
+ return doc.to_a
37
+ else
38
+ Geocoder.log(:warn, "2gis Geocoding API error: unexpected response format.")
39
+ return []
40
+ end
41
+ end
42
+
43
+ def query_url_params(query)
44
+ if query.reverse_geocode?
45
+ q = query.coordinates.reverse.join(",")
46
+ else
47
+ q = query.sanitized_text
48
+ end
49
+ params = {
50
+ :q => q,
51
+ :lang => "#{query.language || configuration.language}",
52
+ :key => configuration.api_key,
53
+ :fields => 'items.street,items.adm_div,items.full_address_name,items.point,items.geometry.centroid'
54
+ }
55
+ params.merge(super)
56
+ end
57
+ end
58
+ end
@@ -13,7 +13,7 @@ module Geocoder::Lookup
13
13
  end
14
14
 
15
15
  def base_query_url(query)
16
- "#{protocol}://api.ordnancesurvey.co.uk/opennames/v1/find?"
16
+ "#{protocol}://api.os.uk/search/names/v1/find?"
17
17
  end
18
18
 
19
19
  def required_api_key_parts
@@ -24,11 +24,11 @@ module Geocoder::Lookup
24
24
 
25
25
  def results(query)
26
26
  return [] unless doc = fetch_data(query)
27
- if err = doc['error']
28
- if err["status"] == 401 and err["message"] == "invalid key"
27
+ if [400, 403].include? doc['statusCode']
28
+ if doc['statusCode'] == 403 and doc['message'] == 'Invalid key'
29
29
  raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid API key.")
30
30
  else
31
- Geocoder.log(:warn, "Yandex Geocoding API error: #{err['status']} (#{err['message']}).")
31
+ Geocoder.log(:warn, "Yandex Geocoding API error: #{doc['statusCode']} (#{doc['message']}).")
32
32
  end
33
33
  return []
34
34
  end
@@ -23,7 +23,7 @@ module Geocoder
23
23
  text.split(/\s*,\s*/).join(',')
24
24
  end
25
25
  else
26
- text
26
+ text.to_s.strip
27
27
  end
28
28
  end
29
29
 
@@ -0,0 +1,146 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder
4
+ module Result
5
+ class AbstractApi < Base
6
+
7
+ ##
8
+ # Geolocation
9
+
10
+ def state
11
+ @data['region']
12
+ end
13
+
14
+ def state_code
15
+ @data['region_iso_code']
16
+ end
17
+
18
+ def city
19
+ @data["city"]
20
+ end
21
+
22
+ def city_geoname_id
23
+ @data["city_geoname_id"]
24
+ end
25
+
26
+ def region_geoname_id
27
+ @data["region_geoname_id"]
28
+ end
29
+
30
+ def postal_code
31
+ @data["postal_code"]
32
+ end
33
+
34
+ def country
35
+ @data["country"]
36
+ end
37
+
38
+ def country_code
39
+ @data["country_code"]
40
+ end
41
+
42
+ def country_geoname_id
43
+ @data["country_geoname_id"]
44
+ end
45
+
46
+ def country_is_eu
47
+ @data["country_is_eu"]
48
+ end
49
+
50
+ def continent
51
+ @data["continent"]
52
+ end
53
+
54
+ def continent_code
55
+ @data["continent_code"]
56
+ end
57
+
58
+ def continent_geoname_id
59
+ @data["continent_geoname_id"]
60
+ end
61
+
62
+ ##
63
+ # Security
64
+
65
+ def is_vpn?
66
+ @data.dig "security", "is_vpn"
67
+ end
68
+
69
+ ##
70
+ # Timezone
71
+
72
+ def timezone_name
73
+ @data.dig "timezone", "name"
74
+ end
75
+
76
+ def timezone_abbreviation
77
+ @data.dig "timezone", "abbreviation"
78
+ end
79
+
80
+ def timezone_gmt_offset
81
+ @data.dig "timezone", "gmt_offset"
82
+ end
83
+
84
+ def timezone_current_time
85
+ @data.dig "timezone", "current_time"
86
+ end
87
+
88
+ def timezone_is_dst
89
+ @data.dig "timezone", "is_dst"
90
+ end
91
+
92
+ ##
93
+ # Flag
94
+
95
+ def flag_emoji
96
+ @data.dig "flag", "emoji"
97
+ end
98
+
99
+ def flag_unicode
100
+ @data.dig "flag", "unicode"
101
+ end
102
+
103
+ def flag_png
104
+ @data.dig "flag", "png"
105
+ end
106
+
107
+ def flag_svg
108
+ @data.dig "flag", "svg"
109
+ end
110
+
111
+ ##
112
+ # Currency
113
+
114
+ def currency_currency_name
115
+ @data.dig "currency", "currency_name"
116
+ end
117
+
118
+ def currency_currency_code
119
+ @data.dig "currency", "currency_code"
120
+ end
121
+
122
+ ##
123
+ # Connection
124
+
125
+ def connection_autonomous_system_number
126
+ @data.dig "connection", "autonomous_system_number"
127
+ end
128
+
129
+ def connection_autonomous_system_organization
130
+ @data.dig "connection", "autonomous_system_organization"
131
+ end
132
+
133
+ def connection_connection_type
134
+ @data.dig "connection", "connection_type"
135
+ end
136
+
137
+ def connection_isp_name
138
+ @data.dig "connection", "isp_name"
139
+ end
140
+
141
+ def connection_organization_name
142
+ @data.dig "connection", "organization_name"
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,62 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class AmazonLocationService < Base
5
+ def initialize(result)
6
+ @place = result.place
7
+ super
8
+ end
9
+
10
+ def coordinates
11
+ [@place.geometry.point[1], @place.geometry.point[0]]
12
+ end
13
+
14
+ def address
15
+ @place.label
16
+ end
17
+
18
+ def neighborhood
19
+ @place.neighborhood
20
+ end
21
+
22
+ def route
23
+ @place.street
24
+ end
25
+
26
+ def city
27
+ @place.municipality || @place.sub_region
28
+ end
29
+
30
+ def state
31
+ @place.region
32
+ end
33
+
34
+ def state_code
35
+ @place.region
36
+ end
37
+
38
+ def province
39
+ @place.region
40
+ end
41
+
42
+ def province_code
43
+ @place.region
44
+ end
45
+
46
+ def postal_code
47
+ @place.postal_code
48
+ end
49
+
50
+ def country
51
+ @place.country
52
+ end
53
+
54
+ def country_code
55
+ @place.country
56
+ end
57
+
58
+ def place_id
59
+ data.place_id if data.respond_to?(:place_id)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,65 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class Azure < Base
5
+ def address
6
+ @data['address']['freeformAddress']
7
+ end
8
+
9
+ def building_number
10
+ @data['address']['buildingNumber']
11
+ end
12
+
13
+ def city
14
+ @data['address']['municipality']
15
+ end
16
+
17
+ def coordinates
18
+ if @data['position'].is_a?(String) # reverse geocoding result
19
+ @data['position'].split(',').map(&:to_f)
20
+ elsif @data['position'].is_a?(Hash) # forward geocoding result
21
+ [@data['position']['lat'], @data['position']['lon']]
22
+ end
23
+ end
24
+
25
+ def country
26
+ @data['address']['country']
27
+ end
28
+
29
+ def country_code
30
+ @data['address']['countryCode']
31
+ end
32
+
33
+ def district
34
+ @data['address']['municipalitySubdivision']
35
+ end
36
+
37
+ def postal_code
38
+ @data['address']['postalCode']
39
+ end
40
+
41
+ def province
42
+ @data['address']['countrySubdivision']
43
+ end
44
+
45
+ def state
46
+ @data['address']['countrySubdivision']
47
+ end
48
+
49
+ def state_code
50
+ @data['address']['countrySubdivisionCode']
51
+ end
52
+
53
+ def street_name
54
+ @data['address']['streetName']
55
+ end
56
+
57
+ def street_number
58
+ @data['address']['streetNumber']
59
+ end
60
+
61
+ def viewport
62
+ @data['viewport'] || {}
63
+ end
64
+ end
65
+ end
@@ -275,7 +275,7 @@ module Geocoder::Result
275
275
  private
276
276
 
277
277
  def city?(result_type)
278
- %w(village town city).include?(result_type)
278
+ result_type == 'municipality'
279
279
  end
280
280
 
281
281
  end
@@ -16,11 +16,14 @@ module Geocoder::Result
16
16
  end
17
17
  end
18
18
 
19
- def state_code
19
+ def state
20
20
  attributes['Region']
21
21
  end
22
22
 
23
- alias_method :state, :state_code
23
+ def state_code
24
+ abbr = attributes['RegionAbbr']
25
+ abbr.to_s == "" ? state : abbr
26
+ end
24
27
 
25
28
  def country
26
29
  country_key = reverse_geocode? ? "CountryCode" : "Country"