Class: Address::NormalizedAddress

Inherits:
Object
  • Object
show all
Defined in:
app/services/address/normalized_address.rb

Overview

require 'geocoder'
require 'bigdecimal'
require 'set'

Constant Summary collapse

ACCEPTABLE_TYPES =

Geocoder::Configuration.lookup = :google

Set.new(["street_address", "premise", "subpremise", "establishment"])
Attrs =
[:address_line1, :address_line2, :city, :state_province, :state_code,
:postal_code, :country, :country_iso, :latitude, :longitude, :formatted_address]

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ NormalizedAddress

Returns a new instance of NormalizedAddress.



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
# File 'app/services/address/normalized_address.rb', line 18

def initialize(data)
  @address_line1  = nil
  @address_line2  = nil
  @city           = nil
  @postal_code    = nil
  @state_province = nil
  @country        = nil
  @country_iso    = nil
  @latitude       = nil
  @longitude      = nil
  @state_code     = nil
  # The Google result consists of:
  #
  # - An array ("address_components") of hashes consisting of
  #   {"long_name" => "...", "short_name" => "...", "types" => [...]}
  # - A "geometry" hash, with the latitude and longitude
  # - A "partial_match" indicator (which we're ignoring)
  # - A "types" array (which we're also ignoring)

  data["address_components"].each do |hash|
    types = hash["types"]
    value = hash["long_name"]
    next unless value.present?
    short_value = hash["short_name"]
    if types.include? "subpremise"
      @address_line2 = value
      @address_line2.insert(0, '#') unless @address_line2.starts_with?('#')
    elsif types.include? "street_number"
      @house_number = value
    elsif types.include? "sublocality"
      @city = value
    elsif types.include? "locality"
      @city = value if @city.nil?
    elsif types.include? "country"
      @country = value
      @country_iso = short_value
    elsif types.include? "postal_code"
      @postal_code = value
    elsif types.include? "route"
      @street = value
    elsif types.include? "administrative_area_level_1"
      @state_province = value
      @state_code = short_value
    elsif types.include? "establishment"
      @establishment = value
    end
  end

  @address_line1 = "#{@house_number} #{@street}".squish.presence || @establishment

  if data["formatted_address"]
    @formatted_address = data["formatted_address"]
  else
    @formatted_address = [
      @address_line1, @address_line2, @city, @state_province, @postal_code
    ].select {|s| ! (s.nil? || s.empty?)}.join(' ')
  end

  # Latitude and longitude

  if data["geometry"] and data["geometry"]["location"]
    loc = data["geometry"]["location"]
    @latitude = BigDecimal(loc["lat"].to_s)
    @longitude = BigDecimal(loc["lng"].to_s)
  end
end

Class Method Details

.from_string(raw_address_string) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'app/services/address/normalized_address.rb', line 94

def self.from_string(raw_address_string)
  
  normalized_address = nil

  # Geocoder.search() returns an array of results. Take the first one.
  geocoded = Geocoder.search(raw_address_string)
  if geocoded && (geocoded.length > 0)
    # Geocoder returns data that may or may not be granular enough. For
    # instance, attempting to retrieve information about nonexistent
    # address '100 Main St, XYZ, PA, US' still returns a value, but the
    # value's type is "administrative_area_level_1", which means the data
    # is granular to a (U.S.) state. If it's a valid address, we should
    # get data that's more granular than that. Of the codes listed at
    # http://code.google.com/apis/maps/documentation/geocoding/#Types
    # we're interested in "street_address", "premise" and
    # "subpremise".
    data = geocoded[0].data
    types = Set.new(data["types"])
    if !(types & ACCEPTABLE_TYPES).empty?
      normalized_address = new(data)
    end
  end

  normalized_address
end

Instance Method Details

#inspectObject



89
90
91
# File 'app/services/address/normalized_address.rb', line 89

def inspect
  Hash[ Attrs.map {|field| [field, self.send(field)]} ]
end

#to_sObject



85
86
87
# File 'app/services/address/normalized_address.rb', line 85

def to_s
  @formatted_address
end