430a90fd73d90e3bcf43c7e4a268ef64e60a137c
[freeside.git] / FS / FS / GeocodeCache.pm
1 package FS::GeocodeCache;
2
3 use strict;
4 use vars qw($conf $DEBUG);
5 use base qw( FS::geocode_Mixin );
6 use FS::Record qw( qsearch qsearchs );
7 use FS::Conf;
8 use FS::Misc::Geo;
9
10 use Data::Dumper;
11
12 FS::UID->install_callback( sub { $conf = new FS::Conf; } );
13
14 $DEBUG = 0;
15
16 =head1 NAME
17
18 FS::GeocodeCache - An address undergoing the geocode process.
19
20 =head1 SYNOPSIS
21
22   use FS::GeocodeCache;
23
24   $record = FS::GeocodeCache->standardize(%location_hash);
25
26 =head1 DESCRIPTION
27
28 An FS::GeocodeCache object represents a street address in the process of 
29 being geocoded.  FS::GeocodeCache inherits from FS::geocode_Mixin.
30
31 Most methods on this object throw an exception on error.
32
33 FS::GeocodeCache has the following fields, with the same meaning as in 
34 L<FS::cust_location>:
35
36 =over 4
37
38 =item address1
39
40 =item address2
41
42 =item city
43
44 =item county
45
46 =item state
47
48 =item zip
49
50 =item latitude
51
52 =item longitude
53
54 =item addr_clean
55
56 =item country
57
58 =item censustract
59
60 =item geocode
61
62 =item district
63
64 =back
65
66 =head1 METHODS
67
68 =over 4
69
70 =item new HASHREF
71
72 Creates a new cache object.  For internal use.  See C<standardize>.
73
74 =cut
75
76 # minimalist constructor
77 sub new {
78   my $class = shift;
79   my $self = {
80     company     => '',
81     address1    => '',
82     address2    => '',
83     city        => '',
84     state       => '',
85     zip         => '',
86     country     => '',
87     latitude    => '',
88     longitude   => '',
89     addr_clean  => '',
90     censustract => '',
91     @_
92   };
93   bless $self, $class;
94 }
95
96 # minimalist accessor, for compatibility with geocode_Mixin
97 sub get {
98   $_[0]->{$_[1]}
99 }
100
101 sub set {
102   $_[0]->{$_[1]} = $_[2];
103 }
104
105 sub location_hash { %{$_[0]} };
106
107 =item set_censustract
108
109 Look up the censustract, if it's not already filled in, and return it.
110 On error, sets 'error' and returns nothing.
111
112 This uses the "get_censustract_*" methods in L<FS::Misc::Geo>; currently
113 the only one is 'ffiec'.
114
115 =cut
116
117 sub set_censustract {
118   my $self = shift;
119
120   if ( $self->get('censustract') =~ /^\d{9}(\.\d{2}|\d{6})$/ ) {
121     return $self->get('censustract');
122   }
123
124   my $year = $conf->config('census_legacy') || 2020;
125   my $method = ($year==2020) ? 'uscensus' : 'ffiec';
126
127   $method = "get_censustract_$method";
128   my $censustract = eval { FS::Misc::Geo->$method($self, $year) };
129   $self->set("censustract_error", $@);
130   $self->set("censustract", $censustract);
131 }
132
133 =item set_coord
134
135 Set the latitude and longitude fields if they're not already set.  Returns
136 those values, in order.
137
138 =cut
139
140 sub set_coord { # the one in geocode_Mixin will suffice
141   my $self = shift;
142   if ( !$self->get('latitude') || !$self->get('longitude') ) {
143     $self->SUPER::set_coord;
144     $self->set('coord_error', $@);
145   }
146   return $self->get('latitude'), $self->get('longitude');
147 }
148
149 =head1 CLASS METHODS
150
151 =over 4
152
153 =item standardize LOCATION
154
155 Given a location hash or L<FS::geocode_Mixin> object, standardize the 
156 address using the configured method and return an L<FS::GeocodeCache> 
157 object.
158
159 The methods are the "standardize_*" functions in L<FS::Geo::Misc>.
160
161 =cut
162
163 sub standardize {
164   my $class = shift;
165   my $location = shift;
166   $location = { $location->location_hash }
167     if UNIVERSAL::can($location, 'location_hash');
168
169   local $Data::Dumper::Terse = 1;
170   warn "standardizing location:\n".Dumper($location) if $DEBUG;
171
172   my $method = $conf->config('address_standardize_method');
173
174   if ( $method ) {
175     $method = "standardize_$method";
176     my $new_location = eval { FS::Misc::Geo->$method( $location ) };
177     if ( $new_location ) {
178       $location = {
179         addr_clean => 'Y',
180         %$new_location
181         # standardize_* can return an address with addr_clean => '' if
182         # the address is somehow questionable
183       }
184     }
185     else {
186       # XXX need an option to decide what to do on error
187       $location->{'addr_clean'} = '';
188       $location->{'error'} = $@;
189     }
190     warn "result:\n".Dumper($location) if $DEBUG;
191   }
192   # else $location = $location
193   my $cache = $class->new(%$location);
194   return $cache;
195 }
196
197 =back
198
199 =head1 BUGS
200
201 =head1 SEE ALSO
202
203 L<FS::Record>, schema.html from the base documentation.
204
205 =cut
206
207 1;
208