source: rdnap/lib/Geo/Coordinates/RDNAP.pm@ 379

Last change on this file since 379 was 203, checked in by Rick van der Zwet, 14 years ago

Little wrapper, as, the orginal one is gone.

File size: 10.4 KB
Line 
1package Geo::Coordinates::RDNAP;
2
3use strict;
4use warnings;
5
6use vars qw($VERSION);
7$VERSION = '0.11';
8
9use Carp;
10use Params::Validate qw/validate BOOLEAN SCALAR/;
11
12use Exporter;
13use vars qw/@ISA @EXPORT_OK/;
14@ISA = qw/Exporter/;
15@EXPORT_OK = qw/from_rd to_rd deg dms/;
16
17sub deg {
18 my (@in) = @_;
19 my @out;
20
21 while (my($d, $m, $s) = splice (@in, 0, 3)) {
22 push @out, $d + ($m||0)/60 + ($s||0)/3600;
23 }
24 return @out;
25}
26
27sub dms {
28 return map {int($_), int($_*60)%60, ($_-int($_*60)/60)*3600} @_;
29}
30
31my %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
46my %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
61my %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
73my %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
86my %bessel = (
87 a => 6377397.155,
88 e2 => 6674372e-9,
89 f_i => 299.1528128,
90);
91
92my %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
101my %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
111my %e2b = map {$_ => -$b2e{$_}} keys %b2e;
112
113my @amersfoort_b = ( 3903_453.148, 368_135.313, 5012_970.306 );
114my @amersfoort_e = ( 3904_046.180, 368_161.313, 5013_449.047 );
115
116sub from_rd {
117 croak 'Geo::Coordinates::RDNAP::from_rd needs two or three arguments'
118 if (@_ !=2 && @_ != 3);
119
120 my ($x, $y, $h) = (@_, 0);
121
122 croak "Geo::Coordinates::RDNAP::from_rd: X out of bounds: $x"
123 if ($x < -7_000 or $x > 300_000);
124 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 $x = ($x/100_000) - 1.55;
131 $y = ($y/100_000) - 4.63;
132
133 my $lat = (52*60*60) + (9*60) + 22.178;
134 my $lon = (5 *60*60) + (23*60) + 15.5;
135
136 foreach my $i (keys %a) {
137 my ($m, $n) = split //, $i;
138 $lat += $a{$i} * ($x**$m) * ($y**$n);
139 }
140
141 foreach my $i (keys %b) {
142 my ($m, $n) = split //, $i;
143 $lon += $b{$i} * ($x**$m) * ($y**$n);
144 }
145
146 # Step 2: spherical coords -> X, Y, Z
147 my @coords = _ellipsoid_to_cartesian($lat/3600, $lon/3600, $h, \%bessel);
148
149 # Step 3: Bessel -> ETRS89
150 @coords = _transform_datum( @coords, \%b2e, \@amersfoort_b );
151
152 # Step 4: X, Y, Z -> spherical coords
153 return _cartesian_to_ellipsoid(@coords, \%etrs89);
154}
155
156sub to_rd {
157 croak 'Geo::Coordinates::RDNAP::to_rd needs two or three arguments'
158 if (@_ !=2 && @_ != 3);
159
160 my ($lat, $lon, $h) = (@_, 0);
161
162 # Use the approximated transformation.
163 # Step 1: spherical coords -> X, Y, Z
164 my @coords = _ellipsoid_to_cartesian($lat, $lon, $h, \%etrs89);
165
166 # Step 2: ETRS89 -> Bessel
167 @coords = _transform_datum( @coords, \%e2b, \@amersfoort_e );
168
169 # Step 3: X, Y, Z -> spherical coords
170 ($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 $lat = ($lat * 3600 - ((52*60*60) + (9*60) + 22.178))/10_000;
176 $lon = ($lon * 3600 - ((5 *60*60) + (23*60) + 15.5))/10_000;
177
178 my $x = 155e3;
179 my $y = 463e3;
180
181 foreach my $i (keys %c) {
182 my ($m, $n) = split //, $i;
183 $x += $c{$i} * ($lat**$m) * ($lon**$n);
184 }
185
186 foreach my $i (keys %d) {
187 my ($m, $n) = split //, $i;
188 $y += $d{$i} * ($lat**$m) * ($lon**$n);
189 }
190
191 croak "Geo::Coordinates::RDNAP::to_rd: X out of bounds: $x"
192 if ($x < -7_000 or $x > 300_000);
193 croak "Geo::Coordinates::RDNAP::to_rd: Y out of bounds: $y"
194 if ($y < 289_000 or $y > 629_000);
195
196 return ($x, $y, $h);
197}
198
199sub _to_rads {
200 return $_[0] * 2*3.14159_26535_89793 /360;
201}
202
203sub _from_rads {
204 return $_[0] / (2*3.14159_26535_89793) *360;
205}
206
207sub _ellipsoid_to_cartesian {
208 my ($lat, $lon, $h, $para) = @_;
209
210 my $sinphi = sin(_to_rads($lat));
211 my $cosphi = cos(_to_rads($lat));
212 my $n = $para->{a}/sqrt(1 - $para->{e2}*$sinphi*$sinphi);
213
214 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
221sub _cartesian_to_ellipsoid {
222 my ($x, $y, $z, $para) = @_;
223
224 my $lon = atan2($y, $x);
225
226 my $r = sqrt($x*$x+$y*$y);
227 my $phi = 0;
228 my $n_sinphi = $z;
229 my $n;
230 my $oldphi;
231
232 do {
233 $oldphi = $phi;
234 $phi = atan2($z + $para->{e2}*$n_sinphi, $r);
235 my $sinphi = sin($phi);
236 $n = $para->{a}/sqrt(1-$para->{e2}*$sinphi*$sinphi);
237 $n_sinphi = $n*$sinphi;
238 } while (abs($oldphi-$phi) > 1e-8);
239
240 my $h = $r/cos($phi) - $n;
241
242 return (_from_rads($phi), _from_rads($lon), $h);
243}
244
245sub _transform_datum {
246 my ($x, $y, $z, $t, $centre) = @_;
247
248 return (
249 $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
2581;
259__END__
260
261=head1 NAME
262
263Geo::Coordinates::RDNAP - convert to/from Dutch RDNAP coordinate system
264
265=head1 SYNOPSIS
266
267 use Geo::Coordinates::RDNAP qw/from_rd to_rd dd dms/;
268
269 # RD coordinates and height in meters
270 my ($lat, $lon, $h) = from_rd( 150_000, 480_000, -2.75 );
271
272 printf "%d %d' %.2f\" %d %d' %.2f\"", dms($lat, $lon);
273
274 lat/lon coordinates in degrees; height in meters
275 my ($x, $y, $h) = to_rd( 52.75, 6.80, 10 );
276
277 # equivalent: to_rd( deg(52,45,0, 6,48,0), 10 );
278
279=head1 DESCRIPTION
280
281This module converts between two coordinate systems: RD-NAP and ETRS89.
282ETRS89 is a geodesic frame of reference used in Europe, which is
283approximately equal to the international reference frame WGS84.
284GPS data. Coordinates in ETRS89 are given in degrees (latitude and
285longitude) and meters (height above the reference ellipsoid).
286
287RD-NAP (or "Amersfoort datum") is a Dutch coordinate system, consisting
288of the X and Y coordinates of the Rijksdriehoekmeting, used e.g. in
289topographical maps, and a Z coordinate which is the height above Normaal
290Amsterdams Peil, the mean sea level at Amsterdam. X, Y, and Z are all
291expressed in meters (this is a change compared to the previous versions
292of this module!)
293
294These transformations should only be used for locations in or close to
295the Netherlands.
296
297See http://www.rdnap.nl/ for a description of the RD-NAP system;
298especially http://www.rdnap.nl/download/rdnaptrans.pdf for the formulas
299used in this module.
300
301=head2 Precision
302
303This module implements an approximated transformation, which should be
304accurate to about 25 cm in X and Y, and about 1 meter in the vertical
305direction, for all locations in the Netherlands. The full
306transformation, called RDNAPTRANS, is NOT implemented in this module. It
307takes into account small deviations, measured at more than 5000 points
308in the Netherlands.
309
310Coordinates in ETRS89 deviate from WGS84 and ITRS because the former is
311coupled to the Eurasian plate, which drifts a few cm per year compared
312to other plates. The current (2006) difference between these coordinate
313systems is in the order of 40 cm.
314
315=head2 Disclaimer
316
317Although this module implements conversion to/from the RD-NAP coordinate
318system, it is not a product of RDNAP, the cooperation between the
319Kadaster and Rijkwaterstaat, which maintains this coordinate system.
320
321RDNAPTRANS is a trademark, presumably by Kadaster and/or
322Rijkswaterstaat. This module is not an implementation of RDNAPTRANS. For
323the official transformation software, visit http://www.rdnap.nl.
324
325=head1 FUNCTIONS
326
327=over 4
328
329=item from_rd( $x, $y, $h )
330
331Converts coordinates in the RD-NAP coordinate system to geographical
332coordinates. The input are the X and Y coordinates in the RD system,
333given in meters, and optionally the height above NAP in meters.
334
335This should only be used for points in or close to the Netherlands. For
336this area, X should roughly be between 0 and 300_000, and Y between
337300_000 and 650_000.
338
339The output is a list of three numbers: latitude and longitude in
340degrees, according to the ETRS89 coordinate system, and height above the
341ETRS89 reference geoid, in meters.
342
343=item to_rd( $lat, $lon, $h )
344
345Converts geegraphical coordinates to coordinates in the RD-NAP
346coordinate system. The input are the latituse and longitude in degrees,
347and optionally the height above the ETRS89 reference geoid in meters.
348
349This should only be used for points in or close to the Netherlands.
350
351The output is a list of three numbers: X and Y in the RD system in
352meters, and the height above NAP in meters.
353
354=item deg
355
356Helper function to convert degrees/minutes/seconds to decimal degrees.
357Works only for positive latitude and longitude.
358
359=item dms
360
361Helper function to convert decimal degrees to degrees/minutes/seconds.
362Works only for positive latitude and longitude. The returned degrees and
363minutes are integers; the returned number of seconds can be fractional.
364
365When rounding the number of seconds, remember that it wraps at 60 (and
366so does the number of minutes). One easy way (but perhaps not the
367fastest) of taking this into account is the following piece of code:
368
369 ($d, $m, $s) = dms($degrees);
370 $s = int($s + 0.5);
371 ($d, $m, $s) = map {sprintf "%.0f"} dms(deg($d, $m, $s));
372
373=back
374
375=head1 BUGS
376
377None known.
378
379=head1 AUTHOR
380
381Eugene van der Pijll C<< <pijll@cpan.org> >>
382
383=head1 COPYRIGHT
384
385Copyright (c) 2006 Eugene van der Pijll.
386This program is free software; you can redistribute it and/or modify it
387under the same terms as Perl itself.
388
389The full text of the license can be found in the LICENSE file included
390with this module.
391
392=cut
Note: See TracBrowser for help on using the repository browser.