1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
require 'geocoder'
require 'retryable'
Geocoder.configure(
lookup: :google,
timeout: 10,
api_key: GOOGLE_API_KEY
)
# raise on geocoding errors such as query limit exceeded
Geocoder.configure(always_raise: :all)
# Try to geocode a given query, on exceptions it retries up to 3 times then gives up.
# @param [String] query string to geocode
# @return [Hash] first valid result or nil
def geocode(query, params)
Retryable.retryable(tries: 3, sleep: ->(n) { 2**n }) do
Geocoder.search(query, params: params).first
end
rescue => e
warn "Attempts exceeded for query #{query}, last error was #{e.message}"
nil
end
def load_country_yaml(alpha2)
YAML.load_file(File.join(ISO3166_ROOT_PATH, 'lib', 'countries', 'data', 'countries', "#{alpha2}.yaml"))
end
def save_country_yaml(alpha2, data)
File.open(File.join(ISO3166_ROOT_PATH, 'lib', 'countries', 'data', 'countries', "#{alpha2}.yaml"), 'w+') { |f| f.write data.to_yaml }
end
def country_codes
@country_codes ||= Dir['lib/countries/data/countries/*.yaml'].map { |x| File.basename(x, File.extname(x)) }.uniq
end
namespace :geocode do
desc 'Retrieve and store countries coordinates'
task :fetch_countries do
require 'countries'
# Iterate over countries
ISO3166::Country.all.each do |country|
code = country.alpha2
# Load unmutated yaml file.
data = load_country_yaml(country.alpha2)
next unless (result = geocode(country.iso_short_name, {region: country.alpha2}))
puts "WARNING:: Geocoder returned something that was not a country for #{country.alpha2}" unless result.types.include?('country')
geometry = result.geometry
# Extract center point data
if geometry['location']
data[code]['geo']['latitude'] = geometry['location']['lat']
data[code]['geo']['longitude'] = geometry['location']['lng']
end
# Extract bounding box data
next unless geometry['bounds']
data[code]['geo']['bounds'] = geometry['bounds']
data[code]['geo']['min_latitude'] = geometry['bounds']['southwest']['lat']
data[code]['geo']['min_longitude'] = geometry['bounds']['southwest']['lng']
data[code]['geo']['max_latitude'] = geometry['bounds']['northeast']['lat']
data[code]['geo']['max_longitude'] = geometry['bounds']['northeast']['lng']
# Persist
save_country_yaml(code, data)
end
end
desc 'Retrieve and store subdivisions coordinates'
task :fetch_subdivisions do
require_relative '../../countries'
# Iterate all countries with subdivisions
ISO3166::Country.all.select(&:subdivisions?).each do |c|
# Iterate subdivisions
state_data = c.subdivisions.dup
state_data.reject { |_, data| data['geo'] }.each do |code, data|
location = "#{c.alpha2}-#{code}"
next unless (result = geocode(location, {region: c.alpha2}))
geometry = result.geometry
if geometry['location']
state_data[code]['geo'] ||= {}
state_data[code]['geo']['latitude'] = geometry['location']['lat']
state_data[code]['geo']['longitude'] = geometry['location']['lng']
end
next unless geometry['bounds']
state_data[code]['geo']['min_latitude'] = geometry['bounds']['southwest']['lat']
state_data[code]['geo']['min_longitude'] = geometry['bounds']['southwest']['lng']
state_data[code]['geo']['max_latitude'] = geometry['bounds']['northeast']['lat']
state_data[code]['geo']['max_longitude'] = geometry['bounds']['northeast']['lng']
end
# Write updated YAML for current country
File.open(File.join(ISO3166_ROOT_PATH, 'lib', 'countries', 'data', 'subdivisions', "#{c.alpha2}.yaml"), 'w+') { |f| f.write state_data.to_yaml }
end
end
end
|