File Coverage

blib/lib/Geo/Line.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             # Copyrights 2005-2014 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.01.
5              
6 1     1   58864 use strict;
  1         3  
  1         43  
7 1     1   5 use warnings;
  1         3  
  1         47  
8              
9             package Geo::Line;
10 1     1   6 use vars '$VERSION';
  1         3  
  1         88  
11             $VERSION = '0.96';
12              
13 1     1   7 use base qw/Geo::Shape Math::Polygon/;
  1         2  
  1         616  
14              
15             use Carp;
16             use List::Util qw/min max/;
17             use Scalar::Util qw/refaddr/;
18              
19              
20             sub new(@)
21             { my ($thing, %args) = @_;
22             if(my $points = $args{points})
23             { @$points >= 2
24             or croak "ERROR: line needs at least two points";
25              
26             my $proj = $args{proj};
27             foreach my $p (@$points)
28             { next unless UNIVERSAL::isa($p, 'Geo::Point');
29             $proj ||= $p->proj;
30             $p = [ $p->xy($proj) ]; # replace
31             }
32             $args{proj} = $proj;
33             }
34              
35             ref $thing
36             or return shift->Math::Polygon::new(%args);
37              
38             # instance method: clone!
39             $thing->Math::Polygon::new
40             ( ring => $thing->{GL_ring}
41             , filled => $thing->{GL_fill}
42             , proj => $thing->proj
43             , %args
44             );
45             }
46              
47             sub init($)
48             { my ($self, $args) = @_;
49             $self->Geo::Shape::init($args);
50              
51             $self->Math::Polygon::init($args);
52              
53             $self->{GL_ring} = $args->{ring} || $args->{filled};
54             $self->{GL_fill} = $args->{filled};
55             $self->{GL_bbox} = $args->{bbox};
56             $self;
57             }
58              
59              
60             sub line(@)
61             { my $thing = shift;
62             my @points;
63             push @points, shift while @_ && ref $_[0];
64             $thing->new(points => \@points, @_);
65             }
66              
67              
68             sub ring(@)
69             { my $thing = shift;
70             my $self = $thing->line(@_, ring => 1);
71             my $points = $self->points;
72              
73             my ($first, $last) = @$points[0, -1];
74             push @$points, $first
75             unless $first->[0] == $last->[0] && $first->[1] == $last->[1];
76             $self;
77             }
78              
79              
80             sub filled(@)
81             { my $thing = shift;
82             $thing->ring(@_, filled => 1);
83             }
84              
85              
86             sub bboxFromString($;$)
87             { my ($class, $string, $nick) = @_;
88              
89             $string =~ s/^\s+//;
90             $string =~ s/\s+$//;
91             return () unless length $string;
92              
93             # line starts with project label
94             $nick = $1 if $string =~ s/^(\w+)\s*\:\s*//;
95              
96             # Split the line
97             my @parts = $string =~ m/\,/ ? split(/\s*\,\s*/, $string) : ($string);
98              
99             # expand dashes
100             @parts = map { m/^([nesw])(\d.*?)\s*\-\s*(\d.*?)\s*$/i ? ($1.$2, $1.$3)
101             : m/^(\d.*?)([nesw])\s*\-\s*(\d.*?)\s*$/i ? ($2.$1, $2.$3)
102             : m/^(\d.*?)\s*\-\s*(\d.*?)\s*([nesw])\s*$/i ? ($1.$3, $2.$3)
103             : $_
104             } @parts;
105              
106             # split on blanks
107             @parts = map { split /\s+/, $_ } @parts;
108              
109             # Now, the first word may be a projection. That is: any non-coordinate,
110             # anything which starts with more than one letter.
111             if($parts[0] =~ m/^[a-z_]{2}/i)
112             { $nick = lc(shift @parts); # overrules default
113             }
114              
115             $nick ||= Geo::Proj->defaultProjection;
116             my $proj = Geo::Proj->projection($nick);
117              
118             die "ERROR: Too few values in $string (got @parts, expect 4)\n"
119             if @parts < 4;
120              
121             die "ERROR: Too many values in $string (got @parts, expect 4)"
122             if @parts > 4;
123              
124             unless($proj)
125             { die "ERROR: No projection defined for $string\n";
126             return undef;
127             }
128              
129             if(! $proj->proj4->isLatlong)
130             { die "ERROR: can only handle latlong coordinates, on the moment\n";
131             }
132              
133             my(@lats, @longs);
134             foreach my $part (@parts)
135             { if($part =~ m/[ewEW]$/ || $part =~ m/^[ewEW]/)
136             { my $lat = $class->dms2deg($part);
137             defined $lat
138             or die "ERROR: dms latitude coordinate not understood: $part\n";
139             push @lats, $lat;
140             }
141             else
142             { my $long = $class->dms2deg($part);
143             defined $long
144             or die "ERROR: dms longitude coordinate not understood: $part\n";
145             push @longs, $long;
146             }
147             }
148              
149             die "ERROR: expect two lats and two longs, but got "
150             . @lats."/".@longs."\n" if @lats!=2;
151              
152             (min(@lats), min(@longs), max(@lats), max(@longs), $nick);
153             }
154              
155              
156              
157             sub ringFromString($;$)
158             { my $class = shift;
159             my ($xmin, $ymin, $xmax, $ymax, $nick) = $class->bboxFromString(@_)
160             or return ();
161              
162             $class->bboxRing($xmin, $ymin, $xmax, $ymax, $nick);
163             }
164              
165              
166             sub geopoints()
167             { my $self = shift;
168             my $proj = $self->proj;
169              
170             map { Geo::Point->new(x => $_->[0], y => $_->[1], proj => $proj) }
171             $self->points;
172             }
173              
174              
175             sub geopoint(@)
176             { my $self = shift;
177             my $proj = $self->proj;
178              
179             unless(wantarray)
180             { my $p = $self->point(shift) or return ();
181             return Geo::Point->(x => $p->[0], y => $p->[1], proj => $proj);
182             }
183              
184             map { Geo::Point->(x => $_->[0], y => $_->[1], proj => $proj) }
185             $self->point(@_);
186              
187             }
188              
189              
190             sub isRing()
191             { my $self = shift;
192             return $self->{GL_ring} if defined $self->{GL_ring};
193              
194             my ($first, $last) = $self->points(0, -1);
195             $self->{GL_ring} = ($first->[0]==$last->[0] && $first->[1]==$last->[1]);
196             }
197              
198              
199             sub isFilled() { shift->{GL_fill} }
200              
201             #----------------
202              
203             sub in($)
204             { my ($self, $projnew) = @_;
205             return $self if ! defined $projnew || $projnew eq $self->proj;
206              
207             # projnew can be 'utm'
208             my ($realproj, @points) = $self->projectOn($projnew, $self->points);
209              
210             @points ? $self->new(points => \@points, proj => $realproj) : $self;
211             }
212              
213             #----------------
214              
215             sub equal($;$)
216             { my $self = shift;
217             my $other = shift;
218              
219             return 0 if $self->nrPoints != $other->nrPoints;
220              
221             $self->Math::Polygon::equal($other->in($self->proj), @_);
222             }
223              
224              
225             sub bbox() { shift->Math::Polygon::bbox }
226              
227              
228             sub area()
229             { my $self = shift;
230              
231             croak "ERROR: area requires a ring of points"
232             unless $self->isRing;
233              
234             $self->Math::Polygon::area;
235             }
236              
237              
238             sub perimeter()
239             { my $self = shift;
240              
241             croak "ERROR: perimeter requires a ring of points."
242             unless $self->isRing;
243              
244             $self->Math::Polygon::perimeter;
245             }
246              
247              
248             sub length() { shift->Math::Polygon::perimeter }
249              
250              
251             sub clip(@)
252             { my $self = shift;
253             my $proj = $self->proj;
254             my @bbox = @_==1 ? $_[0]->bbox : @_;
255             $self->isFilled ? $self->fillClip1(@bbox) : $self->lineClip(@bbox);
256             }
257              
258             #----------------
259              
260             sub toString(;$)
261             { my ($self, $proj) = @_;
262             my $line;
263             if(defined $proj)
264             { $line = $self->in($proj);
265             }
266             else
267             { $proj = $self->proj;
268             $line = $self;
269             }
270              
271             my $type = $line->isFilled ? 'filled'
272             : $line->isRing ? 'ring'
273             : 'line';
274              
275             "$type\[$proj](".$line->Math::Polygon::string.')';
276             }
277             *string = \&toString;
278              
279             1;