geocoder 1.4.3 → 1.6.1
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 +5 -5
- data/CHANGELOG.md +64 -0
- data/LICENSE +1 -1
- data/README.md +365 -883
- data/examples/autoexpire_cache_redis.rb +5 -3
- data/lib/generators/geocoder/config/templates/initializer.rb +3 -2
- data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +2 -0
- data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +2 -0
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +1 -1
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +1 -1
- data/lib/generators/geocoder/migration_version.rb +15 -0
- data/lib/geocoder/cache.rb +6 -2
- data/lib/geocoder/calculations.rb +1 -1
- data/lib/geocoder/cli.rb +2 -2
- data/lib/geocoder/configuration.rb +2 -2
- data/lib/geocoder/exceptions.rb +1 -1
- data/lib/geocoder/ip_address.rb +14 -1
- data/lib/geocoder/lookup.rb +13 -6
- data/lib/geocoder/lookups/amap.rb +63 -0
- data/lib/geocoder/lookups/baidu.rb +14 -10
- data/lib/geocoder/lookups/baidu_ip.rb +7 -36
- data/lib/geocoder/lookups/ban_data_gouv_fr.rb +17 -4
- data/lib/geocoder/lookups/base.rb +28 -5
- data/lib/geocoder/lookups/bing.rb +15 -13
- data/lib/geocoder/lookups/db_ip_com.rb +52 -0
- data/lib/geocoder/lookups/dstk.rb +4 -2
- data/lib/geocoder/lookups/esri.rb +39 -29
- data/lib/geocoder/lookups/freegeoip.rb +16 -7
- data/lib/geocoder/lookups/geocoder_ca.rb +4 -4
- data/lib/geocoder/lookups/geocodio.rb +5 -5
- data/lib/geocoder/lookups/geoportail_lu.rb +7 -7
- data/lib/geocoder/lookups/google.rb +13 -9
- data/lib/geocoder/lookups/google_places_details.rb +4 -4
- data/lib/geocoder/lookups/google_places_search.rb +4 -4
- data/lib/geocoder/lookups/google_premier.rb +11 -1
- data/lib/geocoder/lookups/here.rb +29 -23
- data/lib/geocoder/lookups/ip2location.rb +67 -0
- data/lib/geocoder/lookups/ipapi_com.rb +9 -13
- data/lib/geocoder/lookups/ipdata_co.rb +62 -0
- data/lib/geocoder/lookups/ipgeolocation.rb +51 -0
- data/lib/geocoder/lookups/ipinfo_io.rb +11 -29
- data/lib/geocoder/lookups/ipregistry.rb +68 -0
- data/lib/geocoder/lookups/ipstack.rb +63 -0
- data/lib/geocoder/lookups/latlon.rb +4 -4
- data/lib/geocoder/lookups/location_iq.rb +26 -8
- data/lib/geocoder/lookups/mapbox.rb +12 -6
- data/lib/geocoder/lookups/mapquest.rb +4 -5
- data/lib/geocoder/lookups/maxmind.rb +6 -6
- data/lib/geocoder/lookups/maxmind_geoip2.rb +8 -7
- data/lib/geocoder/lookups/nominatim.rb +17 -5
- data/lib/geocoder/lookups/opencagedata.rb +7 -6
- data/lib/geocoder/lookups/osmnames.rb +57 -0
- data/lib/geocoder/lookups/pelias.rb +8 -9
- data/lib/geocoder/lookups/pickpoint.rb +41 -0
- data/lib/geocoder/lookups/pointpin.rb +10 -9
- data/lib/geocoder/lookups/postcode_anywhere_uk.rb +4 -5
- data/lib/geocoder/lookups/postcodes_io.rb +31 -0
- data/lib/geocoder/lookups/smarty_streets.rb +20 -10
- data/lib/geocoder/lookups/telize.rb +26 -6
- data/lib/geocoder/lookups/tencent.rb +59 -0
- data/lib/geocoder/lookups/yandex.rb +12 -8
- data/lib/geocoder/models/active_record.rb +4 -3
- data/lib/geocoder/query.rb +14 -0
- data/lib/geocoder/railtie.rb +1 -1
- data/lib/geocoder/request.rb +32 -0
- data/lib/geocoder/results/amap.rb +87 -0
- data/lib/geocoder/results/baidu.rb +10 -14
- data/lib/geocoder/results/ban_data_gouv_fr.rb +1 -1
- data/lib/geocoder/results/base.rb +13 -1
- data/lib/geocoder/results/bing.rb +1 -1
- data/lib/geocoder/results/db_ip_com.rb +58 -0
- data/lib/geocoder/results/freegeoip.rb +0 -5
- data/lib/geocoder/results/geocoder_ca.rb +3 -3
- data/lib/geocoder/results/geoip2.rb +24 -10
- data/lib/geocoder/results/geoportail_lu.rb +5 -3
- data/lib/geocoder/results/google.rb +16 -5
- data/lib/geocoder/results/here.rb +12 -1
- data/lib/geocoder/results/ip2location.rb +22 -0
- data/lib/geocoder/results/ipdata_co.rb +40 -0
- data/lib/geocoder/results/ipgeolocation.rb +59 -0
- data/lib/geocoder/results/ipregistry.rb +308 -0
- data/lib/geocoder/results/ipstack.rb +60 -0
- data/lib/geocoder/results/maxmind.rb +0 -5
- data/lib/geocoder/results/maxmind_local.rb +0 -5
- data/lib/geocoder/results/nominatim.rb +12 -0
- data/lib/geocoder/results/opencagedata.rb +12 -2
- data/lib/geocoder/results/osmnames.rb +56 -0
- data/lib/geocoder/results/pickpoint.rb +6 -0
- data/lib/geocoder/results/postcodes_io.rb +40 -0
- data/lib/geocoder/results/smarty_streets.rb +55 -19
- data/lib/geocoder/results/telize.rb +0 -5
- data/lib/geocoder/results/tencent.rb +72 -0
- data/lib/geocoder/results/test.rb +1 -1
- data/lib/geocoder/sql.rb +4 -4
- data/lib/geocoder/stores/active_record.rb +16 -5
- data/lib/geocoder/stores/base.rb +1 -2
- data/lib/geocoder/version.rb +1 -1
- data/lib/hash_recursive_merge.rb +1 -2
- data/lib/maxmind_database.rb +3 -3
- data/lib/tasks/geocoder.rake +11 -3
- metadata +30 -14
- data/lib/geocoder/lookups/geocoder_us.rb +0 -43
- data/lib/geocoder/lookups/mapzen.rb +0 -15
- data/lib/geocoder/lookups/okf.rb +0 -44
- data/lib/geocoder/lookups/ovi.rb +0 -62
- data/lib/geocoder/results/geocoder_us.rb +0 -39
- data/lib/geocoder/results/mapzen.rb +0 -5
- data/lib/geocoder/results/okf.rb +0 -106
- data/lib/geocoder/results/ovi.rb +0 -71
|
@@ -7,38 +7,34 @@ module Geocoder::Result
|
|
|
7
7
|
['lat', 'lng'].map{ |i| @data['location'][i] }
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def address
|
|
11
|
-
@data['formatted_address']
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def state
|
|
15
|
-
province
|
|
16
|
-
end
|
|
17
|
-
|
|
18
10
|
def province
|
|
19
|
-
@data['addressComponent']['province']
|
|
11
|
+
@data['addressComponent'] and @data['addressComponent']['province'] or ""
|
|
20
12
|
end
|
|
21
13
|
|
|
14
|
+
alias_method :state, :province
|
|
15
|
+
|
|
22
16
|
def city
|
|
23
|
-
@data['addressComponent']['city']
|
|
17
|
+
@data['addressComponent'] and @data['addressComponent']['city'] or ""
|
|
24
18
|
end
|
|
25
19
|
|
|
26
20
|
def district
|
|
27
|
-
@data['addressComponent']['district']
|
|
21
|
+
@data['addressComponent'] and @data['addressComponent']['district'] or ""
|
|
28
22
|
end
|
|
29
23
|
|
|
30
24
|
def street
|
|
31
|
-
@data['addressComponent']['street']
|
|
25
|
+
@data['addressComponent'] and @data['addressComponent']['street'] or ""
|
|
32
26
|
end
|
|
33
27
|
|
|
34
28
|
def street_number
|
|
35
|
-
@data['addressComponent']['street_number']
|
|
29
|
+
@data['addressComponent'] and @data['addressComponent']['street_number']
|
|
36
30
|
end
|
|
37
31
|
|
|
38
32
|
def formatted_address
|
|
39
|
-
@data['formatted_address']
|
|
33
|
+
@data['formatted_address'] or ""
|
|
40
34
|
end
|
|
41
35
|
|
|
36
|
+
alias_method :address, :formatted_address
|
|
37
|
+
|
|
42
38
|
def address_components
|
|
43
39
|
@data['addressComponent']
|
|
44
40
|
end
|
|
@@ -20,8 +20,20 @@ module Geocoder
|
|
|
20
20
|
##
|
|
21
21
|
# A string in the given format.
|
|
22
22
|
#
|
|
23
|
+
# This default implementation dumbly follows the United States address
|
|
24
|
+
# format and will return incorrect results for most countries. Some APIs
|
|
25
|
+
# return properly formatted addresses and those should be funneled
|
|
26
|
+
# through this method.
|
|
27
|
+
#
|
|
23
28
|
def address(format = :full)
|
|
24
|
-
|
|
29
|
+
if state_code.to_s != ""
|
|
30
|
+
s = ", #{state_code}"
|
|
31
|
+
elsif state.to_s != ""
|
|
32
|
+
s = ", #{state}"
|
|
33
|
+
else
|
|
34
|
+
s = ""
|
|
35
|
+
end
|
|
36
|
+
"#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, '')
|
|
25
37
|
end
|
|
26
38
|
|
|
27
39
|
##
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'geocoder/results/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder::Result
|
|
4
|
+
class DbIpCom < Base
|
|
5
|
+
|
|
6
|
+
def coordinates
|
|
7
|
+
['latitude', 'longitude'].map{ |coordinate_name| @data[coordinate_name] }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def city
|
|
11
|
+
@data['city']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def district
|
|
15
|
+
@data['district']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def state_code
|
|
19
|
+
@data['stateProv']
|
|
20
|
+
end
|
|
21
|
+
alias_method :state, :state_code
|
|
22
|
+
|
|
23
|
+
def zip_code
|
|
24
|
+
@data['zipCode']
|
|
25
|
+
end
|
|
26
|
+
alias_method :postal_code, :zip_code
|
|
27
|
+
|
|
28
|
+
def country_name
|
|
29
|
+
@data['countryName']
|
|
30
|
+
end
|
|
31
|
+
alias_method :country, :country_name
|
|
32
|
+
|
|
33
|
+
def country_code
|
|
34
|
+
@data['countryCode']
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def continent_name
|
|
38
|
+
@data['continentName']
|
|
39
|
+
end
|
|
40
|
+
alias_method :continent, :continent_name
|
|
41
|
+
|
|
42
|
+
def continent_code
|
|
43
|
+
@data['continentCode']
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def time_zone
|
|
47
|
+
@data['timeZone']
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def gmt_offset
|
|
51
|
+
@data['gmtOffset']
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def currency_code
|
|
55
|
+
@data['currencyCode']
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -3,11 +3,6 @@ require 'geocoder/results/base'
|
|
|
3
3
|
module Geocoder::Result
|
|
4
4
|
class Freegeoip < Base
|
|
5
5
|
|
|
6
|
-
def address(format = :full)
|
|
7
|
-
s = state_code.to_s == "" ? "" : ", #{state_code}"
|
|
8
|
-
"#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
|
|
9
|
-
end
|
|
10
|
-
|
|
11
6
|
def city
|
|
12
7
|
@data['city']
|
|
13
8
|
end
|
|
@@ -16,17 +16,17 @@ module Geocoder::Result
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def city
|
|
19
|
-
@data['city']
|
|
19
|
+
@data['city'] or (@data['standard'] and @data['standard']['city']) or ""
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def state
|
|
23
|
-
@data['prov']
|
|
23
|
+
@data['prov'] or (@data['standard'] and @data['standard']['prov']) or ""
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
alias_method :state_code, :state
|
|
27
27
|
|
|
28
28
|
def postal_code
|
|
29
|
-
@data['postal']
|
|
29
|
+
@data['postal'] or (@data['standard'] and @data['standard']['postal']) or ""
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def country
|
|
@@ -3,10 +3,6 @@ require 'geocoder/results/base'
|
|
|
3
3
|
module Geocoder
|
|
4
4
|
module Result
|
|
5
5
|
class Geoip2 < Base
|
|
6
|
-
def address(format = :full)
|
|
7
|
-
s = state.to_s == '' ? '' : ", #{state_code}"
|
|
8
|
-
"#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, '')
|
|
9
|
-
end
|
|
10
6
|
|
|
11
7
|
def coordinates
|
|
12
8
|
%w[latitude longitude].map do |l|
|
|
@@ -15,11 +11,15 @@ module Geocoder
|
|
|
15
11
|
end
|
|
16
12
|
|
|
17
13
|
def city
|
|
18
|
-
|
|
14
|
+
fetch_name(
|
|
15
|
+
data.fetch('city', {}).fetch('names', {})
|
|
16
|
+
)
|
|
19
17
|
end
|
|
20
18
|
|
|
21
19
|
def state
|
|
22
|
-
|
|
20
|
+
fetch_name(
|
|
21
|
+
data.fetch('subdivisions', []).fetch(0, {}).fetch('names', {})
|
|
22
|
+
)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def state_code
|
|
@@ -27,7 +27,9 @@ module Geocoder
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def country
|
|
30
|
-
|
|
30
|
+
fetch_name(
|
|
31
|
+
data.fetch('country', {}).fetch('names', {})
|
|
32
|
+
)
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def country_code
|
|
@@ -48,14 +50,26 @@ module Geocoder
|
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
def language=(l)
|
|
54
|
+
@language = l.to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def language
|
|
58
|
+
@language ||= default_language
|
|
59
|
+
end
|
|
52
60
|
|
|
53
61
|
def data
|
|
54
62
|
@data.to_hash
|
|
55
63
|
end
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def default_language
|
|
68
|
+
@default_language = Geocoder.config[:language].to_s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def fetch_name(names)
|
|
72
|
+
names[language] || names[default_language] || ''
|
|
59
73
|
end
|
|
60
74
|
end
|
|
61
75
|
end
|
|
@@ -59,11 +59,13 @@ module Geocoder::Result
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
61
|
def geolocalized?
|
|
62
|
-
try_to_extract('coordinates', geomlonlat)
|
|
62
|
+
!!try_to_extract('coordinates', geomlonlat)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def try_to_extract(key,
|
|
66
|
-
|
|
65
|
+
def try_to_extract(key, hash)
|
|
66
|
+
if hash.is_a?(Hash) and hash.include?(key)
|
|
67
|
+
hash[key]
|
|
68
|
+
end
|
|
67
69
|
end
|
|
68
70
|
end
|
|
69
71
|
end
|
|
@@ -120,19 +120,30 @@ module Geocoder::Result
|
|
|
120
120
|
def precision
|
|
121
121
|
geometry['location_type'] if geometry
|
|
122
122
|
end
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
def partial_match
|
|
125
125
|
@data['partial_match']
|
|
126
126
|
end
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
def place_id
|
|
129
129
|
@data['place_id']
|
|
130
|
-
end
|
|
130
|
+
end
|
|
131
131
|
|
|
132
132
|
def viewport
|
|
133
133
|
viewport = geometry['viewport'] || fail
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
bounding_box_from viewport
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def bounds
|
|
138
|
+
bounding_box_from geometry['bounds']
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def bounding_box_from(box)
|
|
144
|
+
return nil unless box
|
|
145
|
+
south, west = %w(lat lng).map { |c| box['southwest'][c] }
|
|
146
|
+
north, east = %w(lat lng).map { |c| box['northeast'][c] }
|
|
136
147
|
[south, west, north, east]
|
|
137
148
|
end
|
|
138
149
|
end
|
|
@@ -17,9 +17,20 @@ module Geocoder::Result
|
|
|
17
17
|
fail unless d = @data['Location']['DisplayPosition']
|
|
18
18
|
[d['Latitude'].to_f, d['Longitude'].to_f]
|
|
19
19
|
end
|
|
20
|
+
|
|
21
|
+
def route
|
|
22
|
+
address_data['Street']
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def street_number
|
|
26
|
+
address_data['HouseNumber']
|
|
27
|
+
end
|
|
20
28
|
|
|
21
29
|
def state
|
|
22
|
-
address_data['
|
|
30
|
+
fail unless d = address_data['AdditionalData']
|
|
31
|
+
if v = d.find{|ad| ad['key']=='StateName'}
|
|
32
|
+
return v['value']
|
|
33
|
+
end
|
|
23
34
|
end
|
|
24
35
|
|
|
25
36
|
def province
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'geocoder/results/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder::Result
|
|
4
|
+
class Ip2location < Base
|
|
5
|
+
|
|
6
|
+
def address(format = :full)
|
|
7
|
+
"#{city_name} #{zip_code}, #{country_name}".sub(/^[ ,]*/, '')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.response_attributes
|
|
11
|
+
%w[country_code country_name region_name city_name latitude longitude
|
|
12
|
+
zip_code time_zone isp domain net_speed idd_code area_code usage_type
|
|
13
|
+
weather_station_code weather_station_name mcc mnc mobile_brand elevation]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
response_attributes.each do |attr|
|
|
17
|
+
define_method attr do
|
|
18
|
+
@data[attr] || ""
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'geocoder/results/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder::Result
|
|
4
|
+
class IpdataCo < Base
|
|
5
|
+
|
|
6
|
+
def city
|
|
7
|
+
@data['city']
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def state
|
|
11
|
+
@data['region']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def state_code
|
|
15
|
+
@data['region_code']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def country
|
|
19
|
+
@data['country_name']
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def country_code
|
|
23
|
+
@data['country_code']
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def postal_code
|
|
27
|
+
@data['postal']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.response_attributes
|
|
31
|
+
%w[ip asn organisation currency currency_symbol calling_code flag time_zone is_eu]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
response_attributes.each do |a|
|
|
35
|
+
define_method a do
|
|
36
|
+
@data[a]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'geocoder/results/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder::Result
|
|
4
|
+
class Ipgeolocation < Base
|
|
5
|
+
|
|
6
|
+
def coordinates
|
|
7
|
+
[@data['latitude'].to_f, @data['longitude'].to_f]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def address(format = :full)
|
|
11
|
+
"#{city}, #{state} #{postal_code}, #{country_name}".sub(/^[ ,]*/, "")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def state
|
|
15
|
+
@data['state_prov']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def state_code
|
|
19
|
+
@data['state_prov']
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def country
|
|
23
|
+
@data['country_name']
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def country_code
|
|
27
|
+
@data['country_code2']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def postal_code
|
|
31
|
+
@data['zipcode']
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.response_attributes
|
|
35
|
+
[
|
|
36
|
+
['ip', ''],
|
|
37
|
+
['hostname', ''],
|
|
38
|
+
['continent_code', ''],
|
|
39
|
+
['continent_name', ''],
|
|
40
|
+
['country_code2', ''],
|
|
41
|
+
['country_code3', ''],
|
|
42
|
+
['country_name', ''],
|
|
43
|
+
['country_capital',''],
|
|
44
|
+
['district',''],
|
|
45
|
+
['state_prov',''],
|
|
46
|
+
['city', ''],
|
|
47
|
+
['zipcode', ''],
|
|
48
|
+
['time_zone', {}],
|
|
49
|
+
['currency', {}]
|
|
50
|
+
]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
response_attributes.each do |attr, default|
|
|
54
|
+
define_method attr do
|
|
55
|
+
@data[attr] || default
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|