File Coverage

blib/lib/Geo/Point.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 5     5   281270 use strict;
  5         14  
  5         223  
7 5     5   43 use warnings;
  5         14  
  5         240  
8              
9             package Geo::Point;
10 5     5   29 use vars '$VERSION';
  5         11  
  5         358  
11             $VERSION = '0.96';
12              
13 5     5   28 use base 'Geo::Shape';
  5         9  
  5         3254  
14              
15             use Geo::Proj;
16             use Carp qw/confess croak/;
17              
18              
19             sub init($)
20             { my ($self, $args) = @_;
21              
22             $self->SUPER::init($args);
23             $self->{GP_x} = defined $args->{x} ? $args->{x}
24             : defined $args->{long} ? $args->{long}
25             : $args->{longitude};
26             $self->{GP_y} = defined $args->{y} ? $args->{y}
27             : defined $args->{lat} ? $args->{lat}
28             : $args->{latitude};
29             $self;
30             }
31              
32              
33             sub latlong(@)
34             { my $thing = shift;
35              
36             if(ref $thing) # instance method
37             { return ($thing->{GP_y}, $thing->{GP_x}) unless @_ > 2;
38              
39             my $proj = pop @_;
40             return $thing->in($proj)->latlong;
41             }
42              
43             # class method
44             $thing->new(lat => shift, long => shift, proj => shift);
45             }
46              
47              
48             sub longlat(@)
49             { my $thing = shift;
50              
51             if(ref $thing) # instance method
52             { return ($thing->{GP_x}, $thing->{GP_y}) unless @_ > 2;
53             my $proj = pop @_;
54             return $thing->in($proj)->longlat;
55             }
56              
57             # class method
58             $thing->new(long => shift, lat => shift, proj => shift);
59             }
60              
61              
62             sub xy(@)
63             { my $thing = shift;
64              
65             if(ref $thing) # instance method
66             { return ($thing->{GP_x}, $thing->{GP_y}) unless @_ > 2;
67              
68             my $proj = pop @_;
69             return $thing->in($proj)->xy;
70             }
71              
72             # class method
73             $thing->new(x => shift, y => shift, proj => shift);
74             }
75              
76              
77             sub yx(@)
78             { my $thing = shift;
79              
80             if(ref $thing) # instance method
81             { return ($thing->{GP_y}, $thing->{GP_x}) unless @_ > 2;
82              
83             my $proj = pop @_;
84             return $thing->in($proj)->yx;
85             }
86              
87             # class method
88             $thing->new(y => shift, x => shift, proj => shift);
89             }
90              
91              
92             sub fromString($;$)
93             { my ($class, $string, $nick) = @_;
94              
95             defined $string or return;
96             $string =~ s/^\s+//;
97             $string =~ s/\s+$//;
98             return () unless length $string;
99              
100             # line starts with project label
101             $nick = $1 if $string =~ s/^(\w+)\s*\:\s*//;
102              
103             # The line is either split by comma's or by blanks.
104             my @parts
105             = $string =~ m/\,/
106             ? (split /\s*\,\s*/, $string)
107             : (split /\s+/, $string);
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 = shift @parts; # overrules default
113             }
114              
115             my $proj;
116             if(!defined $nick)
117             { $proj = Geo::Proj->defaultProjection;
118             $nick = $proj->nick;
119             }
120             elsif($nick eq 'utm')
121             { die "ERROR: UTM requires 3 values: easting, northing, and zone\n"
122             unless @parts==3;
123              
124             my $zone;
125             if($parts[0] =~ m/^\d\d?[C-HJ-NP-X]?$/i )
126             { $zone = shift @parts;
127             }
128             elsif($parts[2] =~ m/^\d\d?[C-HJ-NP-X]?$/i )
129             { $zone = pop @parts;
130             }
131              
132             if(!defined $zone || $zone==0 || $zone > 60)
133             { die "ERROR: illegal UTM zone in $string";
134             }
135              
136             $proj = Geo::Proj->UTMprojection(undef, $zone);
137             $nick = $proj->nick;
138             }
139             else
140             { $proj = Geo::Proj->projection($nick)
141             or croak "ERROR: undefined projection $nick";
142             }
143              
144             croak "ERROR: too few values in '$string' (got ".@parts.", expect 2)\n"
145             if @parts < 2;
146              
147             croak "ERROR: too many values in '$string' (got ".@parts.", expect 2)\n"
148             if @parts > 2;
149              
150             if($proj->proj4->isLatlong)
151             { my ($lats, $longs)
152             = ( $parts[0] =~ m/[ewEW]$/ || $parts[1] =~ m/[nsNS]$/
153             || $parts[0] =~ m/^[ewEW]/ || $parts[1] =~ m/^[nsNS]/
154             )
155             ? reverse(@parts) : @parts;
156              
157             my $lat = $class->dms2deg($lats);
158             defined $lat
159             or die "ERROR: dms latitude coordinate not understood: $lats\n";
160              
161             my $long = $class->dms2deg($longs);
162             defined $long
163             or die "ERROR: dms longitude coordinate not understood: $longs\n";
164              
165             return $class->new(lat => $lat, long => $long, proj => $nick);
166             }
167             else # type eq xy
168             { my ($x, $y) = @parts;
169             die "ERROR: illegal character in x coordinate $x"
170             unless $x =~ m/^\d+(?:\.\d+)$/;
171              
172             die "ERROR: illegal character in y coordinate $y"
173             unless $y =~ m/^\d+(?:\.\d+)$/;
174              
175             return $class->new(x => $x, y => $y, proj => $nick);
176             }
177              
178             ();
179             }
180              
181             #----------------
182              
183             sub longitude() {shift->{GP_x}}
184             sub long() {shift->{GP_x}}
185             sub latitude() {shift->{GP_y}}
186             sub lat() {shift->{GP_y}}
187              
188             sub x() {shift->{GP_x}}
189             sub y() {shift->{GP_y}}
190              
191             #----------------
192              
193             sub in($)
194             { my ($self, $newproj) = @_;
195              
196             # Dirty hacks violate OO, to improve the speed.
197             return $self if $newproj eq $self->{G_proj};
198              
199             my ($n, $p) = $self->projectOn($newproj, [$self->{GP_x}, $self->{GP_y}]);
200             $p ? ref($self)->new(x => $p->[0], y => $p->[1], proj => $n) : $self;
201             }
202              
203              
204             sub normalize()
205             { my $self = shift;
206             my $p = Geo::Proj->projection($self->proj);
207             $p && $p->proj4->isLatlong or return $self;
208             my ($x, $y) = @$self{'GP_x','GP_y'};
209             $x += 360 while $x < -180;
210             $x -= 360 while $x > 180;
211             $y += 180 while $y < -90;
212             $y -= 180 while $y > 90;
213             @$self{'GP_x','GP_y'} = ($x, $y);
214             $self;
215             }
216              
217             #----------------
218              
219             sub bbox() { @{(shift)}[ qw/GP_x GP_y GP_x GP_y/ ] }
220              
221              
222             sub area() { 0 }
223              
224              
225             sub perimeter() { 0 }
226              
227              
228             # When two points are within one UTM zone, this could be done much
229             # easier...
230              
231             sub distancePointPoint($$$)
232             { my ($self, $geodist, $units, $other) = @_;
233              
234             my $here = $self->in('wgs84');
235             my $there = $other->in('wgs84');
236             $geodist->distance($units, $here->latlong, $there->latlong);
237             }
238              
239              
240             sub sameAs($$)
241             { my ($self, $other, $e) = (shift, shift);
242              
243             croak "ERROR: can only compare a point to another Geo::Point"
244             unless $other->isa('Geo::Point');
245              
246             # may be latlong or xy, doesn't matter: $e is corrected for that
247             my($x1, $y1) = $self->xy;
248             my($x2, $y2) = $other->xy;
249             abs($x1-$x2) < $e && abs($y1-$y2) < $e;
250             }
251              
252              
253             sub inBBox($)
254             { my ($self, $other) = @_;
255             my ($x, $y) = $self->in($other->proj)->xy;
256             my ($xmin, $ymin, $xmax, $ymax) = $other->bbox;
257             $xmin <= $x && $x <= $xmax && $ymin <= $y && $y <= $ymax
258             }
259              
260             #----------------
261              
262             sub coordsUsualOrder()
263             { my $self = shift;
264             my $p = Geo::Proj->projection($self->proj);
265             $p && $p->proj4->isLatlong ? $self->latlong : $self->xy;
266             }
267              
268              
269             sub coords()
270             { my ($a, $b) = shift->coordsUsualOrder;
271             defined $a && defined $b or return '(none)';
272              
273             sprintf "%.4f %.4f", $a, $b;
274             }
275              
276              
277             sub toString(;$)
278             { my ($self, $proj) = @_;
279             my $point;
280              
281             if(defined $proj)
282             { $point = $self->in($proj);
283             }
284             else
285             { $proj = $self->proj;
286             $point = $self;
287             }
288              
289             "point[$proj](" .$point->coords.')';
290             }
291             *string = \&toString;
292              
293              
294             sub dms(;$)
295             { my ($self, $proj) = @_;
296             my ($long, $lat) = $proj ? $self->in($proj)->longlat : $self->longlat;
297              
298             my $dmslat = $self->deg2dms($lat, 'N', 'S');
299             my $dmslong = $self->deg2dms($long, 'E', 'W');
300             wantarray ? ($dmslat, $dmslong) : "$dmslat, $dmslong";
301             }
302              
303              
304             sub dm(;$)
305             { my ($self, $proj) = @_;
306             my ($long, $lat) = $proj ? $self->in($proj)->longlat : $self->longlat;
307              
308             my $dmlat = $self->deg2dm($lat, 'N', 'S');
309             my $dmlong = $self->deg2dm($long, 'E', 'W');
310             wantarray ? ($dmlat, $dmlong) : "$dmlat, $dmlong";
311             }
312              
313              
314             sub dmsHTML(;$)
315             { my ($self, $proj) = @_;
316             my @both = $self->dms($proj);
317             foreach (@both)
318             { s/"/\"/g;
319             # The following two translations are nice, but IE does not handle
320             # them correctly when uses as values in form fields.
321             # s/d/\°/g;
322             # s/ /\ \ /g;
323             }
324             wantarray ? @both : "$both[0], $both[1]";
325             }
326              
327              
328             sub dmHTML(;$)
329             { my ($self, $proj) = @_;
330             my @both = $self->dm($proj);
331             foreach (@both)
332             { s/"/\"/g;
333             # See dmsHTML above
334             # s/d/\°/g;
335             # s/ /\ \ /g;
336             }
337             wantarray ? @both : "$both[0], $both[1]";
338             }
339              
340              
341             sub moveWest()
342             { my $self = shift;
343             $self->{GP_x} -= 360 if $self->{GP_x} > 0;
344             }
345              
346              
347             1;