File Coverage

blib/lib/Geo/Coordinates/RDNAP.pm
Criterion Covered Total %
statement 86 86 100.0
branch 6 12 50.0
condition 8 22 36.3
subroutine 16 16 100.0
pod 4 4 100.0
total 120 140 85.7


line stmt bran cond sub pod time code
1             package Geo::Coordinates::RDNAP;
2              
3 3     3   87324 use strict;
  3         8  
  3         465  
4 3     3   22 use warnings;
  3         8  
  3         130  
5              
6 3     3   20 use vars qw($VERSION);
  3         9  
  3         200  
7             $VERSION = '0.11';
8              
9 3     3   19 use Carp;
  3         7  
  3         467  
10 3     3   4057 use Params::Validate qw/validate BOOLEAN SCALAR/;
  3         45009  
  3         253  
11              
12 3     3   30 use Exporter;
  3         6  
  3         102  
13 3     3   17 use vars qw/@ISA @EXPORT_OK/;
  3         4  
  3         5582  
14             @ISA = qw/Exporter/;
15             @EXPORT_OK = qw/from_rd to_rd deg dms/;
16              
17             sub deg {
18 2     2 1 1681 my (@in) = @_;
19 2         3 my @out;
20              
21 2         10 while (my($d, $m, $s) = splice (@in, 0, 3)) {
22 3   50     19 push @out, $d + ($m||0)/60 + ($s||0)/3600;
      50        
23             }
24 2         5 return @out;
25             }
26              
27             sub dms {
28 2     2 1 1136 return map {int($_), int($_*60)%60, ($_-int($_*60)/60)*3600} @_;
  3         18  
29             }
30              
31             my %a = (
32             '01' => 3236.0331637,
33             20 => -32.5915821,
34             '02' => -0.2472814,
35             21 => -0.8501341,
36             '03' => -0.0655238,
37             22 => -0.0171137,
38             40 => 0.0052771,
39             23 => -0.0003859,
40             41 => 0.0003314,
41             '04' => 0.0000371,
42             42 => 0.0000143,
43             24 => -0.0000090,
44             );
45              
46             my %b = (
47             10 => 5261.3028966,
48             11 => 105.9780241,
49             12 => 2.4576469,
50             30 => -0.8192156,
51             31 => -0.0560092,
52             13 => 0.0560089,
53             32 => -0.0025614,
54             14 => 0.0012770,
55             50 => 0.0002574,
56             33 => -0.0000973,
57             51 => 0.0000293,
58             15 => 0.0000291,
59             );
60              
61             my %c = (
62             '01' => 190066.98903,
63             11 => -11830.85831,
64             21 => -114.19754,
65             '03' => -32.38360,
66             31 => -2.34078,
67             13 => -0.60639,
68             23 => 0.15774,
69             41 => -0.04158,
70             '05' => -0.00661,
71             );
72              
73             my %d = (
74             10 => 309020.31810,
75             '02' => 3638.36193,
76             12 => -157.95222,
77             20 => 72.97141,
78             30 => 59.79734,
79             22 => -6.43481,
80             '04' => 0.09351,
81             32 => -0.07379,
82             14 => -0.05419,
83             40 => -0.03444,
84             );
85              
86             my %bessel = (
87             a => 6377397.155,
88             e2 => 6674372e-9,
89             f_i => 299.1528128,
90             );
91              
92             my %etrs89 = (
93             a => 6378137,
94             e2 => 6694380e-9,
95             f_i => 298.257222101,
96             );
97              
98             # Transformation parameters from Bessel to ETRS89 with respect to
99             # Amersfoort.
100              
101             my %b2e = (
102             tx => 593.032,
103             ty => 26,
104             tz => 478.741,
105             a => 1.9848e-6,
106             b => -1.7439e-6,
107             c => 9.0587e-6,
108             d => 4.0772e-6,
109             );
110              
111             my %e2b = map {$_ => -$b2e{$_}} keys %b2e;
112              
113             my @amersfoort_b = ( 3903_453.148, 368_135.313, 5012_970.306 );
114             my @amersfoort_e = ( 3904_046.180, 368_161.313, 5013_449.047 );
115              
116             sub from_rd {
117 4 50 33 4 1 3464 croak 'Geo::Coordinates::RDNAP::from_rd needs two or three arguments'
118             if (@_ !=2 && @_ != 3);
119              
120 4         11 my ($x, $y, $h) = (@_, 0);
121              
122 4 50 33     27 croak "Geo::Coordinates::RDNAP::from_rd: X out of bounds: $x"
123             if ($x < -7_000 or $x > 300_000);
124 4 50 33     22 croak "Geo::Coordinates::RDNAP::from_rd: Y out of bounds: $y"
125             if ($y < 289_000 or $y > 629_000);
126              
127             # Use the approximated transformation.
128             # Step 1: RD -> Bessel (spherical coords)
129              
130 4         9 $x = ($x/100_000) - 1.55;
131 4         7 $y = ($y/100_000) - 4.63;
132              
133 4         6 my $lat = (52*60*60) + (9*60) + 22.178;
134 4         6 my $lon = (5 *60*60) + (23*60) + 15.5;
135              
136 4         21 foreach my $i (keys %a) {
137 48         112 my ($m, $n) = split //, $i;
138 48         194 $lat += $a{$i} * ($x**$m) * ($y**$n);
139             }
140              
141 4         18 foreach my $i (keys %b) {
142 48         117 my ($m, $n) = split //, $i;
143 48         297 $lon += $b{$i} * ($x**$m) * ($y**$n);
144             }
145              
146             # Step 2: spherical coords -> X, Y, Z
147 4         21 my @coords = _ellipsoid_to_cartesian($lat/3600, $lon/3600, $h, \%bessel);
148              
149             # Step 3: Bessel -> ETRS89
150 4         14 @coords = _transform_datum( @coords, \%b2e, \@amersfoort_b );
151              
152             # Step 4: X, Y, Z -> spherical coords
153 4         15 return _cartesian_to_ellipsoid(@coords, \%etrs89);
154             }
155              
156             sub to_rd {
157 4 50 33 4 1 3466 croak 'Geo::Coordinates::RDNAP::to_rd needs two or three arguments'
158             if (@_ !=2 && @_ != 3);
159              
160 4         10 my ($lat, $lon, $h) = (@_, 0);
161              
162             # Use the approximated transformation.
163             # Step 1: spherical coords -> X, Y, Z
164 4         14 my @coords = _ellipsoid_to_cartesian($lat, $lon, $h, \%etrs89);
165              
166             # Step 2: ETRS89 -> Bessel
167 4         16 @coords = _transform_datum( @coords, \%e2b, \@amersfoort_e );
168              
169             # Step 3: X, Y, Z -> spherical coords
170 4         14 ($lat, $lon, $h) = _cartesian_to_ellipsoid(@coords, \%bessel);
171              
172             # Step 4: Bessel -> RD'
173              
174             # Convert to units of 10_000 secs; as deltas from Amersfoort.
175 4         10 $lat = ($lat * 3600 - ((52*60*60) + (9*60) + 22.178))/10_000;
176 4         8 $lon = ($lon * 3600 - ((5 *60*60) + (23*60) + 15.5))/10_000;
177              
178 4         6 my $x = 155e3;
179 4         5 my $y = 463e3;
180              
181 4         16 foreach my $i (keys %c) {
182 36         90 my ($m, $n) = split //, $i;
183 36         156 $x += $c{$i} * ($lat**$m) * ($lon**$n);
184             }
185              
186 4         19 foreach my $i (keys %d) {
187 40         92 my ($m, $n) = split //, $i;
188 40         120 $y += $d{$i} * ($lat**$m) * ($lon**$n);
189             }
190              
191 4 50 33     38 croak "Geo::Coordinates::RDNAP::to_rd: X out of bounds: $x"
192             if ($x < -7_000 or $x > 300_000);
193 4 50 33     23 croak "Geo::Coordinates::RDNAP::to_rd: Y out of bounds: $y"
194             if ($y < 289_000 or $y > 629_000);
195              
196 4         17 return ($x, $y, $h);
197             }
198              
199             sub _to_rads {
200 32     32   202 return $_[0] * 2*3.14159_26535_89793 /360;
201             }
202              
203             sub _from_rads {
204 16     16   59 return $_[0] / (2*3.14159_26535_89793) *360;
205             }
206              
207             sub _ellipsoid_to_cartesian {
208 8     8   17 my ($lat, $lon, $h, $para) = @_;
209              
210 8         21 my $sinphi = sin(_to_rads($lat));
211 8         16 my $cosphi = cos(_to_rads($lat));
212 8         37 my $n = $para->{a}/sqrt(1 - $para->{e2}*$sinphi*$sinphi);
213              
214 8         21 return (($n+$h)*$cosphi*cos(_to_rads($lon)),
215             ($n+$h)*$cosphi*sin(_to_rads($lon)),
216             ($n*(1-$para->{e2})+$h)*$sinphi );
217             }
218              
219             # Returns (lat, lon, h) in degrees.
220              
221             sub _cartesian_to_ellipsoid {
222 8     8   14 my ($x, $y, $z, $para) = @_;
223              
224 8         69 my $lon = atan2($y, $x);
225              
226 8         19 my $r = sqrt($x*$x+$y*$y);
227 8         10 my $phi = 0;
228 8         21 my $n_sinphi = $z;
229 8         12 my $n;
230             my $oldphi;
231              
232 8         13 do {
233 32         38 $oldphi = $phi;
234 32         77 $phi = atan2($z + $para->{e2}*$n_sinphi, $r);
235 32         67 my $sinphi = sin($phi);
236 32         60 $n = $para->{a}/sqrt(1-$para->{e2}*$sinphi*$sinphi);
237 32         98 $n_sinphi = $n*$sinphi;
238             } while (abs($oldphi-$phi) > 1e-8);
239              
240 8         17 my $h = $r/cos($phi) - $n;
241              
242 8         22 return (_from_rads($phi), _from_rads($lon), $h);
243             }
244              
245             sub _transform_datum {
246 8     8   36 my ($x, $y, $z, $t, $centre) = @_;
247              
248             return (
249 8         94 $x + $t->{d}*($x-$centre->[0]) + $t->{c}*($y-$centre->[1])
250             - $t->{b}*($z-$centre->[2]) + $t->{tx},
251             $y - $t->{c}*($x-$centre->[0]) + $t->{d}*($y-$centre->[1])
252             + $t->{a}*($z-$centre->[2]) + $t->{ty},
253             $z + $t->{b}*($x-$centre->[0]) - $t->{a}*($y-$centre->[1])
254             + $t->{d}*($z-$centre->[2]) + $t->{tz}
255             );
256             }
257              
258             1;
259             __END__