File Coverage

blib/lib/Geo/Coordinates/OSGB.pm
Criterion Covered Total %
statement 268 268 100.0
branch 54 60 90.0
condition 11 15 73.3
subroutine 30 30 100.0
pod 5 5 100.0
total 368 378 97.3


line stmt bran cond sub pod time code
1             package Geo::Coordinates::OSGB;
2 10     10   161813 use base qw(Exporter);
  10         33  
  10         1338  
3 10     10   84 use strict;
  10         25  
  10         292  
4 10     10   60 use warnings;
  10         33  
  10         362  
5 10     10   61 use Carp;
  10         37  
  10         906  
6 10     10   252 use 5.008; # at least Perl 5.08 please
  10         46  
7              
8             our $VERSION = '2.19';
9              
10             our %EXPORT_TAGS = (
11             all => [
12             qw(
13             ll_to_grid
14             grid_to_ll
15              
16             ll_to_grid_helmert
17             grid_to_ll_helmert
18              
19             get_ostn02_shift_pair
20             set_default_shape
21             )
22             ]
23             );
24              
25             our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
26              
27 10         1977 use constant ELLIPSOIDS => {
28             WGS84 => [ 6_378_137.000, 6_356_752.31424518, 298.257223563, 0.006694379990141316996137233540 ],
29             ETRS89 => [ 6_378_137.000, 6_356_752.314140, 298.257222101, 0.006694380022900787625359114703 ],
30             GRS80 => [ 6_378_137.000, 6_356_752.314140, 298.257222101, 0.006694380022900787625359114703 ],
31             OSGB36 => [ 6_377_563.396, 6_356_256.909, 299.3249612665, 0.0066705400741492318211148938735613129751683486352306 ],
32 10     10   89 };
  10         23  
33              
34             my $default_shape = 'WGS84';
35              
36             sub set_default_shape {
37 2     2 1 4 my $s = shift;
38 2 50       8 croak "Unknown shape: $s" if !exists ELLIPSOIDS->{$s};
39 2         3 $default_shape = $s;
40 2         5 return;
41             }
42              
43             *get_ostn02_shift_pair = \&_find_OSTN02_shifts_at;
44              
45             # constants for OSGB mercator projection
46 10     10   68 use constant ORIGIN_LONGITUDE => -2 / 57.29577951308232087679815481410517;
  10         21  
  10         576  
47 10     10   58 use constant ORIGIN_LATITUDE => 49 / 57.29577951308232087679815481410517;
  10         19  
  10         438  
48 10     10   51 use constant ORIGIN_EASTING => 400_000;
  10         27  
  10         472  
49 10     10   112 use constant ORIGIN_NORTHING => -100_000;
  10         23  
  10         595  
50 10     10   54 use constant CONVERGENCE_FACTOR => 0.9996012717;
  10         25  
  10         437  
51              
52             # constants for OSTN02 data
53 10     10   67 use constant MIN_X_SHIFT => 86000;
  10         28  
  10         465  
54 10     10   63 use constant MIN_Y_SHIFT => -82000;
  10         23  
  10         436  
55              
56 10     10   65 use constant TENTH_MM => 0.0001;
  10         21  
  10         468  
57 10     10   62 use constant HUNDREDTH_MM => 0.00001;
  10         19  
  10         25095  
