require "fastercsv" module Graticule # A geographic location class Location attr_accessor :latitude, :longitude, :premise, :street, :locality, :region, :postal_code, :country, :precision, :warning, :geocoder alias_method :city, :locality alias_method :state, :region alias_method :zip, :postal_code def initialize(attrs = {}) attrs.each do |key,value| instance_variable_set "@#{key}", value end self.precision ||= Precision.unknown end def attributes [:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision].inject({}) do |result,attr| result[attr] = self.send(attr) unless self.send(attr).blank? result end end def normalized_country normalize_string(country) end def normalized_region normalize_string(region) end def normalized_locality normalize_string(normalize_abbreviations(locality)) end def normalized_street normalize_string(normalize_abbreviations(street)) end def normalized_premise normalize_string(normalize_abbreviations(premise)) end def postal_code_base base = case country when "CA" postal_code.to_s[0..2] else postal_code.to_s[0..4] end base.strip! base end # Returns an Array with latitude and longitude. def coordinates [latitude, longitude] end def ==(other) other.respond_to?(:attributes) ? attributes == other.attributes : false end def eql?(other) self == other end def hash attributes.to_s.hash end # Calculate the distance to another location. See the various Distance formulas # for more information def distance_to(destination, options = {}) options = {:formula => :haversine, :units => :miles}.merge(options) "Graticule::Distance::#{options[:formula].to_s.titleize}".constantize.distance(self, destination, options[:units]) end # Where would I be if I dug through the center of the earth? def antipode Location.new :latitude => -latitude, :longitude => longitude + (longitude >= 0 ? -180 : 180) end alias_method :antipodal_location, :antipode def to_s(options = {}) options = {:coordinates => false, :country => true}.merge(options) result = "" result << "#{street}\n" if street result << [locality, [region, postal_code].compact.join(" ")].compact.join(", ") result << " #{country}" if options[:country] && country result << "\nlatitude: #{latitude}, longitude: #{longitude}" if options[:coordinates] && [latitude, longitude].any? result end def to_hash(options = {}) hash = ActiveSupport::OrderedHash.new hash[:latitude] = latitude hash[:longitude] = longitude hash[:premise] = premise hash[:street] = street hash[:locality] = locality hash[:region] = region hash[:postal_code] = postal_code hash[:country] = country hash[:precision] = precision.order hash[:warning] = warning hash end def to_xml(options = {}) options.reverse_merge!(:root => "location") to_hash.to_xml(options) end def to_json(options = {}) to_hash.to_json(options) end def to_csv(options = {}) hash = to_hash FasterCSV.generate do |csv| if(options[:headers]) csv << hash.keys end csv << hash.values end end private # A list of standard USPS street abbreviations. Taken from: # http://www.usps.com/ncsc/lookups/usps_abbreviations.html USPS_STREET_ABBREVIATIONS = { "allee" => "aly", "alley" => "aly", "ally" => "aly", "anex" => "anx", "annex" => "anx", "annx" => "anx", "arcade" => "arc", "av" => "ave", "aven" => "ave", "avenu" => "ave", "avenue" => "ave", "avn" => "ave", "avnue" => "ave", "bayoo" => "byu", "bayou" => "byu", "beach" => "bch", "bend" => "bnd", "bluf" => "blf", "bluff" => "blf", "bluffs" => "blfs", "bot" => "btm", "bottm" => "btm", "bottom" => "btm", "boul" => "blvd", "boulevard" => "blvd", "boulv" => "blvd", "branch" => "br", "brdge" => "brg", "bridge" => "brg", "brnch" => "br", "brook" => "brk", "brooks" => "brks", "burg" => "bg", "burgs" => "bgs", "bypa" => "byp", "bypas" => "byp", "bypass" => "byp", "byps" => "byp", "camp" => "cp", "canyn" => "cyn", "canyon" => "cyn", "cape" => "cpe", "causeway" => "cswy", "causway" => "cswy", "cen" => "ctr", "cent" => "ctr", "center" => "ctr", "centers" => "ctrs", "centr" => "ctr", "centre" => "ctr", "circ" => "cir", "circl" => "cir", "circle" => "cir", "circles" => "cirs", "ck" => "crk", "cliff" => "clf", "cliffs" => "clfs", "club" => "clb", "cmp" => "cp", "cnter" => "ctr", "cntr" => "ctr", "cnyn" => "cyn", "common" => "cmn", "corner" => "cor", "corners" => "cors", "course" => "crse", "court" => "ct", "courts" => "cts", "cove" => "cv", "coves" => "cvs", "cr" => "crk", "crcl" => "cir", "crcle" => "cir", "crecent" => "cres", "creek" => "crk", "crescent" => "cres", "cresent" => "cres", "crest" => "crst", "crossing" => "xing", "crossroad" => "xrd", "crscnt" => "cres", "crsent" => "cres", "crsnt" => "cres", "crssing" => "xing", "crssng" => "xing", "crt" => "ct", "curve" => "curv", "dale" => "dl", "dam" => "dm", "div" => "dv", "divide" => "dv", "driv" => "dr", "drive" => "dr", "drives" => "drs", "drv" => "dr", "dvd" => "dv", "estate" => "est", "estates" => "ests", "exp" => "expy", "expr" => "expy", "express" => "expy", "expressway" => "expy", "expw" => "expy", "extension" => "ext", "extensions" => "exts", "extn" => "ext", "extnsn" => "ext", "falls" => "fls", "ferry" => "fry", "field" => "fld", "fields" => "flds", "flat" => "flt", "flats" => "flts", "ford" => "frd", "fords" => "frds", "forest" => "frst", "forests" => "frst", "forg" => "frg", "forge" => "frg", "forges" => "frgs", "fork" => "frk", "forks" => "frks", "fort" => "ft", "freeway" => "fwy", "freewy" => "fwy", "frry" => "fry", "frt" => "ft", "frway" => "fwy", "frwy" => "fwy", "garden" => "gdn", "gardens" => "gdns", "gardn" => "gdn", "gateway" => "gtwy", "gatewy" => "gtwy", "gatway" => "gtwy", "glen" => "gln", "glens" => "glns", "grden" => "gdn", "grdn" => "gdn", "grdns" => "gdns", "green" => "grn", "greens" => "grns", "grov" => "grv", "grove" => "grv", "groves" => "grvs", "gtway" => "gtwy", "harb" => "hbr", "harbor" => "hbr", "harbors" => "hbrs", "harbr" => "hbr", "haven" => "hvn", "havn" => "hvn", "height" => "hts", "heights" => "hts", "hgts" => "hts", "highway" => "hwy", "highwy" => "hwy", "hill" => "hl", "hills" => "hls", "hiway" => "hwy", "hiwy" => "hwy", "hllw" => "holw", "hollow" => "holw", "hollows" => "holw", "holws" => "holw", "hrbor" => "hbr", "ht" => "hts", "hway" => "hwy", "inlet" => "inlt", "island" => "is", "islands" => "iss", "isles" => "isle", "islnd" => "is", "islnds" => "iss", "jction" => "jct", "jctn" => "jct", "jctns" => "jcts", "junction" => "jct", "junctions" => "jcts", "junctn" => "jct", "juncton" => "jct", "key" => "ky", "keys" => "kys", "knol" => "knl", "knoll" => "knl", "knolls" => "knls", "la" => "ln", "lake" => "lk", "lakes" => "lks", "landing" => "lndg", "lane" => "ln", "lanes" => "ln", "ldge" => "ldg", "light" => "lgt", "lights" => "lgts", "lndng" => "lndg", "loaf" => "lf", "lock" => "lck", "locks" => "lcks", "lodg" => "ldg", "lodge" => "ldg", "loops" => "loop", "manor" => "mnr", "manors" => "mnrs", "meadow" => "mdw", "meadows" => "mdws", "medows" => "mdws", "mill" => "ml", "mills" => "mls", "mission" => "msn", "missn" => "msn", "mnt" => "mt", "mntain" => "mtn", "mntn" => "mtn", "mntns" => "mtns", "motorway" => "mtwy", "mount" => "mt", "mountain" => "mtn", "mountains" => "mtns", "mountin" => "mtn", "mssn" => "msn", "mtin" => "mtn", "neck" => "nck", "orchard" => "orch", "orchrd" => "orch", "overpass" => "opas", "ovl" => "oval", "parks" => "park", "parkway" => "pkwy", "parkways" => "pkwy", "parkwy" => "pkwy", "passage" => "psge", "paths" => "path", "pikes" => "pike", "pine" => "pne", "pines" => "pnes", "pk" => "park", "pkway" => "pkwy", "pkwys" => "pkwy", "pky" => "pkwy", "place" => "pl", "plain" => "pln", "plaines" => "plns", "plains" => "plns", "plaza" => "plz", "plza" => "plz", "point" => "pt", "points" => "pts", "port" => "prt", "ports" => "prts", "prairie" => "pr", "prarie" => "pr", "prk" => "park", "prr" => "pr", "rad" => "radl", "radial" => "radl", "radiel" => "radl", "ranch" => "rnch", "ranches" => "rnch", "rapid" => "rpd", "rapids" => "rpds", "rdge" => "rdg", "rest" => "rst", "ridge" => "rdg", "ridges" => "rdgs", "river" => "riv", "rivr" => "riv", "rnchs" => "rnch", "road" => "rd", "roads" => "rds", "route" => "rte", "rvr" => "riv", "shoal" => "shl", "shoals" => "shls", "shoar" => "shr", "shoars" => "shrs", "shore" => "shr", "shores" => "shrs", "skyway" => "skwy", "spng" => "spg", "spngs" => "spgs", "spring" => "spg", "springs" => "spgs", "sprng" => "spg", "sprngs" => "spgs", "spurs" => "spur", "sqr" => "sq", "sqre" => "sq", "sqrs" => "sqs", "squ" => "sq", "square" => "sq", "squares" => "sqs", "station" => "sta", "statn" => "sta", "stn" => "sta", "str" => "st", "strav" => "stra", "strave" => "stra", "straven" => "stra", "stravenue" => "stra", "stravn" => "stra", "stream" => "strm", "street" => "st", "streets" => "sts", "streme" => "strm", "strt" => "st", "strvn" => "stra", "strvnue" => "stra", "sumit" => "smt", "sumitt" => "smt", "summit" => "smt", "terr" => "ter", "terrace" => "ter", "throughway" => "trwy", "tpk" => "tpke", "tr" => "trl", "trace" => "trce", "traces" => "trce", "track" => "trak", "tracks" => "trak", "trafficway" => "trfy", "trail" => "trl", "trails" => "trl", "trk" => "trak", "trks" => "trak", "trls" => "trl", "trnpk" => "tpke", "trpk" => "tpke", "tunel" => "tunl", "tunls" => "tunl", "tunnel" => "tunl", "tunnels" => "tunl", "tunnl" => "tunl", "turnpike" => "tpke", "turnpk" => "tpke", "underpass" => "upas", "union" => "un", "unions" => "uns", "valley" => "vly", "valleys" => "vlys", "vally" => "vly", "vdct" => "via", "viadct" => "via", "viaduct" => "via", "view" => "vw", "views" => "vws", "vill" => "vlg", "villag" => "vlg", "village" => "vlg", "villages" => "vlgs", "ville" => "vl", "villg" => "vlg", "villiage" => "vlg", "vist" => "vis", "vista" => "vis", "vlly" => "vly", "vst" => "vis", "vsta" => "vis", "walks" => "walk", "well" => "wl", "wells" => "wls", "wy" => "way", } CUSTOM_ABBREVIATIONS = { "county road" => "cr", "north" => "n", "south" => "s", "east" => "e", "west" => "w", } def normalize_string(string) string.to_s.gsub(/\s/, "").downcase end def normalize_abbreviations(string) string = string.to_s.downcase USPS_STREET_ABBREVIATIONS.merge(CUSTOM_ABBREVIATIONS).each do |name, abbreviation| string.gsub!(/\b#{name}\b/, abbreviation) end string end end end