File Coverage

lib/Geo/Spline.pm
Criterion Covered Total %
statement 89 89 100.0
branch 11 12 91.6
condition 3 6 50.0
subroutine 12 12 100.0
pod 5 7 71.4
total 120 126 95.2


line stmt bran cond sub pod time code
1             package Geo::Spline;
2              
3             =head1 NAME
4              
5             Geo::Spline - Calculate geographic locations between GPS fixes.
6              
7             =head1 SYNOPSIS
8              
9             use Geo::Spline;
10             my $p0={time=>1160449100.67, #seconds
11             lat=>39.197807, #degrees
12             lon=>-77.263510, #degrees
13             speed=>31.124, #m/s
14             heading=>144.8300}; #degrees clockwise from North
15             my $p1={time=>1160449225.66,
16             lat=>39.167718,
17             lon=>-77.242278,
18             speed=>30.615,
19             heading=>150.5300};
20             my $spline=Geo::Spline->new($p0, $p1);
21             my %point=$spline->point(1160449150);
22             print "Lat:", $point{"lat"}, ", Lon:", $point{"lon"}, "\n\n";
23              
24             my @points=$spline->pointlist();
25             foreach (@points) {
26             print "Lat:", $_->{"lat"}, ", Lon:", $_->{"lon"}, "\n";
27             }
28              
29             =head1 DESCRIPTION
30              
31             This program was developed to be able to calculate the position between two GPS fixes using a 2-dimensional 3rd order polynomial spline.
32              
33             f(t) = A + B(t-t0) + C(t-t0)^2 + D(t-t0)^3 #position in X and Y
34             f'(t) = B + 2C(t-t0) + 3D(t-t0)^2 #velocity in X and Y
35              
36             I did some simple Math (for an engineer with a math minor) to come up with these formulas to calculate the unknowns from our knowns.
37              
38             A = x0 # when (t-t0)=0 in f(t)
39             B = v0 # when (t-t0)=0 in f'(t)
40             C = (x1-A-B(t1-t0)-D(t1-t0)^3)/(t1-t0)^2 # solve for C from f(t)
41             C = (v1-B-3D(t1-t0)^2)/2(t1-t0) # solve for C from f'(t)
42             D = (v1(t1-t0)+B(t1-t0)-2x1+2A)/(t1-t0)^3 # equate C=C then solve for D
43              
44             =cut
45              
46 1     1   512 use strict;
  1         2  
  1         38  
47 1     1   6 use vars qw($VERSION);
  1         2  
  1         40  
48 1     1   751 use Geo::Constants qw{PI};
  1         584  
  1         63  
49 1     1   671 use Geo::Functions qw{deg_rad rad_deg round};
  1         839  
  1         442  
