File Coverage

blib/lib/Geo/Walkabout/Line.pm
Criterion Covered Total %
statement 12 96 12.5
branch 0 26 0.0
condition 0 9 0.0
subroutine 4 16 25.0
pod n/a
total 16 147 10.8


line stmt bran cond sub pod time code
1             # $Id: Line.pm,v 1.1.1.1 2000/12/05 00:55:01 schwern Exp $
2              
3             # Somebody's going to want to graft this thing to another database.
4             # May as well plan for it.
5             package Geo::Walkabout::Line;
6             @ISA = qw(Geo::Walkabout::Line::PostgreSQL);
7              
8              
9             package Geo::Walkabout::Line::PostgreSQL;
10              
11 1     1   1928 use strict;
  1         3  
  1         46  
12 1     1   6 use vars qw($VERSION);
  1         3  
  1         58  
13              
14             $VERSION = '0.01';
15              
16 1     1   6 use base qw(Geo::Walkabout::Class::DBI);
  1         2  
  1         679  
17              
18             require Geo::Walkabout::Chain;
19 1     1   904 use Carp::Assert;
  1         1289  
  1         6  
20              
21             __PACKAGE__->table('Line_Feature');
22              
23             __PACKAGE__->columns(Primary => 'TLID');
24             __PACKAGE__->columns(Essential => qw( TLID FeName Chain ));
25              
26             __PACKAGE__->columns(Feature => qw( FeDirP FeName FeType FeDirS ));
27             __PACKAGE__->columns(Zip => qw( ZipL Zip4L ZipR Zip4R ));
28             __PACKAGE__->columns(Chain => qw( Chain_Start Chain_End Chain
29             Chain_Length));
30              
31              
32             =pod
33              
34             =head1 NAME
35              
36             Geo::Walkabout::Line - A "line feature"
37              
38              
39             =head1 SYNOPSIS
40              
41             require Geo::Walkabout::Line;
42              
43             my $line = Geo::Walkabout::Line->retrieve($tlid);
44             my $line = Geo::Walkabout::Line->new(\%data);
45              
46             $line->delete;
47              
48             my $id = $line->id;
49             my $name = $line->name;
50              
51             my($zip_left, $zip_right) = $line->zip;
52              
53             my @address_ranges = $line->addresses_left;
54             my @address_ranges = $line->addresses_right;
55             $line->add_addresses($side, @address_ranges);
56              
57             my $chain = $line->chain;
58              
59             $line->TLID;
60              
61             $line->FeDirP;
62             $line->FeName;
63             $line->FeType;
64             $line->FeDirS;
65              
66             $line->ZipL;
67             $line->Zip4L;
68             $line->ZipR;
69             $line->Zip4R;
70              
71             $line->commit;
72             $line->rollback;
73              
74              
75             =head1 DESCRIPTION
76              
77             This represents a complete "line feature". Roads, waterways, fences,
78             power lines, railroads, boundries... See chapter 3 of the TIGER/Line
79             documentation for details.
80              
81              
82             =head1 Public Methods
83              
84             =head2 Constructors and Destructors
85              
86             Geo::Walkabout::Line is a subclass of Class::DBI and expects to be
87             stored in a database.
88              
89             =over 4
90              
91             =item B
92              
93             my $line = Geo::Walkabout::Line->retrieve($id);
94              
95             Retrieve an existing line feature from the database by its ID.
96              
97             =item B
98              
99             my $line = Geo::Walkabout::Line->new(\%data);
100              
101             Create a new line feature. %data must contain the following fields...
102              
103             TLID Unique TIGER/Line ID
104             Chain A Geo::Walkabout::Chain object representing the line
105              
106             And optionally contain these...
107              
108             FeDirP See accessor descriptions below
109             FeName for what these are
110             FeType
111             FeDirS
112              
113             ZipL
114             Zip4L
115             ZipR
116             Zip4R
117              
118             =cut
119              
120             sub new {
121 0     0     my($proto, $data) = @_;
122              
123 0           assert( $data->{Chain}->isa('Geo::Walkabout::Chain') ) if DEBUG;
124 0           my $chain_obj = $data->{Chain};
125 0           $data->{Chain} = $data->{Chain}->as_pgpath;
126 0           $data->{Chain_Start} = $chain_obj->to_pgpoint($chain_obj->begin);
127 0           $data->{Chain_End} = $chain_obj->to_pgpoint($chain_obj->end);
128              
129             # Blank values translate to null values.
130 0           foreach my $key (keys %$data) {
131 0 0         $data->{$key} = undef unless length $data->{$key};
132             }
133              
134 0           my $self = $proto->SUPER::new($data);
135              
136 0           $self->{chain_obj} = $chain_obj;
137              
138 0           return $self;
139             }
140              
141             =pod
142              
143             =item B
144              
145             $line->delete;
146              
147             Deletes this line feature from the database B.
148              
149             =back
150              
151              
152             =head2 Accessors
153              
154             =over 4
155              
156              
157             =item B
158              
159             my $id = $line->id
160              
161             Returns a unique ID for this object, not necessarily the same as TLID.
162              
163             B Do I, I repeat, do I use attempt to use the TLID as
164             a unique identifer for a Geo::Walkabout::Line object. While the TLID is
165             unique, it is not guaranteed that all Geo::Walkabout::Line objects will
166             have one. Geo::Walkabout::Line objects will come from many sources.
167              
168             =item B
169              
170             my $tlid = $line->TLID;
171              
172             Returns the TIGER/Line ID for this object.
173              
174              
175             =item B
176              
177             my $name = $line->name;
178              
179             The complete name of this feature. Its roughly equivalent to:
180              
181             my $name = join ' ', $line->FeDirP, $line->FeName, $line->FeType,
182             $line->FeDirS;
183              
184             For example: "Elford PL"
185              
186             =cut
187              
188             sub name {
189 0     0     my($self) = shift;
190              
191 0           my @name = ($self->FeDirP, $self->FeName, $self->FeType, $self->FeDirS);
192 0 0         return join ' ', grep { defined && length } @name;
  0            
193             }
194              
195             =pod
196              
197             =item B
198              
199             my($zip_left, $zip_right) = $line->zip;
200              
201             The zip code for the left and right side of this line. Zip may be the
202             5 digit zip code or the 9 digit zip +4.
203              
204             =cut
205              
206             sub zip {
207 0     0     my($self) = shift;
208              
209 0           my $zip4L = $self->Zip4L;
210 0           my $zip4R = $self->Zip4R;
211              
212 0           my($zip_left, $zip_right);
213              
214 0           $zip_left = sprintf "%05d", $self->ZipL;
215 0 0         $zip_left .= sprintf "%04d", $zip4L if defined $zip4L;
216              
217 0           $zip_right = sprintf "%05d", $self->ZipR;
218 0 0         $zip_right .= sprintf "%04d", $zip4R if defined $zip4R;
219            
220              
221 0           return($zip_left, $zip_right);
222             }
223              
224             =pod
225              
226             =item B
227              
228             =item B
229              
230             my @address_ranges = $line->addresses_left;
231             my @address_ranges = $line->addresses_right;
232              
233             The possible addresses on the left side of this line. @address_ranges
234             is a list of range pairs (two element array refs). A range with the
235             same start and end number (such as [146,146]) represents a single
236             anomalous address.
237              
238             The order is meaningless.
239              
240             For example:
241              
242             # Represents that the addresses descend from 290 to 200 from the
243             # start of the line to the end. There is also a single outstanding
244             # address #146 and an additional range of addresses from 20 to 10.
245             # So 10-20, 146 and 200-290.
246             ([290,200],[146,146],[20,10])
247              
248             =cut
249              
250             sub addresses_left {
251 0     0     my($self) = shift;
252              
253 0 0         $self->_get_addresses unless defined $self->{addresses}{L};
254              
255 0           return @{$self->{addresses}{L}};
  0            
256             }
257              
258             sub addresses_right {
259 0     0     my($self) = shift;
260              
261 0 0         $self->_get_addresses unless defined $self->{addresses}{L};
262              
263 0           return @{$self->{addresses}{R}};
  0            
264             }
265              
266             __PACKAGE__->set_sql('GetAddresses', <
267             SELECT Start_Addr, End_Addr, Side
268             FROM Address_Range
269             WHERE TLID = ?
270             SQL
271              
272             sub _get_addresses {
273 0     0     my($self) = shift;
274              
275 0           my $sth;
276 0           my($from, $to, $side); # bind columns.
277 0           eval {
278 0           $sth = $self->sql_GetAddresses;
279 0           $sth->execute([$self->id], [\($from, $to, $side)]);
280             };
281 0 0         if($@) {
282 0           $self->DBIwarn($self->id, '_get_addresses');
283 0           return;
284             }
285              
286 0           $self->{addresses}{L} = [];
287 0           $self->{addresses}{R} = [];
288 0           while( $sth->fetch ) {
289 0           push @{$self->{addresses}{uc $side}}, [$from, $to];
  0            
290             }
291              
292 0           $sth->finish;
293             }
294              
295             =pod
296              
297             =item B
298              
299             $line->add_addresses($side, @address_ranges);
300              
301             Addes a new address range to this line on the given $side. $side is
302             either 'R' or 'L'. @address_range is a list of two element array
303             references representing possible addresses on that side of the street.
304             The ordering is from the start of the chain to the end.
305              
306             =cut
307              
308             __PACKAGE__->set_sql('AddAddress', <
309             INSERT INTO Address_Range
310             (TLID, Start_Addr, End_Addr, Side)
311             VALUES (?, ?, ?, ? )
312             SQL
313              
314              
315             sub add_addresses {
316 0     0     my($self, $side, @address_ranges) = @_;
317              
318 0   0       assert($side eq 'R' or $side eq 'L') if DEBUG;
319              
320 0           my $sth;
321 0           $sth = $self->sql_AddAddress;
322 0           foreach my $address_range (@address_ranges) {
323 0           assert(@$address_range == 2) if DEBUG;
324            
325 0           eval {
326 0           $sth->execute($self->id, @{$address_range}, $side);
  0            
327             };
328 0 0         if ($@) {
329 0           $self->DBIwarn($self->id, 'add_address');
330 0           return;
331             }
332             }
333              
334 0           $sth->finish;
335             }
336              
337             =pod
338              
339             =item B
340              
341             my $chain = $line->chain;
342             $line->chain($chain);
343              
344             The Geo::Walkabout::Chain object representing the shape of this line
345             feature. This is the important bit, the line's actual location in the
346             world. L for details.
347              
348             =cut
349              
350             #'#
351             sub chain {
352 0     0     my($self) = shift;
353 0           my($chain) = @_;
354              
355 0 0         if(@_) {
356 0 0         Carp::carp("This is not a Geo::Walkabout::Chain object")
357             unless $chain->isa("Geo::Walkabout::Chain");
358 0           $self->{chain_obj} = $chain;
359 0           $self->_Chain_accessor($chain->as_pgpath);
360             }
361            
362 0 0         unless( defined $self->{chain_obj} ) {
363 0           $self->{chain_obj} =
364             Geo::Walkabout::Chain->new_from_pgpath($self->_Chain_accessor);
365             }
366              
367 0           return $self->{chain_obj};
368             }
369              
370             =pod
371              
372             =item B
373              
374             my $fedirp = $line->FeDirP;
375             $line->FeDirP($fedirp);
376              
377             Feature Direction Prefix. For example, if you had "North Southington
378             Road", "N" would be the FeDirP.
379              
380             Possible values are "N", "NE", "NW", "S", "SE", "SW", "E", "W", "EX".
381             "EX" means "Extended" or "Extension".
382              
383             =cut
384              
385             sub FeDirP {
386 0     0     my($self) = shift;
387 0           my($fedirp) = @_;
388              
389 0 0 0       if(@_ and !_valid_fedir($fedirp)) {
390 0           Carp::carp("'$fedirp' is not a valid feature direction.");
391             }
392              
393 0           $self->_FeDirP_accessor(@_);
394             }
395              
396             sub _valid_fedir {
397 0     0     my($fedir) = shift;
398              
399 0           return $fedir =~ /^(?:N[EW]?|S[EW]?|E[X]?|W)$/;
400             }
401              
402             =pod
403              
404             =item B
405              
406             my $fename = $line->FeName;
407             $line->FeName($fename);
408              
409             Feature Name. Continuing the example, "Southington" is the FeName.
410              
411              
412             =item B
413              
414             my $fetype = $line->FeType;
415             $line->FeType($fetype);
416              
417             Feature Type. "Rd" would be the feature type from above. Standard
418             abbreviations can be found in Appendix D of the TIGER/Line
419             documentation.
420              
421              
422             =item B
423              
424             my $fedirs = $line->FeDirS;
425             $line->FeDirS($fedirs);
426              
427             Feature Type Suffix. Same as FeDirP, except it follows the feature
428             name. So for "Red Rock West", the FeDirS would be "W".
429              
430             =cut
431              
432             sub FeDirS {
433 0     0     my($self) = shift;
434 0           my($fedirs) = @_;
435              
436 0 0 0       if(@_ and !_valid_fedir($fedirs)) {
437 0           Carp::carp("'$fedirs' is not a valid feature direction.");
438             }
439              
440 0           $self->_FeDirS_accessor(@_);
441             }
442              
443             =pod
444              
445             =item B
446              
447             =item B
448              
449             my $zipl = $line->ZipL;
450             $line->ZipL($zipl);
451             my $zipr = $line->ZipR;
452             $line->ZipR($zipr);
453              
454             5 digit zip codes for the left and right side of this line.
455              
456             =item B
457              
458             =item B
459              
460             my $zip4l = $line->Zip4L;
461             $line->Zip4L($zip4l);
462             my $zip4r = $line->Zip4R;
463             $line->Zip4R($zip4r);
464              
465             4 digit +4 zip code extension for the left and right side of this line.
466              
467              
468             =back
469              
470             =head2 Other Methods
471              
472             =over 4
473              
474             =item B
475              
476             $line->commit;
477              
478             Commit changes made to this line to the database.
479              
480             =cut
481              
482             sub commit {
483 0     0     my($self) = shift;
484              
485 0           $self->_Chain_accessor($self->chain->as_pgpath);
486              
487 0           $self->SUPER::commit;
488             }
489              
490             =pod
491              
492             =item B
493              
494             $line->rollback;
495              
496             Throw away changes made to this line and refresh it from the database.
497             If an object is changed and destroyed without committing or rolling
498             back a warning will be thrown.
499              
500             =back
501              
502              
503             =head1 AUTHOR
504              
505             Michael G Schwern
506              
507              
508             =head1 SEE ALSO
509              
510             L, L, L
511              
512             =cut
513              
514             1;