geocoder 1.6.2 → 1.7.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/LICENSE +1 -1
- data/README.md +328 -231
- data/bin/console +6 -0
- data/lib/generators/geocoder/config/templates/initializer.rb +7 -1
- data/lib/geocoder/cache.rb +16 -33
- data/lib/geocoder/cache_stores/base.rb +40 -0
- data/lib/geocoder/cache_stores/generic.rb +35 -0
- data/lib/geocoder/cache_stores/redis.rb +34 -0
- data/lib/geocoder/configuration.rb +11 -4
- data/lib/geocoder/configuration_hash.rb +4 -4
- data/lib/geocoder/ip_address.rb +8 -1
- data/lib/geocoder/lookup.rb +16 -2
- data/lib/geocoder/lookups/abstract_api.rb +46 -0
- data/lib/geocoder/lookups/amazon_location_service.rb +54 -0
- data/lib/geocoder/lookups/ban_data_gouv_fr.rb +1 -1
- data/lib/geocoder/lookups/base.rb +8 -2
- data/lib/geocoder/lookups/bing.rb +1 -1
- data/lib/geocoder/lookups/esri.rb +6 -0
- data/lib/geocoder/lookups/geoapify.rb +72 -0
- data/lib/geocoder/lookups/geocodio.rb +1 -1
- data/lib/geocoder/lookups/geoip2.rb +4 -0
- data/lib/geocoder/lookups/google.rb +7 -2
- data/lib/geocoder/lookups/google_places_details.rb +8 -14
- data/lib/geocoder/lookups/google_places_search.rb +28 -2
- data/lib/geocoder/lookups/google_premier.rb +4 -0
- data/lib/geocoder/lookups/ip2location.rb +10 -6
- data/lib/geocoder/lookups/ipdata_co.rb +1 -1
- data/lib/geocoder/lookups/ipqualityscore.rb +50 -0
- data/lib/geocoder/lookups/latlon.rb +1 -2
- data/lib/geocoder/lookups/maxmind_local.rb +7 -1
- data/lib/geocoder/lookups/melissa_street.rb +41 -0
- data/lib/geocoder/lookups/photon.rb +89 -0
- data/lib/geocoder/lookups/smarty_streets.rb +6 -1
- data/lib/geocoder/lookups/telize.rb +1 -1
- data/lib/geocoder/lookups/test.rb +4 -0
- data/lib/geocoder/lookups/uk_ordnance_survey_names.rb +1 -1
- data/lib/geocoder/lookups/yandex.rb +1 -2
- data/lib/geocoder/results/abstract_api.rb +146 -0
- data/lib/geocoder/results/amazon_location_service.rb +57 -0
- data/lib/geocoder/results/ban_data_gouv_fr.rb +26 -1
- data/lib/geocoder/results/db_ip_com.rb +1 -1
- data/lib/geocoder/results/esri.rb +5 -2
- data/lib/geocoder/results/geoapify.rb +179 -0
- data/lib/geocoder/results/ipqualityscore.rb +54 -0
- data/lib/geocoder/results/ipregistry.rb +4 -8
- data/lib/geocoder/results/mapbox.rb +10 -4
- data/lib/geocoder/results/melissa_street.rb +46 -0
- data/lib/geocoder/results/nationaal_georegister_nl.rb +1 -1
- data/lib/geocoder/results/nominatim.rb +27 -15
- data/lib/geocoder/results/photon.rb +119 -0
- data/lib/geocoder/util.rb +29 -0
- data/lib/geocoder/version.rb +1 -1
- metadata +22 -10
- data/examples/autoexpire_cache_dalli.rb +0 -62
- data/examples/autoexpire_cache_redis.rb +0 -30
- data/lib/hash_recursive_merge.rb +0 -73
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'geocoder/results/base'
|
4
|
+
|
5
|
+
module Geocoder
|
6
|
+
module Result
|
7
|
+
# https://apidocs.geoapify.com/docs/geocoding/api
|
8
|
+
class Geoapify < Base
|
9
|
+
def address(_format = :full)
|
10
|
+
properties['formatted']
|
11
|
+
end
|
12
|
+
|
13
|
+
def address_line1
|
14
|
+
properties['address_line1']
|
15
|
+
end
|
16
|
+
|
17
|
+
def address_line2
|
18
|
+
properties['address_line2']
|
19
|
+
end
|
20
|
+
|
21
|
+
def house_number
|
22
|
+
properties['housenumber']
|
23
|
+
end
|
24
|
+
|
25
|
+
def street
|
26
|
+
properties['street']
|
27
|
+
end
|
28
|
+
|
29
|
+
def postal_code
|
30
|
+
properties['postcode']
|
31
|
+
end
|
32
|
+
|
33
|
+
def district
|
34
|
+
properties['district']
|
35
|
+
end
|
36
|
+
|
37
|
+
def suburb
|
38
|
+
properties['suburb']
|
39
|
+
end
|
40
|
+
|
41
|
+
def city
|
42
|
+
properties['city']
|
43
|
+
end
|
44
|
+
|
45
|
+
def county
|
46
|
+
properties['county']
|
47
|
+
end
|
48
|
+
|
49
|
+
def state
|
50
|
+
properties['state']
|
51
|
+
end
|
52
|
+
|
53
|
+
# Not currently available in the API
|
54
|
+
def state_code
|
55
|
+
''
|
56
|
+
end
|
57
|
+
|
58
|
+
def country
|
59
|
+
properties['country']
|
60
|
+
end
|
61
|
+
|
62
|
+
def country_code
|
63
|
+
return unless properties['country_code']
|
64
|
+
|
65
|
+
properties['country_code'].upcase
|
66
|
+
end
|
67
|
+
|
68
|
+
def coordinates
|
69
|
+
return unless properties['lat']
|
70
|
+
return unless properties['lon']
|
71
|
+
|
72
|
+
[properties['lat'], properties['lon']]
|
73
|
+
end
|
74
|
+
|
75
|
+
# See: https://tools.ietf.org/html/rfc7946#section-3.1
|
76
|
+
#
|
77
|
+
# Each feature has a "Point" type in the Geoapify API.
|
78
|
+
def geometry
|
79
|
+
return unless data['geometry']
|
80
|
+
|
81
|
+
symbol_hash data['geometry']
|
82
|
+
end
|
83
|
+
|
84
|
+
# See: https://tools.ietf.org/html/rfc7946#section-5
|
85
|
+
def bounds
|
86
|
+
data['bbox']
|
87
|
+
end
|
88
|
+
|
89
|
+
# Type of the result, one of:
|
90
|
+
#
|
91
|
+
# * :unknown
|
92
|
+
# * :amenity
|
93
|
+
# * :building
|
94
|
+
# * :street
|
95
|
+
# * :suburb
|
96
|
+
# * :district
|
97
|
+
# * :postcode
|
98
|
+
# * :city
|
99
|
+
# * :county
|
100
|
+
# * :state
|
101
|
+
# * :country
|
102
|
+
#
|
103
|
+
def type
|
104
|
+
return :unknown unless properties['result_type']
|
105
|
+
|
106
|
+
properties['result_type'].to_sym
|
107
|
+
end
|
108
|
+
|
109
|
+
# Distance in meters to given bias:proximity or to given coordinates for
|
110
|
+
# reverse geocoding
|
111
|
+
def distance
|
112
|
+
properties['distance']
|
113
|
+
end
|
114
|
+
|
115
|
+
# Calculated rank for the result, containing the following keys:
|
116
|
+
#
|
117
|
+
# * `popularity` - The popularity score of the result
|
118
|
+
# * `confidence` - The confidence value of the result (0-1)
|
119
|
+
# * `match_type` - The result's match type, one of following:
|
120
|
+
# * full_match
|
121
|
+
# * inner_part
|
122
|
+
# * match_by_building
|
123
|
+
# * match_by_street
|
124
|
+
# * match_by_postcode
|
125
|
+
# * match_by_city_or_disrict
|
126
|
+
# * match_by_country_or_state
|
127
|
+
#
|
128
|
+
# Example:
|
129
|
+
# {
|
130
|
+
# popularity: 8.615793062435909,
|
131
|
+
# confidence: 0.88,
|
132
|
+
# match_type: :full_match
|
133
|
+
# }
|
134
|
+
def rank
|
135
|
+
return unless properties['rank']
|
136
|
+
|
137
|
+
r = symbol_hash(properties['rank'])
|
138
|
+
r[:match_type] = r[:match_type].to_sym if r[:match_type]
|
139
|
+
r
|
140
|
+
end
|
141
|
+
|
142
|
+
# Examples:
|
143
|
+
#
|
144
|
+
# Open
|
145
|
+
# {
|
146
|
+
# sourcename: 'openstreetmap',
|
147
|
+
# wheelchair: 'limited',
|
148
|
+
# wikidata: 'Q186125',
|
149
|
+
# wikipedia: 'en:Madison Square Garden',
|
150
|
+
# website: 'http://www.thegarden.com/',
|
151
|
+
# phone: '12124656741',
|
152
|
+
# osm_type: 'W',
|
153
|
+
# osm_id: 138141251,
|
154
|
+
# continent: 'North America',
|
155
|
+
# }
|
156
|
+
def datasource
|
157
|
+
return unless properties['datasource']
|
158
|
+
|
159
|
+
symbol_hash properties['datasource']
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def properties
|
165
|
+
@properties ||= data['properties'] || {}
|
166
|
+
end
|
167
|
+
|
168
|
+
def symbol_hash(orig_hash)
|
169
|
+
{}.tap do |result|
|
170
|
+
orig_hash.each_key do |key|
|
171
|
+
next unless orig_hash[key]
|
172
|
+
|
173
|
+
result[key.to_sym] = orig_hash[key]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'geocoder/results/base'
|
2
|
+
|
3
|
+
module Geocoder
|
4
|
+
module Result
|
5
|
+
class Ipqualityscore < Base
|
6
|
+
|
7
|
+
def self.key_method_mappings
|
8
|
+
{
|
9
|
+
'request_id' => :request_id,
|
10
|
+
'success' => :success?,
|
11
|
+
'message' => :message,
|
12
|
+
'city' => :city,
|
13
|
+
'region' => :state,
|
14
|
+
'country_code' => :country_code,
|
15
|
+
'mobile' => :mobile?,
|
16
|
+
'fraud_score' => :fraud_score,
|
17
|
+
'ISP' => :isp,
|
18
|
+
'ASN' => :asn,
|
19
|
+
'organization' => :organization,
|
20
|
+
'is_crawler' => :crawler?,
|
21
|
+
'host' => :host,
|
22
|
+
'proxy' => :proxy?,
|
23
|
+
'vpn' => :vpn?,
|
24
|
+
'tor' => :tor?,
|
25
|
+
'active_vpn' => :active_vpn?,
|
26
|
+
'active_tor' => :active_tor?,
|
27
|
+
'recent_abuse' => :recent_abuse?,
|
28
|
+
'bot_status' => :bot?,
|
29
|
+
'connection_type' => :connection_type,
|
30
|
+
'abuse_velocity' => :abuse_velocity,
|
31
|
+
'timezone' => :timezone,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
key_method_mappings.each_pair do |key, meth|
|
36
|
+
define_method meth do
|
37
|
+
@data[key]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :state_code, :state
|
42
|
+
alias_method :country, :country_code
|
43
|
+
|
44
|
+
def postal_code
|
45
|
+
'' # No suitable fallback
|
46
|
+
end
|
47
|
+
|
48
|
+
def address
|
49
|
+
[city, state, country_code].compact.reject(&:empty?).join(', ')
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -9,6 +9,10 @@ module Geocoder::Result
|
|
9
9
|
@data = flatten_hash(data)
|
10
10
|
end
|
11
11
|
|
12
|
+
def coordinates
|
13
|
+
[@data['location_latitude'], @data['location_longitude']]
|
14
|
+
end
|
15
|
+
|
12
16
|
def flatten_hash(hash)
|
13
17
|
hash.each_with_object({}) do |(k, v), h|
|
14
18
|
if v.is_a? Hash
|
@@ -35,14 +39,6 @@ module Geocoder::Result
|
|
35
39
|
@data['location_country_code']
|
36
40
|
end
|
37
41
|
|
38
|
-
def latitude
|
39
|
-
@data['location_latitude']
|
40
|
-
end
|
41
|
-
|
42
|
-
def longitude
|
43
|
-
@data['location_longitude']
|
44
|
-
end
|
45
|
-
|
46
42
|
def postal_code
|
47
43
|
@data['location_postal']
|
48
44
|
end
|
@@ -23,7 +23,10 @@ module Geocoder::Result
|
|
23
23
|
context_part('region')
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
def state_code
|
27
|
+
value = context_part('region', 'short_code')
|
28
|
+
value.split('-').last unless value.nil?
|
29
|
+
end
|
27
30
|
|
28
31
|
def postal_code
|
29
32
|
context_part('postcode')
|
@@ -33,7 +36,10 @@ module Geocoder::Result
|
|
33
36
|
context_part('country')
|
34
37
|
end
|
35
38
|
|
36
|
-
|
39
|
+
def country_code
|
40
|
+
value = context_part('country', 'short_code')
|
41
|
+
value.upcase unless value.nil?
|
42
|
+
end
|
37
43
|
|
38
44
|
def neighborhood
|
39
45
|
context_part('neighborhood')
|
@@ -45,8 +51,8 @@ module Geocoder::Result
|
|
45
51
|
|
46
52
|
private
|
47
53
|
|
48
|
-
def context_part(name)
|
49
|
-
context.
|
54
|
+
def context_part(name, key = 'text')
|
55
|
+
(context.detect { |c| c['id'] =~ Regexp.new(name) } || {})[key]
|
50
56
|
end
|
51
57
|
|
52
58
|
def context
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'geocoder/results/base'
|
2
|
+
|
3
|
+
module Geocoder::Result
|
4
|
+
class MelissaStreet < Base
|
5
|
+
def address(format = :full)
|
6
|
+
@data['FormattedAddress']
|
7
|
+
end
|
8
|
+
|
9
|
+
def street_address
|
10
|
+
@data['AddressLine1']
|
11
|
+
end
|
12
|
+
|
13
|
+
def suffix
|
14
|
+
@data['ThoroughfareTrailingType']
|
15
|
+
end
|
16
|
+
|
17
|
+
def number
|
18
|
+
@data['PremisesNumber']
|
19
|
+
end
|
20
|
+
|
21
|
+
def city
|
22
|
+
@data['Locality']
|
23
|
+
end
|
24
|
+
|
25
|
+
def state_code
|
26
|
+
@data['AdministrativeArea']
|
27
|
+
end
|
28
|
+
alias_method :state, :state_code
|
29
|
+
|
30
|
+
def country
|
31
|
+
@data['CountryName']
|
32
|
+
end
|
33
|
+
|
34
|
+
def country_code
|
35
|
+
@data['CountryISO3166_1_Alpha2']
|
36
|
+
end
|
37
|
+
|
38
|
+
def postal_code
|
39
|
+
@data['PostalCode']
|
40
|
+
end
|
41
|
+
|
42
|
+
def coordinates
|
43
|
+
[@data['Latitude'].to_f, @data['Longitude'].to_f]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -4,12 +4,12 @@ module Geocoder::Result
|
|
4
4
|
class Nominatim < Base
|
5
5
|
|
6
6
|
def poi
|
7
|
-
return
|
7
|
+
return address_data[place_type] if address_data.key?(place_type)
|
8
8
|
return nil
|
9
9
|
end
|
10
10
|
|
11
11
|
def house_number
|
12
|
-
|
12
|
+
address_data['house_number']
|
13
13
|
end
|
14
14
|
|
15
15
|
def address
|
@@ -18,65 +18,71 @@ module Geocoder::Result
|
|
18
18
|
|
19
19
|
def street
|
20
20
|
%w[road pedestrian highway].each do |key|
|
21
|
-
return
|
21
|
+
return address_data[key] if address_data.key?(key)
|
22
22
|
end
|
23
23
|
return nil
|
24
24
|
end
|
25
25
|
|
26
26
|
def city
|
27
27
|
%w[city town village hamlet].each do |key|
|
28
|
-
return
|
28
|
+
return address_data[key] if address_data.key?(key)
|
29
29
|
end
|
30
30
|
return nil
|
31
31
|
end
|
32
32
|
|
33
33
|
def village
|
34
|
-
|
34
|
+
address_data['village']
|
35
35
|
end
|
36
36
|
|
37
37
|
def town
|
38
|
-
|
38
|
+
address_data['town']
|
39
39
|
end
|
40
40
|
|
41
41
|
def state
|
42
|
-
|
42
|
+
address_data['state']
|
43
43
|
end
|
44
44
|
|
45
45
|
alias_method :state_code, :state
|
46
46
|
|
47
47
|
def postal_code
|
48
|
-
|
48
|
+
address_data['postcode']
|
49
49
|
end
|
50
50
|
|
51
51
|
def county
|
52
|
-
|
52
|
+
address_data['county']
|
53
53
|
end
|
54
54
|
|
55
55
|
def country
|
56
|
-
|
56
|
+
address_data['country']
|
57
57
|
end
|
58
58
|
|
59
59
|
def country_code
|
60
|
-
|
60
|
+
address_data['country_code']
|
61
61
|
end
|
62
62
|
|
63
63
|
def suburb
|
64
|
-
|
64
|
+
address_data['suburb']
|
65
65
|
end
|
66
66
|
|
67
67
|
def city_district
|
68
|
-
|
68
|
+
address_data['city_district']
|
69
69
|
end
|
70
70
|
|
71
71
|
def state_district
|
72
|
-
|
72
|
+
address_data['state_district']
|
73
73
|
end
|
74
74
|
|
75
75
|
def neighbourhood
|
76
|
-
|
76
|
+
address_data['neighbourhood']
|
77
|
+
end
|
78
|
+
|
79
|
+
def municipality
|
80
|
+
address_data['municipality']
|
77
81
|
end
|
78
82
|
|
79
83
|
def coordinates
|
84
|
+
return [] unless @data['lat'] && @data['lon']
|
85
|
+
|
80
86
|
[@data['lat'].to_f, @data['lon'].to_f]
|
81
87
|
end
|
82
88
|
|
@@ -105,5 +111,11 @@ module Geocoder::Result
|
|
105
111
|
end
|
106
112
|
end
|
107
113
|
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def address_data
|
118
|
+
@data['address'] || {}
|
119
|
+
end
|
108
120
|
end
|
109
121
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'geocoder/results/base'
|
2
|
+
|
3
|
+
module Geocoder::Result
|
4
|
+
class Photon < Base
|
5
|
+
def name
|
6
|
+
properties['name']
|
7
|
+
end
|
8
|
+
|
9
|
+
def address(_format = :full)
|
10
|
+
parts = []
|
11
|
+
parts << name if name
|
12
|
+
parts << street_address if street_address
|
13
|
+
parts << city
|
14
|
+
parts << state if state
|
15
|
+
parts << postal_code
|
16
|
+
parts << country
|
17
|
+
|
18
|
+
parts.join(', ')
|
19
|
+
end
|
20
|
+
|
21
|
+
def street_address
|
22
|
+
return unless street
|
23
|
+
return street unless house_number
|
24
|
+
|
25
|
+
"#{house_number} #{street}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def house_number
|
29
|
+
properties['housenumber']
|
30
|
+
end
|
31
|
+
|
32
|
+
def street
|
33
|
+
properties['street']
|
34
|
+
end
|
35
|
+
|
36
|
+
def postal_code
|
37
|
+
properties['postcode']
|
38
|
+
end
|
39
|
+
|
40
|
+
def city
|
41
|
+
properties['city']
|
42
|
+
end
|
43
|
+
|
44
|
+
def state
|
45
|
+
properties['state']
|
46
|
+
end
|
47
|
+
|
48
|
+
def state_code
|
49
|
+
''
|
50
|
+
end
|
51
|
+
|
52
|
+
def country
|
53
|
+
properties['country']
|
54
|
+
end
|
55
|
+
|
56
|
+
def country_code
|
57
|
+
''
|
58
|
+
end
|
59
|
+
|
60
|
+
def coordinates
|
61
|
+
return unless geometry
|
62
|
+
return unless geometry[:coordinates]
|
63
|
+
|
64
|
+
geometry[:coordinates].reverse
|
65
|
+
end
|
66
|
+
|
67
|
+
def geometry
|
68
|
+
return unless data['geometry']
|
69
|
+
|
70
|
+
symbol_hash data['geometry']
|
71
|
+
end
|
72
|
+
|
73
|
+
def bounds
|
74
|
+
properties['extent']
|
75
|
+
end
|
76
|
+
|
77
|
+
# Type of the result (OSM object type), one of:
|
78
|
+
#
|
79
|
+
# :node
|
80
|
+
# :way
|
81
|
+
# :relation
|
82
|
+
#
|
83
|
+
def type
|
84
|
+
{
|
85
|
+
'N' => :node,
|
86
|
+
'W' => :way,
|
87
|
+
'R' => :relation
|
88
|
+
}[properties['osm_type']]
|
89
|
+
end
|
90
|
+
|
91
|
+
def osm_id
|
92
|
+
properties['osm_id']
|
93
|
+
end
|
94
|
+
|
95
|
+
# See: https://wiki.openstreetmap.org/wiki/Tags
|
96
|
+
def osm_tag
|
97
|
+
return unless properties['osm_key']
|
98
|
+
return properties['osm_key'] unless properties['osm_value']
|
99
|
+
|
100
|
+
"#{properties['osm_key']}=#{properties['osm_value']}"
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def properties
|
106
|
+
@properties ||= data['properties'] || {}
|
107
|
+
end
|
108
|
+
|
109
|
+
def symbol_hash(orig_hash)
|
110
|
+
{}.tap do |result|
|
111
|
+
orig_hash.each_key do |key|
|
112
|
+
next unless orig_hash[key]
|
113
|
+
|
114
|
+
result[key.to_sym] = orig_hash[key]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geocoder
|
4
|
+
module Util
|
5
|
+
#
|
6
|
+
# Recursive version of Hash#merge!
|
7
|
+
#
|
8
|
+
# Adds the contents of +h2+ to +h1+,
|
9
|
+
# merging entries in +h1+ with duplicate keys with those from +h2+.
|
10
|
+
#
|
11
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
12
|
+
# When both +h1+ and +h2+ contains an entry with the same key,
|
13
|
+
# it merges and returns the values from both hashes.
|
14
|
+
#
|
15
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
16
|
+
# h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
|
17
|
+
# recursive_hash_merge(h1, h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
18
|
+
#
|
19
|
+
# Simply using Hash#merge! would return
|
20
|
+
#
|
21
|
+
# h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
22
|
+
#
|
23
|
+
def self.recursive_hash_merge(h1, h2)
|
24
|
+
h1.merge!(h2) do |_key, oldval, newval|
|
25
|
+
oldval.class == h1.class ? self.recursive_hash_merge(oldval, newval) : newval
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/geocoder/version.rb
CHANGED