NAME¶
Geo::Coordinates::OSTN02 - An implementation of the OSGB's OSTN02 transformation
SYNOPSIS¶
use Geo::Coordinates::OSTN02 qw/OSGB36_to_ETRS89 ETRS89_to_OSGB36/;
($x, $y, $z) = OSGB36_to_ETRS89($e, $n, $h);
($e, $n, $h) = ETRS89_to_OSGB36($x, $y, $z);
DESCRIPTION¶
The purpose and use of these modules is described briefly in the companion
Geo::Coordinates::OSGB modules. In essence they implement in Perl the Ordnance
Survey's OSTN02 transformation which is part of the current definition of the
British National Grid. You are strongly recommended to read the official
public guides and other introductory documents that are published by the
Ordnance Survey of Great Britain with a wealth of other technical information
about the OSTN02 transformation.
FUNCTIONS¶
The following functions can be exported from the
"Geo::Coordinates::OSTN02" module:
OSGB36_to_ETRS89
ETRS89_to_OSGB36
None is exported by default.
- OSGB36_to_ETRS89(easting, northing, [elevation])
- Transforms from normal OSGB36 grid references to a pseudo-grid based on
the WGS84/ETRS89 geoid model, which can then be translated to lat/long
coordinates using "grid_to_ll()" with the 'WGS84' parameter.
my $elevation = '100'; # in metres
my ($e, $n) = parse_grid_trad('TQ304293');
my ($x, $y, $z) = OSGB36_to_ETRS89($e, $n, $elevation);
my ($lat, $lon) = grid_to_ll($x, $y, 'ETRS89'); # or 'WGS84'
Elevation will default to 0 metres if you omit it.
- ETRS89_to_OSGB36(x, y, [z])
- Transforms WGS84/ETRS89 pseudo-grid coordinates into OSGB36 grid
references.
my ($lat, $lon, $height) = (51.5, -1, 10); # Somewhere in London
my ($x, $y) = ll_to_grid($lat, $lon, 'ETRS89'); # or 'WGS84'
my ($e, $n, $elevation) = ETRS89_to_OSGB36($x, $y, $height);
IMPLEMENTATION NOTES¶
Since 2002 the British Ordnance Survey have defined the UK national grid not as
a projection from their own model of the earth (the Airy 1830 geoid, revised
1936, known as OSGB36), but as a simple table of calculated differences from a
projection based on the European standard geoid ETRS89 (which is for Europe a
functional equivalent of the international WGS84 geoid). This revision is
known as OSGM02 and the transformation is called OSTN02.
The idea is that you project your WGS84 latitude and longitude coordinates into
WGS84 (or ETRS89) pseudo-grid references, and then look up the appropriate
three dimensional correction in the OS table, and add the results to your grid
reference to give you a standard British National Grid reference that you can
use with British OS maps. Going back the other way is slightly more
complicated, as you have to work backwards, but a simple iteration will do the
job. This package implements these lookups and adjustments. You give it a
three dimensional grid reference (easting, northing, and altitude, all in
metres) and the package returns it corrected from one system to the other.
The problem in the implementation is that the table is huge, and most of it is
empty as the OS have chosen to leave the transformation undefined for areas
that are off shore. So this package only works properly for grid references
that are actually on one or more OS maps. The complete table (including all
the 0 lines) contains nearly 1 million lines with six data points and a key.
In text form as supplied by the OS that is about 36M bytes of table. By
leaving out the lines where the transformation is undefined, omitting a couple
of redundant fields, and storing everything as hex strings, this module brings
the amount of data down to just over 6M bytes, which loads in about 1 second
on my test system. It would be possible to compress the data down to 3M bytes
by storing it as packed decimals, but then it would be difficult to include
inline in this module, as it would break every time I edited it.
The data is stored below, after the __DATA__ line. Each line is 18 bytes long
and represents the transformation parameters for an individual grid square of
1km by 1km. Each line contains five fields all expressed as hexadecimal
integers.
Start Length Meaning
0 3 The northing of the square in km
3 3 The easting of the square in km
6 4 The x-offset in mm (easting)
10 4 The y-offset in mm (northing)
14 4 The z-offset in mm (elevation)
To keep the numbers small and positive the values given for the offsets are
actually the amount that they exceed the respective smallest values in the
data set. Currently these minima are x: 86275mm, y: -81603mm, and z: 43982mm.
So when we read a line from the data we have to add these minima to the
values, convert to decimal, and multiply by 1000 to get back to metres.
When you load the OSTN02 module, the first thing it does is to load all 309,798
lines into an array called @ostn_data by simply doing this.
our @ostn_data = <DATA>;
This is why it takes a few microseconds to load the module, but once loaded it's
all very fast.
When we need to find the data values for a given grid reference, we work out the
appropriate grid square by truncating the easting and northing to the next
lowest whole kilometer, and pass these as the argument to the
"_get_ostn_ref" subroutine. This is the only subroutine that
actually touches the data.
The core of the subroutine is a binary search. We work out the key by converting
the northing and easting to hexadecimal and concatenating them. We add leading
zeros so that each value is exactly three bytes long and the combined key is
exactly six bytes long. The maximum value for easting in the OSGB grid is
700km and for northing 1250km, which are 2BC and 4E2, so each fits into three
bytes. (And by the time this is called we have already checked that the values
don't exceed the maxima).
This works pretty quickly, the only slow bit is loading the array at the
beginning, but it is much faster and needs *much* less memory than loading all
the data into the hash. (This would be simpler, and is what the original
version did, but it was too slow to be usable and meant that the tests failed
on many smaller systems as part of CPAN testing). We do still use a hash, but
only to cache lines that we've already found. Read the code for details. This
only gives a tiny speed up in general, so I might remove it in future
versions.
Back in 2008, loading the array took about 1.2 seconds on my Windows machine (a
2.8G Hz Intel Pentium M processor with 2G byte of memory) and just under 0.5
seconds on my Linux system (a 2.8G Hz Intel Pentium 4 processor with 512M
bytes of memory). I think that probably said more about speed of the disks I
had (and probably the efficiency of Perl under Linux) back then. Today on my
newish Mac Mini there is only a very slightly noticeable delay. Once the data
is loaded, calling the routines is reasonably quick.
BUGS¶
Please report any to toby@cpan.org
EXAMPLES¶
See the test routines included in the distribution.
AUTHOR¶
Toby Thurston -- 04 Oct 2013
toby@cpan.org
SEE ALSO¶
See Geo::Coordinates::OSGB for the main routines.