58              
59             # load all the data from below
60             my @ostn_data;
61             while () {
62             push @ostn_data, $_;
63             }
64             close DATA;
65             # closing the DATA file handle supresses annoying additions to any error messages
66             my %ostn_shifts_for;
67              
68              
69             sub _llh_to_cartesian {
70 29     29   86 my ( $lat, $lon, $H, $shape ) = @_;
71              
72 29         55 my ( $a, $b, $f, $ee ) = @{ ELLIPSOIDS->{$shape} };
  29         89  
73              
74 29         74 my $phi = $lat / 57.29577951308232087679815481410517;
75 29         69 my $sp = sin $phi;
76 29         69 my $cp = cos $phi;
77 29         58 my $lam = $lon / 57.29577951308232087679815481410517;
78 29         68 my $sl = sin $lam;
79 29         62 my $cl = cos $lam;
80              
81 29         80 my $nu = $a / sqrt( 1 - $ee * $sp * $sp );
82              
83 29         72 my $x = ( $nu + $H ) * $cp * $cl;
84 29         58 my $y = ( $nu + $H ) * $cp * $sl;
85 29         67 my $z = ( ( 1 - $ee ) * $nu + $H ) * $sp;
86              
87 29         94 return ( $x, $y, $z );
88             }
89              
90             sub _cartesian_to_llh {
91 29     29   73 my ( $x, $y, $z, $shape ) = @_;
92              
93 29         59 my ( $a, $b, $f, $ee ) = @{ ELLIPSOIDS->{$shape} };
  29         88  
94              
95 29         92 my $p = sqrt($x*$x+$y*$y);
96 29         122 my $lam = atan2 $y, $x;
97 29         89 my $phi = atan2 $z, $p*(1-$ee);
98              
99 29         58 my ( $nu, $oldphi, $sp );
100 29         50 while (1) {
101 87         159 $sp = sin $phi;
102 87         184 $nu = $a / sqrt(1 - $ee*$sp*$sp);
103 87         145 $oldphi = $phi;
104 87         184 $phi = atan2 $z+$ee*$nu*$sp, $p;
105 87 100       239 last if abs($oldphi-$phi) < 1E-12;
106             }
107              
108 29         65 my $lat = $phi * 57.29577951308232087679815481410517;
109 29         56 my $lon = $lam * 57.29577951308232087679815481410517;
110 29         76 my $H = $p / cos($phi) - $nu;
111              
112 29         95 return ( $lat, $lon, $H );
113             }
114              
115             sub _small_Helmert_transform_for_OSGB {
116 29     29   79 my ($direction, $xa, $ya, $za) = @_;
117 29         64 my $tx = $direction * -446.448;
118 29         59 my $ty = $direction * +125.157;
119 29         54 my $tz = $direction * -542.060;
120 29         61 my $sp = $direction * 0.0000204894 + 1;
121 29         68 my $rx = ($direction * -0.1502/3600) / 57.29577951308232087679815481410517;
122 29         64 my $ry = ($direction * -0.2470/3600) / 57.29577951308232087679815481410517;
123 29         57 my $rz = ($direction * -0.8421/3600) / 57.29577951308232087679815481410517;
124 29         103 my $xb = $tx + $sp*$xa - $rz*$ya + $ry*$za;
125 29         63 my $yb = $ty + $rz*$xa + $sp*$ya - $rx*$za;
126 29         72 my $zb = $tz - $ry*$xa + $rx*$ya + $sp*$za;
127 29         85 return ($xb, $yb, $zb);
128             }
129              
130             sub _shift_ll_from_osgb36_to_wgs84 {
131 11     11   28 my ($lat, $lon) = @_;
132 11         30 my ($xa, $ya, $za) = _llh_to_cartesian($lat, $lon, 0, 'OSGB36' );
133 11         37 my ($xb, $yb, $zb) = _small_Helmert_transform_for_OSGB(-1,$xa, $ya, $za);
134 11         40 my ($latx, $lonx, $junk) = _cartesian_to_llh($xb, $yb, $zb, 'WGS84');
135 11         108 return ($latx, $lonx);
136             }
137              
138             sub _shift_ll_from_wgs84_to_osgb36 {
139 18     18   43 my ($lat, $lon) = @_;
140 18         57 my ($xa, $ya, $za) = _llh_to_cartesian($lat, $lon, 0, 'WGS84');
141 18         61 my ($xb, $yb, $zb) = _small_Helmert_transform_for_OSGB(+1,$xa, $ya, $za);
142 18         58 my ($latx, $lonx, $junk) = _cartesian_to_llh($xb, $yb, $zb, 'OSGB36');
143 18         88 return ($latx, $lonx);
144             }
145              
146             sub ll_to_grid {
147              
148 214     214 1 84895 my ( $lat, $lon, $options ) = @_;
149              
150             # we might have been passed a hash as the first argument
151 214 50 66     725 if (ref $lat && defined $lat->{lat} && defined $lat->{lon}) {
      66        
152 1         3 $options = $lat;
153 1         2 $lat = $options->{lat};
154 1         2 $lon = $options->{lon};
155             }
156              
157             # correct reversed arguments, this is always valid in OSGB area
158 214 100       702 if ($lat < $lon) {
159 1         2 ($lat, $lon) = ($lon, $lat)
160             }
161              
162 214 100       805 my $shape = exists $options->{shape} ? $options->{shape} : $default_shape;
163 214 50       634 croak "Unknown shape: $shape" if !exists ELLIPSOIDS->{$shape};
164              
165 214         691 my ($e,$n) = _project_onto_grid($lat, $lon, $shape);
166              
167 214         559 my @out;
168             # If we were using LL from OS maps, then we are done
169 214 100       954 if ($shape eq 'OSGB36') {
170 12         20 @out = map { sprintf '%.3f', $_ } ($e, $n);
  24         138  
171 12 100       75 return wantarray ? @out : "@out";
172             }
173              
174             # now shape is WGS84 etc so we must adjust
175 202         682 my ($dx, $dy) = _find_OSTN02_shifts_at($e,$n);
176 202 100       731 if ($dx) {
177 188         566 @out = map { sprintf '%.3f', $_ } ($e + $dx, $n + $dy);
  376         3486  
178 188 100       1770 return wantarray ? @out : "@out";
179             }
180              
181             # still here? Then do Helmert shift into OSGB36 and re-project
182 14         53 return ll_to_grid_helmert($lat, $lon)
183             }
184              
185             sub ll_to_grid_helmert {
186 18     18 1 56 my ($lat, $lon) = @_;
187 18         62 my @out = map { sprintf '%.0f', $_ } # round to metres
  36         166  
188             _project_onto_grid( _shift_ll_from_wgs84_to_osgb36($lat, $lon), 'OSGB36' );
189 18 100       190 return wantarray ? @out : "@out";
190             }
191              
192             sub _project_onto_grid {
193              
194 232     232   640 my ( $lat, $lon, $shape ) = @_;
195              
196 232         414 my ($a,$b,$f,$e2) = @{ ELLIPSOIDS->{$shape} };
  232         728  
197              
198 232         789 my $n = ($a-$b)/($a+$b);
199 232         508 my $af = $a * CONVERGENCE_FACTOR;
200              
201 232         438 my $phi = $lat / 57.29577951308232087679815481410517;
202 232         475 my $lam = $lon / 57.29577951308232087679815481410517;
203              
204 232         569 my $cp = cos $phi; my $sp = sin $phi;
  232         594  
205 232         414 my $sp2 = $sp*$sp;
206 232         519 my $tp = $sp/$cp; # cos phi cannot be zero in GB
207 232         452 my $tp2 = $tp*$tp;
208 232         391 my $tp4 = $tp2*$tp2;
209              
210 232         548 my $splat = 1 - $e2 * $sp2;
211 232         435 my $sqrtsplat = sqrt $splat;
212 232         489 my $nu = $af / $sqrtsplat;
213 232         571 my $rho = $af * (1 - $e2) / ($splat*$sqrtsplat);
214 232         512 my $eta2 = $nu/$rho - 1;
215              
216 232         461 my $p_plus = $phi + ORIGIN_LATITUDE;
217 232         438 my $p_minus = $phi - ORIGIN_LATITUDE;
218 232         1664 my $M = $b * CONVERGENCE_FACTOR * (
219             (1 + $n * (1 + 5/4*$n*(1 + $n)))*$p_minus
220             - 3*$n*(1+$n*(1+7/8*$n)) * sin( $p_minus) * cos( $p_plus)
221             + (15/8*$n * ($n*(1+$n))) * sin(2*$p_minus) * cos(2*$p_plus)
222             - 35/24*$n**3 * sin(3*$p_minus) * cos(3*$p_plus)
223             );
224              
225 232         555 my $I = $M + ORIGIN_NORTHING;
226 232         514 my $II = $nu/2 * $sp * $cp;
227 232         753 my $III = $nu/24 * $sp * $cp**3 * (5-$tp2+9*$eta2);
228 232         738 my $IIIA = $nu/720* $sp * $cp**5 *(61-58*$tp2+$tp4);
229              
230 232         474 my $IV = $nu*$cp;
231 232         626 my $V = $nu/6 * $cp**3 * ($nu/$rho-$tp2);
232 232         665 my $VI = $nu/120 * $cp**5 * (5-18*$tp2+$tp4+14*$eta2-58*$tp2*$eta2);
233              
234 232         471 my $dl = $lam - ORIGIN_LONGITUDE;
235 232         580 my $north = $I + ( $II + ( $III + $IIIA * $dl * $dl ) * $dl * $dl ) * $dl * $dl;
236 232         594 my $east = ORIGIN_EASTING + ( $IV + ( $V + $VI * $dl * $dl ) * $dl * $dl ) * $dl;
237              
238 232         926 return ($east, $north);
239             }
240              
241             sub _find_OSTN02_shifts_at {
242              
243 818     818   1914 my ($x, $y) = @_;
244              
245 818 100       2237 return if $x < 0;
246 816 50       1918 return if $y < 0;
247              
248 816         1794 my $e_index = int $x/1000;
249 816         1800 my $n_index = int $y/1000;
250              
251 816 100       2163 return if $n_index+1 > $#ostn_data;
252              
253 814         1624 my $row_size = 701;
254 814         1657 my $key = $e_index + $row_size * $n_index;
255 814 100       2519 if ( ! exists $ostn_shifts_for{$key} ) {
256 297         903 $ostn_shifts_for{$key} = _get_ostn_pair_reference($e_index, $n_index);
257             }
258 814 100       2375 return if ! $ostn_shifts_for{$key};
259              
260 802         1515 my ($se0,$sn0,$se1,$sn1) = @{$ostn_shifts_for{$key}};
  802         2575  
261              
262 802         1668 $key += $row_size;
263 802 100       2183 if ( ! exists $ostn_shifts_for{$key} ) {
264 291         870 $ostn_shifts_for{$key} = _get_ostn_pair_reference($e_index, $n_index+1);
265             }
266 802 100       2242 return if ! $ostn_shifts_for{$key};
267            
268 796         1403 my ($se2,$sn2,$se3,$sn3) = @{$ostn_shifts_for{$key}};
  796         1977  
269              
270 796         1959 my $t = $x/1000 - $e_index; # offset within square
271 796         1657 my $u = $y/1000 - $n_index;
272              
273 796         1649 my $f0 = (1-$t)*(1-$u);
274 796         1472 my $f1 = $t *(1-$u);
275 796         1522 my $f2 = (1-$t)* $u ;
276 796         1352 my $f3 = $t * $u ;
277              
278 796         1592 my $se = $f0*$se0 + $f1*$se1 + $f2*$se2 + $f3*$se3;
279 796         1542 my $sn = $f0*$sn0 + $f1*$sn1 + $f2*$sn2 + $f3*$sn3;
280              
281 796         2798 return ($se, $sn);
282             }
283              
284             sub _d32 {
285 2304     2304   6031 my ($a, $b, $c) = unpack 'CCC', $_;
286 2304         6766 return ($a << 10) + ($b << 5) + $c - 50736;
287             # the magic number here is (48<<10)+(48<<5)+48 to allow for us
288             # using a character set based at chr(48) = 0.
289             }
290              
291             sub _get_ostn_pair_reference {
292 588     588   1229 my $x = shift;
293 588         1089 my $y = shift;
294              
295 588         1891 my $leading_zeros = substr $ostn_data[$y], 0, 3;
296 588 100       1926 return if $x < $leading_zeros;
297              
298 584         1252 my $start = 3 + 6 * ( $x - $leading_zeros );
299 584 100       1593 return if $start + 12 > length $ostn_data[$y];
300              
301 579         1542 my $data = substr $ostn_data[$y], $start, 12;
302 579 100       1837 return if -1 < index $data, '000';
303              
304 576         3149 my @shifts = map { _d32 } unpack "A3A3A3A3", $data;
  2304         4946  
305              
306 576         1659 $shifts[0] += MIN_X_SHIFT;
307 576         1162 $shifts[1] += MIN_Y_SHIFT;
308 576         1155 $shifts[2] += MIN_X_SHIFT;
309 576         941 $shifts[3] += MIN_Y_SHIFT;
310              
311 576         1280 return [ map { $_ / 1000 } @shifts ];
  2304         7891  
312             }
313              
314             sub grid_to_ll {
315              
316 214     214 1 61137 my ($e, $n, $options) = @_;
317              
318 214 50 66     906 if (ref $e && defined $e->{e} && defined $e->{n}) {
      66        
319 1         2 $options = $e;
320 1         2 $e = $options->{e};
321 1         2 $n = $options->{n};
322             }
323              
324 214 100       886 my $shape = exists $options->{shape} ? $options->{shape} : $default_shape;
325              
326 214 50       821 croak "Unknown shape: $shape" if !exists ELLIPSOIDS->{$shape};
327              
328 214         731 my ($os_lat, $os_lon) = _reverse_project_onto_ellipsoid($e, $n, 'OSGB36');
329              
330             # if we want OS map LL we are done
331 214 100       802 if ($shape eq 'OSGB36') {
332 7         90 return ($os_lat, $os_lon)
333             }
334              
335             # If we want WGS84 LL, we must adjust to pseudo grid if we can
336 207         669 my ($dx, $dy) = _find_OSTN02_shifts_at($e,$n);
337 207 100       636 if ($dx) {
338 200         451 my $in_ostn02_polygon = 1;
339 200         551 my ($x,$y) = ($e-$dx, $n-$dy);
340 200         517 my ($last_dx, $last_dy) = ($dx, $dy);
341             APPROX:
342 200         657 for (1..20) {
343 399         992 ($dx, $dy) = _find_OSTN02_shifts_at($x,$y);
344              
345 399 100       1136 if (!$dx) {
346             # we have been shifted off the edge
347 1         1 $in_ostn02_polygon = 0;
348             last APPROX
349 1         3 }
350              
351 398         957 ($x,$y) = ($e-$dx, $n-$dy);
352 398 100 100     2031 last APPROX if abs($dx-$last_dx) < TENTH_MM
353             && abs($dy-$last_dy) < TENTH_MM;
354 199         599 ($last_dx, $last_dy) = ($dx, $dy);
355             }
356 200 100       869 if ($in_ostn02_polygon ) {
357 199         703 return _reverse_project_onto_ellipsoid($e-$dx, $n-$dy, 'WGS84')
358             }
359             }
360              
361             # If we get here, we must use the Helmert approx
362 8         25 return _shift_ll_from_osgb36_to_wgs84($os_lat, $os_lon)
363             }
364              
365             sub grid_to_ll_helmert {
366 3     3 1 8 my ($e, $n) = @_;
367 3         8 my ($os_lat, $os_lon) = _reverse_project_onto_ellipsoid($e, $n, 'OSGB36');
368 3         9 return _shift_ll_from_osgb36_to_wgs84($os_lat, $os_lon)
369             }
370              
371             sub _reverse_project_onto_ellipsoid {
372              
373 416     416   1136 my ( $easting, $northing, $shape ) = @_;
374              
375 416         742 my ( $a, $b, $f, $e2 ) = @{ ELLIPSOIDS->{$shape} };
  416         1363  
376              
377 416         1212 my $n = ( $a - $b ) / ( $a + $b );
378 416         922 my $af = $a * CONVERGENCE_FACTOR;
379              
380 416         852 my $dn = $northing - ORIGIN_NORTHING;
381 416         805 my $de = $easting - ORIGIN_EASTING;
382              
383 416         946 my $phi = ORIGIN_LATITUDE + $dn/$af;
384 416         800 my $lam = ORIGIN_LONGITUDE;
385              
386 416         783 my ($M, $p_plus, $p_minus);
387 416         805 while (1) {
388 1504         2501 $p_plus = $phi + ORIGIN_LATITUDE;
389 1504         2352 $p_minus = $phi - ORIGIN_LATITUDE;
390 1504         7701 $M = $b * CONVERGENCE_FACTOR * (
391             (1 + $n * (1 + 5/4*$n*(1 + $n)))*$p_minus
392             - 3*$n*(1+$n*(1+7/8*$n)) * sin( $p_minus) * cos( $p_plus)
393             + (15/8*$n * ($n*(1+$n))) * sin(2*$p_minus) * cos(2*$p_plus)
394             - 35/24*$n**3 * sin(3*$p_minus) * cos(3*$p_plus)
395             );
396 1504 100       4799 last if abs($dn-$M) < HUNDREDTH_MM;
397 1088         2104 $phi = $phi + ($dn-$M)/$af;
398             }
399              
400 416         1089 my $cp = cos $phi;
401 416         831 my $sp = sin $phi;
402 416         906 my $tp = $sp / $cp; # cos phi cannot be zero in GB
403              
404 416         965 my $splat = 1 - $e2 * $sp * $sp;
405 416         872 my $sqrtsplat = sqrt $splat;
406 416         795 my $nu = $af / $sqrtsplat;
407 416         969 my $rho = $af * (1 - $e2) / ( $splat * $sqrtsplat );
408 416         853 my $eta2 = $nu / $rho - 1;
409              
410 416         963 my $VII = $tp / (2 * $rho * $nu);
411 416         1419 my $VIII = $tp / (24 * $rho * $nu**3) * (5 + $eta2 + ( 3 - 9 * $eta2 ) * $tp * $tp );
412 416         1512 my $IX = $tp / (720 * $rho * $nu**5) * (61 + ( 90 + 45 * $tp * $tp ) * $tp * $tp );
413              
414 416         825 my $secp = 1/$cp;
415              
416 416         759 my $X = $secp / $nu;
417 416         1286 my $XI = $secp / ( 6 * $nu**3 ) * ( $nu / $rho + 2 * $tp * $tp );
418 416         1342 my $XII = $secp / ( 120 * $nu**5 ) * ( 5 + ( 28 + 24 * $tp * $tp ) * $tp * $tp );
419 416         1302 my $XIIA = $secp / ( 5040 * $nu**7 ) * ( 61 + ( 662 + ( 1320 + 720 * $tp * $tp ) * $tp * $tp ) * $tp * $tp );
420              
421 416         1058 $phi = $phi + ( -$VII + ( $VIII - $IX * $de * $de ) * $de * $de) * $de * $de;
422 416         972 $lam = $lam + ( $X + ( -$XI + ( $XII - $XIIA * $de * $de ) * $de * $de) * $de * $de) * $de;
423              
424             # now put into degrees & return
425 416         2629 return ($phi * 57.29577951308232087679815481410517,
426             $lam * 57.29577951308232087679815481410517);
427             }
428              
429             1;
430              
431             =pod
432              
433             =head1 NAME
434              
435             Geo::Coordinates::OSGB - Convert coordinates between Lat/Lon and the British National Grid
436              
437             An implementation of co-ordinate conversion for England, Wales, and Scotland
438             based on formulae and data published by the Ordnance Survey of Great Britain.
439              
440             =head1 VERSION
441              
442             2.19
443              
444             =for HTML
445            
446              
447             =head1 SYNOPSIS
448              
449             use Geo::Coordinates::OSGB qw(ll_to_grid grid_to_ll);
450              
451             ($easting,$northing) = ll_to_grid($lat,$lon);
452             ($lat,$lon) = grid_to_ll($easting,$northing);
453              
454             =head1 DESCRIPTION
455              
456             These modules convert accurately between an OSGB national grid reference and
457             coordinates given in latitude and longitude.
458              
459             In Version 2.10 and above, the default ellipsoid model used is the I
460             international standard WGS84. This means that you can take latitude and
461             longitude readings from your GPS receiver, or read them from Wikipedia, or
462             Google Earth, or your car's sat-nav, and use this module to convert them to
463             accurate British National grid references for use with one of the Ordnance
464             Survey's paper maps. And I, of course.
465              
466             The module is implemented purely in Perl, and should run on any platform with
467             Perl version 5.8 or better.
468              
469             In this description, the abbreviations `OS' and `OSGB' mean `the Ordnance
470             Survey of Great Britain': the British government agency that produces the
471             standard maps of England, Wales, and Scotland. Any mention of `sheets' or
472             `maps' refers to one or more of the map sheets defined in the accompanying maps
473             module.
474              
475             This code is written for the British national grid system. It is of no use
476             outside Britain. In fact it's only really useful in the areas covered by the
477             OS's main series of maps, which exclude the Channel Islands and Northern
478             Ireland.
479              
480             =head1 SUBROUTINES/METHODS
481              
482             The following functions can be exported from the
483             C module:
484              
485             grid_to_ll ll_to_grid
486              
487             Neither of these is exported by default.
488              
489             =head2 Main subroutines
490              
491             =head3 C
492              
493             C translates a latitude and longitude pair into a grid
494             easting and northing pair.
495              
496             When called in a list context, C returns the easting and
497             northing as a list of two. When called in a scalar context, it returns
498             a single string with the numbers separated by spaces.
499              
500             The arguments should be supplied as real numbers representing
501             decimal degrees, like this
502              
503             my ($e,$n) = ll_to_grid(51.5, -2.1); # (393154.801, 177900.605)
504              
505             Following the normal mathematical convention, positive arguments mean North or
506             East, negative South or West.
507              
508             If you have data with degrees, minutes and seconds, you can convert them
509             to decimals like this:
510              
511             my ($e,$n) = ll_to_grid(51+25/60, 0-5/60-2/3600);
512              
513             If you have trouble remembering the order of the arguments, or the returned
514             values, note that latitude comes before longitude in the alphabet too, as
515             easting comes before northing. However, since reasonable latitudes for the
516             OSGB are in the range 49 to 61, and reasonable longitudes in the range -9 to
517             +2, C accepts the arguments in either order; if your longitude is
518             larger than your latitude, then the values of the arguments will be silently
519             swapped.
520              
521             You can also supply the arguments as named keywords (but be sure to use
522             the curly braces so that you pass them as a reference):
523              
524             my ($e,$n) = ll_to_grid( { lat => 51.5, lon => -2.1 } );
525              
526             The easting and northing will be returned as the orthogonal distances in metres
527             from the `false point of origin' of the British Grid (which is a point some way
528             to the south-west of the Scilly Isles). The returned pair refers to a point on
529             the usual OSGB grid, which extends from the Scilly Isles in the south west to
530             the Shetlands in the north.
531              
532             my ($e,$n) = ll_to_grid(51.5, -2.1); # (393154.801, 177900.605)
533             my $s = ll_to_grid(51.5, -2.1); # "393154.801 177900.605"
534              
535             If the coordinates you supply are in the area covered by the OSTN02
536             transformation data, then the results will be rounded to 3 decimal places,
537             which corresponds to the nearest millimetre. If they are outside the coverage
538             (which normally means more than a few km off shore) then the conversion is
539             automagically done using a Helmert transformation instead of the OSTN02 data.
540             The results will be rounded to the nearest metre in this case, although you
541             probably should not rely on the results being more accurate than about 5m.
542              
543             # A point in the sea, to the north-west of Coll
544             my $s = ll_to_grid(56.75,-7); # returns "94471 773206"
545              
546             The numbers returned may be negative if your latitude and longitude are
547             far enough south and west, but beware that the transformation is less
548             and less accurate or useful the further you get from the British Isles.
549              
550             If you want the result presented in a more traditional grid reference
551             format you should pass the results to one of the grid formatting
552             routines from L. Like this.
553              
554             my $s = ll_to_grid(51.5, -2.1); # "393154.801 177900.605"
555             $s = format_grid(ll_to_grid(51.5,-2.1)); # "ST 931 779"
556             $s = format_grid_GPS(ll_to_grid(51.5,-2.1)); # "ST 93154 77900"
557             $s = format_grid_map(ll_to_grid(51.5,-2.1)); # "ST 931 779 on A:173, B:156, C:157"
558              
559             C also takes an optional argument that sets the ellipsoid
560             model to use. This defaults to `WGS84', the name of the normal model
561             for working with normal GPS coordinates, but if you want to work with
562             the traditional latitude and longitude values printed around the edges of
563             OS maps before 2015 then you should add an optional shape parameter like this:
564              
565             my ($e, $n) = ll_to_grid(49,-2, {shape => 'OSGB36'});
566              
567             Incidentally, if you make this call above you will get back
568             (400000,-100000) which are the coordinates of the `true point of origin'
569             of the British grid. You should get back an easting of 400000 for any
570             point with longitude 2W since this is the central meridian used for the
571             OSGB projection. However you will get a slightly different value unless
572             you specify C<< {shape => 'OSGB36'} >> because the WGS84 meridians are not
573             quite the same as OSGB36.
574              
575             =head3 C
576              
577             The routine C takes an easting and northing pair
578             representing the distance in metres from the `false point of origin' of
579             the OSGB grid and returns a pair of real numbers representing the
580             equivalent longitude and latitude coordinates in the WGS84 model.
581              
582             Following convention, positive results are North of the equator and East
583             of the prime meridian, negative numbers are South and West. The
584             fractional parts of the results represent decimal fractions of degrees.
585              
586             No special processing is done in scalar context because there is no
587             obvious assumption about how to round the results. You will just get
588             the length of the list returned, which is 2.
589              
590             The arguments must be an (easting, northing) pair representing the
591             absolute grid reference in metres from the point of origin. You can get
592             these from a traditional grid reference string by calling
593             C first.
594              
595             my ($lat, $lon) = grid_to_ll(parse_grid('SM 349 231'))
596              
597             An optional last argument defines the ellipsoid model to use just as it
598             does for C. This is only necessary is you are working
599             with an ellipsoid model other than WGS84. Pass the argument as a hash
600             ref with a `shape' key.
601              
602             my ($lat, $lon) = grid_to_ll(400000, 300000, {shape => 'OSGB36'});
603              
604             If you like named arguments then you can use a single hash ref for all
605             of them (this is strictly optional):
606              
607             my ($lat, $lon) = grid_to_ll({ e => 400000, n => 300000, shape => 'OSGB36'});
608              
609             The results returned will be floating point numbers with the default
610             Perl precision. Unless you are running with long double precision
611             floats you will get 13 decimal places for latitude and 14 places for
612             longitude; but this does not mean that the calculations are accurate to
613             that many places. The OS online conversion tools return decimal degrees
614             to only 6 places. A difference of 1 in the sixth decimal place
615             represents a distance on the ground of about 10 cm. This is probably a
616             good rule of thumb for the reliability of these calculations, but all
617             the available decimal places are returned so that you can choose the
618             rounding that is appropriate for your application. Here's one way to do
619             that:
620              
621             my ($lat, $lon) = map { sprintf "%.6f", $_ } grid_to_ll(431234, 312653);
622              
623              
624             =head2 Additional subroutines
625              
626             =head3 C
627              
628             The default ellipsoid shape used for conversion to and from latitude and
629             longitude is `WGS84' as used in the international GPS system. This
630             default it set every time that you load the module. If you want to
631             process or produce a large number latitude and longitude coordinates in
632             the British Ordnance Survey system (as printed round the edges of OS
633             Landranger and Explorer maps before 2015) you can use C<< set_default_shape('OSGB36'); >> to
634             set the default shape to OSGB36. This saves you having to add C<< {
635             shape => 'OSGB36' } >> to every call of C or C.
636              
637             You can use C<< set_default_shape('WGS84'); >> to set the default shape back
638             to WGS84 again when finished with OSGB36 coordinates.
639              
640             =head3 C
641              
642             You can use this function to do a quicker conversion from WGS84 lat/lon
643             to the OS grid without using the whole OSTN02 data set. The algorithm
644             used is known as a Helmert transformation. This is the usual coordinate
645             conversion algorithm implemented in most consumer-level GPS devices. It
646             is based on parameters supplied by the OS; they suggest that in most of
647             the UK this conversion is accurate to within about 5m.
648              
649             my ($e, $n) = ll_to_grid_helmert(51.477811, -0.001475); # RO Greenwich
650              
651             The input must be decimal degrees in the WGS84 model, with latitude
652             first and longitude second. The results are rounded to the nearest
653             whole metre. They can be used with C in the same way as
654             the results from C.
655              
656             This function is called automatically by C if your
657             coordinates are WGS84 and lie outside the OSTN02 polygon.
658              
659             =head3 C
660              
661             You can use this function to do a quicker conversion from OS grid
662             references to WGS84 latitude and longitude coordinates without using the
663             whole OSTN02 data set. The algorithm used is known as a Helmert
664             transformation. This is the usual coordinate conversion algorithm
665             implemented in most consumer-level GPS devices. It is based on
666             parameters supplied by the OS; they suggest that in most of the UK this
667             conversion is accurate to within about 5m.
668              
669             my ($lat, $lon) = grid_to_ll_helmert(538885, 177322);
670              
671             The input must be in metres from false point of origin (as produced by
672             C) and the results are in decimal degrees using the WGS84
673             model.
674              
675             The results are returned with the full Perl precision in the same way as
676             C so that you can choose an appropriate rounding for your
677             needs. Four or five decimal places is probably appropriate in most
678             cases. This represents somewhere between 1 and 10 m on the ground.
679              
680             This function is called automatically by C if the grid
681             reference you supply lies outside the OSTN02 polygon. (Generally such
682             spots are in the sea). The results are only reliable close to mainland
683             Britain.
684              
685             =head3 C
686              
687             This function provides an interface to the OSTN02 data. The input is an
688             easting, northing pair (in metres as usual), and the usual output is another
689             pair, also in metres, representing the easting and northing differences between
690             OSGB36 and WGS84 at this particular grid reference. The values returned are
691             the result of interpolation from the OSTN02 values at the four corners of the
692             relevant square kilometre. For more detail about OSTN02, see Background.pod.
693              
694             These values are those used to do the accurate conversion at the heart of this
695             module. You don't normally need these, but this interface is provided (a) for
696             debugging and testing and (b) as a way to check whether a point is covered by
697             one of the OSTN02 polygons.
698              
699             If a particular point is not in one of the polygons, this function will return
700             C. This feature is used by the by C function in
701             L to make sure that the chosen random point is
702             well within the OSTN02 boundary.
703              
704             If you are curious to see the boundaries, you should explore the
705             C script in the C directory.
706              
707             =head3 Importing all the functions
708              
709             You can import all the functions defined in C with an C<:all> tag.
710              
711             use Geo::Coordinates::OSGB ':all';
712              
713             =head1 EXAMPLES
714              
715             use Geo::Coordinates::OSGB qw/ll_to_grid grid_to_ll/;
716              
717             # Latitude and longitude according to the WGS84 model
718             ($lat, $lon) = grid_to_ll($e, $n);
719              
720             # and to go the other way
721             ($e, $n) = ll_to_grid($lat,$lon);
722              
723             See the test files for more examples of usage.
724              
725             =head1 BUGS AND LIMITATIONS
726              
727             The formulae supplied by the OS and used for the conversion routines are
728             specifically designed to be close floating-point approximations rather
729             than exact mathematical equivalences. So after round-trips like these:
730              
731             ($lat1,$lon1) = grid_to_ll(ll_to_grid($lat0,$lon0));
732             ($e1,$n1) = ll_to_grid(grid_to_ll($e0,$n0));
733              
734             neither C<$lat1 == $lat0> nor C<$lon1 == $lon0> nor C<$e1 == $e0> nor
735             C<$n1 == $n0> exactly. However the differences should be very small.
736              
737             The OS formulae were designed to give an accuracy of about 1 mm of
738             error. This means that you can rely on the third decimal place for grid
739             references and about the seventh or eighth for latitude and longitude
740             (although the OS themselves only provide six decimal places in their
741             results).
742              
743             For all of England, Wales, Scotland, and the Isle of Man the error will
744             be tiny. All other areas, like Northern Ireland, the Channel Islands or
745             Rockall, and any areas of sea more than a few miles off shore, are
746             outside the coverage of OSTN02, so the simpler, less accurate
747             transformation is used. The OS state that this is accurate to about 5m
748             but that the parameters used are only valid in the reasonably close
749             vicinity of the British Isles.
750              
751             Not enough testing has been done. I am always grateful for the feedback
752             I get from users, but especially for problem reports that help me to
753             make this a better module.
754              
755             =head1 DIAGNOSTICS
756              
757             The only error message you will get from this module is about the
758             ellipsoid shape used for the transformation. If you try to set C<<
759             {shape => 'blah'} >> the module will croak with a message saying
760             C. The shape should be one of the shapes defined:
761             WGS84 or OSGB36.
762              
763             Should this software not do what you expect, then please first read this
764             documentation, secondly verify that you have installed it correctly and
765             that it passes all the installation tests on your set up, thirdly study
766             the source code to see what it's supposed to be doing, fourthly get in
767             touch to ask me about it.
768              
769             =head1 CONFIGURATION AND ENVIRONMENT
770              
771             There is no configuration required either of these modules or your
772             environment. It should work on any recent version of Perl, on any
773             platform.
774              
775             =head1 DEPENDENCIES
776              
777             Perl 5.08 or better.
778              
779             =head1 INCOMPATIBILITIES
780              
781             None known.
782              
783             =head1 LICENSE AND COPYRIGHT
784              
785             Copyright (C) 2002-2017 Toby Thurston
786              
787             OSTN02 transformation data included in this module is freely available
788             from the Ordnance Survey but remains Crown Copyright (C) 2002
789              
790             This program is free software; you can redistribute it and/or modify it
791             under the terms of the GNU General Public License as published by the
792             Free Software Foundation; either version 2 of the License, or (at your
793             option) any later version.
794              
795             This program is distributed in the hope that it will be useful, but
796             WITHOUT ANY WARRANTY; without even the implied warranty of
797             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
798             General Public License for more details.
799              
800             You should have received a copy of the GNU General Public License along
801             with this program; if not, write to the Free Software Foundation, Inc.,
802             51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
803              
804             =head1 AUTHOR
805              
806             Toby Thurston -- 30 Jul 2017
807              
808             toby@cpan.org
809              
810             =head1 SEE ALSO
811              
812             See L for routines to format grid references.
813              
814             The UK Ordnance Survey's explanations on their web pages.
815              
816             See L for a general approach (not based on the OSGB).
817              
818             =cut
819              
820             # leave blank line after cut so that pod2html works properly
821              
822             __DATA__