50              
51             $VERSION = sprintf("%d.%02d", q{Revision: 0.16} =~ /(\d+)\.(\d+)/);
52              
53             =head1 CONSTRUCTOR
54              
55             =head2 new
56              
57             my $spline=Geo::Spline->new($p0, $p1);
58              
59             =cut
60              
61             sub new {
62 1     1 1 471 my $this = shift();
63 1   33     8 my $class = ref($this) || $this;
64 1         2 my $self = {};
65 1         2 bless $self, $class;
66 1         5 $self->initialize(@_);
67 1         3 return $self;
68             }
69              
70             =head1 METHODS
71              
72             =cut
73              
74             sub initialize {
75 1     1 0 4 my $self = shift();
76 1         7 $self->{'pt0'}=shift();
77 1         3 $self->{'pt1'}=shift();
78 1         2 my $ellipsoid=$self->ellipsoid("WGS84");
79 1         3 my $dt=$self->{'pt1'}->{'time'} - $self->{'pt0'}->{'time'};
80 1 50       4 die ("Delta time must be greater than zero.") if ($dt<=0);
81 1         5 my ($A, $B, $C, $D)=$self->ABCD(
82             $self->{'pt0'}->{'time'},
83             $self->{'pt0'}->{'lat'} * $ellipsoid->polar_circumference / 360,
84             $self->{'pt0'}->{'speed'} * cos(rad_deg($self->{'pt0'}->{'heading'})),
85             $self->{'pt1'}->{'time'},
86             $self->{'pt1'}->{'lat'} * $ellipsoid->polar_circumference / 360,
87             $self->{'pt1'}->{'speed'} * cos(rad_deg($self->{'pt1'}->{'heading'})));
88 1         5 $self->{'Alat'}=$A;
89 1         2 $self->{'Blat'}=$B;
90 1         2 $self->{'Clat'}=$C;
91 1         2 $self->{'Dlat'}=$D;
92 1         5 ($A, $B, $C, $D)=$self->ABCD(
93             $self->{'pt0'}->{'time'},
94             $self->{'pt0'}->{'lon'} * $ellipsoid->equatorial_circumference / 360,
95             $self->{'pt0'}->{'speed'} * sin(rad_deg($self->{'pt0'}->{'heading'})),
96             $self->{'pt1'}->{'time'},
97             $self->{'pt1'}->{'lon'} * $ellipsoid->equatorial_circumference / 360,
98             $self->{'pt1'}->{'speed'} * sin(rad_deg($self->{'pt1'}->{'heading'})));
99 1         4 $self->{'Alon'}=$A;
100 1         2 $self->{'Blon'}=$B;
101 1         2 $self->{'Clon'}=$C;
102 1         2 $self->{'Dlon'}=$D;
103             }
104              
105             =head2 ellipsoid
106              
107             Method to set or retrieve the current ellipsoid object. The ellipsoid is a Geo::Ellipsoids object.
108              
109             my $ellipsoid=$obj->ellipsoid; #Default is WGS84
110              
111             $obj->ellipsoid('Clarke 1866'); #Built in ellipsoids from Geo::Ellipsoids
112             $obj->ellipsoid({a=>1}); #Custom Sphere 1 unit radius
113              
114             =cut
115              
116             sub ellipsoid {
117 1259     1259 1 1169 my $self = shift();
118 1259 100       2129 if (@_) {
119 1         2 my $param=shift();
120 1     1   786 use Geo::Ellipsoids;
  1         2331  
  1         591  
121 1         4 my $obj=Geo::Ellipsoids->new($param);
122 1         214 $self->{'ellipsoid'}=$obj;
123             }
124 1259         1820 return $self->{'ellipsoid'};
125             }
126              
127             sub ABCD {
128 2     2 0 102 my $self = shift();
129 2         5 my $t0 = shift();
130 2         3 my $x0 = shift();
131 2         8 my $v0 = shift();
132 2         3 my $t1 = shift();
133 2         3 my $x1 = shift();
134 2         3 my $v1 = shift();
135             #x=f(t)=A+B(t-t0)+C(t-t0)^2+D(t-t0)^3
136             #v=f'(t)=B+2C(t-t0)+3D(t-t0)^2
137             #A=x0
138             #B=v0
139             #C=(x1-A-B(t1-t0)-D(t1-t0)^3)/((t1-t0)^2) # from f(t)
140             #C=(v1-B-3D(t1-t0)^2)/2(t1-t0) # from f'(t)
141             #D=(v1t+Bt-2x1+2A)/t^3 # from C=C
142 2         2 my $A=$x0;
143 2         3 my $B=$v0;
144             #=(C3*(A3-A2)+B6*(A3-A2)-2*B3+2*B5)/(A3-A2)^3 # for Excel
145 2         24 my $D=($v1*($t1-$t0)+$B*($t1-$t0)-2*$x1+2*$A)/($t1-$t0)**3;
146             #=(B3-B5-B6*(A3-A2)-B8*(A3-A2)^3)/(A3-A2)^2 # for Excel
147 2         7 my $C=($x1-$A-$B*($t1-$t0)-$D*($t1-$t0)**3)/($t1-$t0)**2;
148 2         6 return($A,$B,$C,$D);
149             }
150              
151             =head2 point
152              
153             Method returns a single point from a single time.
154              
155             my $point=$spline->point($t1);
156             my %point=$spline->point($t1);
157              
158             =cut
159              
160             sub point {
161 1258     1258 1 2153 my $self=shift();
162 1258         1156 my $timereal=shift();
163 1258         1825 my $ellipsoid=$self->ellipsoid;
164 1258         1980 my $t=$timereal-$self->{'pt0'}->{'time'};
165 1258         2171 my ($Alat, $Blat, $Clat, $Dlat)=($self->{'Alat'}, $self->{'Blat'},$self->{'Clat'},$self->{'Dlat'});
166 1258         1953 my ($Alon, $Blon, $Clon, $Dlon)=($self->{'Alon'}, $self->{'Blon'},$self->{'Clon'},$self->{'Dlon'});
167 1258         2610 my $lat=$Alat + $Blat * $t + $Clat * $t ** 2 + $Dlat * $t ** 3;
168 1258         2017 my $lon=$Alon + $Blon * $t + $Clon * $t ** 2 + $Dlon * $t ** 3;
169 1258         1694 my $vlat=$Blat + 2 * $Clat * $t + 3 * $Dlat * $t ** 2;
170 1258         1625 my $vlon=$Blon + 2 * $Clon * $t + 3 * $Dlon * $t ** 2;
171 1258         1595 my $speed=sqrt($vlat ** 2 + $vlon ** 2);
172 1258         2453 my $heading=PI()/2 - atan2($vlat,$vlon);
173 1258         4667 $heading=deg_rad($heading);
174 1258         8403 $lat/=$ellipsoid->polar_circumference / 360;
175 1258         12048 $lon/=$ellipsoid->equatorial_circumference / 360;
176 1258         10500 my %pt=(time=>$timereal,
177             lat=>$lat,
178             lon=>$lon,
179             speed=>$speed,
180             heading=>$heading);
181 1258 100       7856 return wantarray ? %pt : \%pt;
182             }
183              
184             =head2 pointlist
185              
186             Method returns a list of points from a list of times.
187              
188             my $list=$spline->pointlist($t1,$t2,$t3);
189             my @list=$spline->pointlist($t1,$t2,$t3);
190              
191             =cut
192              
193             sub pointlist {
194 4     4 1 2580 my $self=shift();
195 4         58 my @list=@_;
196 4 100       14 @list=$self->timelist() if (scalar(@list)== 0);
197 4         10 my @points=();
198 4         7 foreach (@list) {
199 1256         2148 push @points, {$self->point($_)};
200             }
201 4 100       456 return wantarray ? @points : \@points;
202             }
203              
204             =head2 timelist
205              
206             Method returns a list of times (n+1). The default will return a list with an integer number of seconds between spline end points.
207              
208             my $list=$spline->timelist($samples);
209             my @list=$spline->timelist();
210              
211             =cut
212              
213             sub timelist {
214 6     6 1 795 my $self=shift();
215 6         12 my $t0=$self->{'pt0'}->{'time'};
216 6         8 my $t1=$self->{'pt1'}->{'time'};
217 6         7 my $dt=$t1-$t0;
218 6   66     20 my $count=shift() || round($dt);
219 6         28 my @list;
220 6         10 foreach(0..$count) {
221 1506         1370 my $t=$t0+$dt*($_/$count);
222 1506         1527 push @list, $t;
223             }
224 6 100       323 return wantarray ? @list : \@list;
225             }
226              
227             1;
228              
229             __END__