app/models/geo_location.rb
class GeoLocation < ApplicationRecord
# When to perform geocoding queries (to google)
# ==========================================================================================
after_validation :geocode, :if => [ 'address_changed?', 'latitude.nil?' ]
after_find :geocode_and_save, :if => 'latitude.nil?'
# What to do when performing a geocoding query (to google)
# ==========================================================================================
geocoded_by :address, language: I18n.locale do |geo_location, geo_query_results|
result = geo_query_results.first
if result
# Definition of `result` with available methods can be found here:
# https://github.com/alexreisner/geocoder/blob/master/lib/geocoder/results/base.rb
# https://github.com/alexreisner/geocoder/blob/master/lib/geocoder/results/google.rb
# https://github.com/alexreisner/geocoder/blob/master/lib/geocoder/results/nominatim.rb
geo_location.latitude = result.latitude
geo_location.longitude = result.longitude
geo_location.city = result.city
geo_location.country = result.country
geo_location.country_code = result.country_code
geo_location.postal_code = result.postal_code
if result.respond_to?(:route)
# There is no way to determine whether the street format is
# `{{street name}} {{street number}}` or `{{street number}} {{street name}}`.
# Therefore take the format from the original input.
#
if result.address.try(:include?, [result.street_number, result.route].join(" "))
geo_location.street = [result.street_number, result.route].join(" ")
else
geo_location.street = [result.route, result.street_number].join(" ")
end
elsif result.respond_to?(:street)
geo_location.street = result.street
end
geo_location.state = result.state
end
self
end
# This method performs the geociding query and stores the query datetime.
#
def geocode
self.queried_at = DateTime.now
begin
super
rescue ArgumentError => e
raise e unless Rails.env.test?
end
end
# Perform geocode query and save the record.
# This is needed after finding a record where no geocode query has been done, yet.
#
def geocode_and_save
geocode
save
end
# This method returns `true` if the geocoding query has been performed, already.
#
# After a query, the attributes may still be empty, since this might be an
# invalid address. Therefore, it's good to know, whether the query has been
# done already.
#
def geocoded?
(queried_at.present? || country_code.present?)
end
# Finder Methods
# ==========================================================================================
# Find a geo_location object by address string.
# If the address already exists in the database, return the object from
# the database. Otherwise, create a new database entry for the address
# and perform a geocoding query.
#
# This method is already defined be ActiveRecord.
#
# def self.find_or_create_by_address( address_string )
# end
# Country Specific Methods
# ==========================================================================================
# This method returns true if the location is in Europe.
#
def in_europe?
country_code.in? GeoLocation.european_country_codes
end
def self.european_country_codes
%w(AD AL AT BA BE BG BY CH CY CZ DE DK EE ES FI FO FR GG GI GR GB HR HU IE IM IS IT JE LI LT LU LV MC MD\
MK MT NL NO PL PT RO RU SE SI SJ SK SM TR UA UK VA YU)
end
# The following method is a country-specific accessor to the the German
# Postleitzahl (PLZ). If returns the German postal code if the
# address is in Germany. Otherwise, it returns nil.
#
# There are cases when the maps api can't isolate the postal code.
# In this case, the `postal_code` returns `nil`.
# Try to get it from the regex then.
#
def plz
if country_code == "DE"
postal_code || address.match(/(\d{5})/).try(:[], 1)
end
end
# This returns the country code by ISO 3166-1-Alpha-3.
#
# Example:
#
# User.first.address_fields.first.geo_location.country_code_with_3_letters
# # => "DEU"
#
def country_code_with_3_letters
GeoLocation.country_codes_3_letters_from_2_letters[self.country_code]
end
# This is a hash mapping of ISO 3166-1-Alpha-2 to ISO 3166-1-Alpha-3.
# See this issue: https://github.com/fiedl/wingolfsplattform/issues/67.
#
def self.country_codes_3_letters_from_2_letters
{
"AD" => "AND",
"AE" => "ARE",
"AF" => "AFG",
"AG" => "ATG",
"AI" => "AIA",
"AL" => "ALB",
"AM" => "ARM",
"AO" => "AGO",
"AQ" => "ATA",
"AR" => "ARG",
"AS" => "ASM",
"AT" => "AUT",
"AU" => "AUS",
"AW" => "ABW",
"AX" => "ALA",
"AZ" => "AZE",
"BA" => "BIH",
"BB" => "BRB",
"BD" => "BGD",
"BE" => "BEL",
"BF" => "BFA",
"BG" => "BGR",
"BH" => "BHR",
"BI" => "BDI",
"BJ" => "BEN",
"BL" => "BLM",
"BM" => "BMU",
"BN" => "BRN",
"BO" => "BOL",
"BQ" => "BES",
"BR" => "BRA",
"BS" => "BHS",
"BT" => "BTN",
"BV" => "BVT",
"BW" => "BWA",
"BY" => "BLR",
"BZ" => "BLZ",
"CA" => "CAN",
"CC" => "CCK",
"CD" => "COD",
"CF" => "CAF",
"CG" => "COG",
"CH" => "CHE",
"CI" => "CIV",
"CK" => "COK",
"CL" => "CHL",
"CM" => "CMR",
"CN" => "CHN",
"CO" => "COL",
"CR" => "CRI",
"CU" => "CUB",
"CV" => "CPV",
"CW" => "CUW",
"CX" => "CXR",
"CY" => "CYP",
"CZ" => "CZE",
"DE" => "DEU",
"DJ" => "DJI",
"DK" => "DNK",
"DM" => "DMA",
"DO" => "DOM",
"DZ" => "DZA",
"EC" => "ECU",
"EE" => "EST",
"EG" => "EGY",
"EH" => "ESH",
"ER" => "ERI",
"ES" => "ESP",
"ET" => "ETH",
"FI" => "FIN",
"FJ" => "FJI",
"FK" => "FLK",
"FM" => "FSM",
"FO" => "FRO",
"FR" => "FRA",
"GA" => "GAB",
"GB" => "GBR",
"GD" => "GRD",
"GE" => "GEO",
"GF" => "GUF",
"GG" => "GGY",
"GH" => "GHA",
"GI" => "GIB",
"GL" => "GRL",
"GM" => "GMB",
"GN" => "GIN",
"GP" => "GLP",
"GQ" => "GNQ",
"GR" => "GRC",
"GS" => "SGS",
"GT" => "GTM",
"GU" => "GUM",
"GW" => "GNB",
"GY" => "GUY",
"HK" => "HKG",
"HM" => "HMD",
"HN" => "HND",
"HR" => "HRV",
"HT" => "HTI",
"HU" => "HUN",
"ID" => "IDN",
"IE" => "IRL",
"IL" => "ISR",
"IM" => "IMN",
"IN" => "IND",
"IO" => "IOT",
"IQ" => "IRQ",
"IR" => "IRN",
"IS" => "ISL",
"IT" => "ITA",
"JE" => "JEY",
"JM" => "JAM",
"JO" => "JOR",
"JP" => "JPN",
"KE" => "KEN",
"KG" => "KGZ",
"KH" => "KHM",
"KI" => "KIR",
"KM" => "COM",
"KN" => "KNA",
"KP" => "PRK",
"KR" => "KOR",
"XK" => "XKX",
"KW" => "KWT",
"KY" => "CYM",
"KZ" => "KAZ",
"LA" => "LAO",
"LB" => "LBN",
"LC" => "LCA",
"LI" => "LIE",
"LK" => "LKA",
"LR" => "LBR",
"LS" => "LSO",
"LT" => "LTU",
"LU" => "LUX",
"LV" => "LVA",
"LY" => "LBY",
"MA" => "MAR",
"MC" => "MCO",
"MD" => "MDA",
"ME" => "MNE",
"MF" => "MAF",
"MG" => "MDG",
"MH" => "MHL",
"MK" => "MKD",
"ML" => "MLI",
"MM" => "MMR",
"MN" => "MNG",
"MO" => "MAC",
"MP" => "MNP",
"MQ" => "MTQ",
"MR" => "MRT",
"MS" => "MSR",
"MT" => "MLT",
"MU" => "MUS",
"MV" => "MDV",
"MW" => "MWI",
"MX" => "MEX",
"MY" => "MYS",
"MZ" => "MOZ",
"NA" => "NAM",
"NC" => "NCL",
"NE" => "NER",
"NF" => "NFK",
"NG" => "NGA",
"NI" => "NIC",
"NL" => "NLD",
"NO" => "NOR",
"NP" => "NPL",
"NR" => "NRU",
"NU" => "NIU",
"NZ" => "NZL",
"OM" => "OMN",
"PA" => "PAN",
"PE" => "PER",
"PF" => "PYF",
"PG" => "PNG",
"PH" => "PHL",
"PK" => "PAK",
"PL" => "POL",
"PM" => "SPM",
"PN" => "PCN",
"PR" => "PRI",
"PS" => "PSE",
"PT" => "PRT",
"PW" => "PLW",
"PY" => "PRY",
"QA" => "QAT",
"RE" => "REU",
"RO" => "ROU",
"RS" => "SRB",
"RU" => "RUS",
"RW" => "RWA",
"SA" => "SAU",
"SB" => "SLB",
"SC" => "SYC",
"SD" => "SDN",
"SS" => "SSD",
"SE" => "SWE",
"SG" => "SGP",
"SH" => "SHN",
"SI" => "SVN",
"SJ" => "SJM",
"SK" => "SVK",
"SL" => "SLE",
"SM" => "SMR",
"SN" => "SEN",
"SO" => "SOM",
"SR" => "SUR",
"ST" => "STP",
"SV" => "SLV",
"SX" => "SXM",
"SY" => "SYR",
"SZ" => "SWZ",
"TC" => "TCA",
"TD" => "TCD",
"TF" => "ATF",
"TG" => "TGO",
"TH" => "THA",
"TJ" => "TJK",
"TK" => "TKL",
"TL" => "TLS",
"TM" => "TKM",
"TN" => "TUN",
"TO" => "TON",
"TR" => "TUR",
"TT" => "TTO",
"TV" => "TUV",
"TW" => "TWN",
"TZ" => "TZA",
"UA" => "UKR",
"UG" => "UGA",
"UM" => "UMI",
"US" => "USA",
"UY" => "URY",
"UZ" => "UZB",
"VA" => "VAT",
"VC" => "VCT",
"VE" => "VEN",
"VG" => "VGB",
"VI" => "VIR",
"VN" => "VNM",
"VU" => "VUT",
"WF" => "WLF",
"WS" => "WSM",
"YE" => "YEM",
"YT" => "MYT",
"ZA" => "ZAF",
"ZM" => "ZMB",
"ZW" => "ZWE",
"CS" => "SCG",
"AN" => "ANT"
}
end
def self.country_codes
self.country_codes_3_letters_from_2_letters.keys
end
def self.usa_state_shortcuts
{"Alabama"=>"AL", "Alaska"=>"AK", "Arizona"=>"AZ", "Arkansas"=>"AR","California"=>"CA",
"Colorado"=>"CO", "Connecticut"=>"CT", "Delaware"=>"DE", "District of Columbia"=>"DC",
"Florida"=>"FL", "Georgia"=>"GA", "Hawaii"=>"HI", "Idaho"=>"ID", "Illinois"=>"IL",
"Indiana"=>"IN", "Iowa"=>"IA","Kansas"=>"KS", "Kentucky"=>"KY", "Louisiana"=>"LA",
"Maine"=>"ME","Maryland"=>"MD", "Massachusetts"=>"MA", "Michigan"=>"MI", "Minnesota"=>"MN",
"Mississippi"=>"MS", "Missouri"=>"MO", "Montana"=>"MT","Nebraska"=>"NE", "Nevada"=>"NV",
"New Hampshire"=>"NH", "NewJersey"=>"NJ", "New Mexico"=>"NM", "New York"=>"NY",
"North Carolina"=>"NC", "North Dakota"=>"ND", "Ohio"=>"OH", "Oklahoma"=>"OK",
"Oregon"=>"OR", "Pennsylvania"=>"PA", "Puerto Rico"=>"PR", "Rhode Island"=>"RI",
"South Carolina"=>"SC", "South Dakota"=>"SD", "Tennessee"=>"TN", "Texas"=>"TX",
"Utah"=>"UT", "Vermont"=>"VT", "Virginia"=>"VA", "Washington"=>"WA", "West Virginia"=>"WV",
"Wisconsin"=>"WI", "Wyoming"=>"WY"}
end
end