.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is turned on, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{ . if \nF \{ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "Geo::Coordinates::OSGB 3pm" .TH Geo::Coordinates::OSGB 3pm "2013-10-04" "perl v5.18.1" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" Geo::Coordinates::OSGB \- Convert coordinates between Lat/Lon and the British National Grid .PP An implementation of co\-ordinate conversion for England, Wales, and Scotland based on formulae published by the Ordnance Survey of Great Britain. .PP These modules will convert accurately between an OSGB national grid reference and lat/lon coordinates based on the OSGB geoid model. (For an explanation of what a geoid model is and why you should care, read the Theory section below.) The OSGB geoid model fits mainland Britain very well, but is rather different from the international WGS84 model that has rapidly become the de facto universal standard model thanks to the popularity of GPS devices and maps on the Internet. So, if you are trying to translate from an OSGB grid reference to lat/lon coordinates that can be used in Google Earth, Wikipedia, or some other Internet based tool, you will need to do two transformations: first translate your grid ref into OSGB lat/lon; then nudge the result into WGS84. Routines are provided to do both of these operations, but they are only approximate. The inaccuracy of the approximation varies according to where you are in the country but may be as much as several metres in some areas. .PP To get more accurate results you need to combine this module with its companion Geo::Coordinates::OSTN02 which implements the transformation that now defines the relationship between GPS survey data based on WGS84 and the British National Grid. Using this module you should be able to get results that are accurate to within a few centimetres, but it is slightly slower and requires more memory to run. .PP Note that the OSGB (and therefore this module) does not cover the whole of the British Isles, nor even the whole of the UK, in particular it covers neither the Channel Islands nor Northern Ireland. The coverage that is included is essentially the same as the coverage provided by the OSGB "Landranger" 1:50000 series maps. .SH "VERSION" .IX Header "VERSION" Examine \f(CW$Geo::Coordinates::OSGB::VERSION\fR for details. .SH "SYNOPSIS" .IX Header "SYNOPSIS" .Vb 1 \& use Geo::Coordinates::OSGB qw(ll_to_grid grid_to_ll); \& \& # Basic conversion routines \& ($easting,$northing) = ll_to_grid($lat,$lon); \& ($lat,$lon) = grid_to_ll($easting,$northing); .Ve .SH "DESCRIPTION" .IX Header "DESCRIPTION" These modules provide a collection of routines to convert between coordinates expressed as latitude & longtitude and map grid references, using the formulae given in the British Ordnance Survey's excellent information leaflet, referenced below in the Theory section. There are some key concepts explained in that section that you need to know in order to use these modules successfully, so you are recommended to at least skim through it now. .PP The module is implemented purely in Perl, and should run on any Perl platform. .PP In this description `\s-1OS\s0' means `the Ordnance Survey of Great Britain': the British government agency that produces the standard maps of England, Wales, and Scotland. Any mention of `sheets' or `maps' refers to one or more of the 204 sheets in the 1:50,000 scale `Landranger' series of \s-1OS\s0 maps. .PP This code is fine tuned to the British national grid system. You could use it elsewhere but you would need to adapt it. Some starting points for doing this are explained in the Theory section below. .SH "SUBROUTINES/METHODS" .IX Header "SUBROUTINES/METHODS" The following functions can be exported from the \f(CW\*(C`Geo::Coordinates::OSGB\*(C'\fR module: .PP .Vb 1 \& grid_to_ll ll_to_grid \& \& shift_ll_into_WGS84 shift_ll_from_WGS84 \& \& parse_grid \& parse_trad_grid format_grid_trad \& parse_GPS_grid format_grid_GPS \& parse_landranger_grid format_grid_landranger \& \& parse_ISO_ll format_ll_trad \& format_ll_ISO .Ve .PP None of these is exported by default, so pick the ones you want or use an \f(CW\*(C`:all\*(C'\fR tag to import them all at once. .PP .Vb 1 \& use Geo::Coordinates::OSGB \*(Aq:all\*(Aq; .Ve .IP "ll_to_grid(lat,lon)" 4 .IX Item "ll_to_grid(lat,lon)" When called in a void context, or with no arguments \f(CW\*(C`ll_to_grid\*(C'\fR does nothing. .Sp When called in a list context, \f(CW\*(C`ll_to_grid\*(C'\fR returns two numbers that represent the easting and the northing corresponding to the latitude and longitude supplied. .Sp The parameters can be supplied as real numbers representing decimal degrees, like this .Sp .Vb 1 \& my ($e,$n) = ll_to_grid(51.5, 2.1); .Ve .Sp Following the normal convention, positive numbers mean North or East, negative South or West. If you have data with degrees, minutes and seconds, you can convert them to decimals like this: .Sp .Vb 1 \& my ($e,$n) = ll_to_grid(51+25/60, 0\-5/60\-2/3600); .Ve .Sp Or you can use a single string in \s-1ISO 6709\s0 form, like this: .Sp .Vb 1 \& my ($e,$n) = ll_to_grid(\*(Aq+5130\-00005/\*(Aq); .Ve .Sp To learn exactly what is matched by this last option, read the source of the module and look for the definition of \f(CW$ISO_LL_PATTERN\fR. Note that the neither the \f(CW\*(C`+\*(C'\fR or \f(CW\*(C`\-\*(C'\fR signs at the beginning and in the middle, nor the trailing \f(CW\*(C`/\*(C'\fR may be omitted. .Sp If you have trouble remembering the order of the arguments, or the returned values, note that latitude comes before longitude in the alphabet too, as easting comes before northing. .Sp The easting and northing will be returned as a whole number of metres from the point of origin of the British Grid (which is a point a little way to the south-west of the Scilly Isles). .Sp If you want the result presented in a more traditional grid reference format you should pass the results to one of the grid formatting routines, which are described below. Like this. .Sp .Vb 3 \& $gridref = format_grid_trad(ll_to_grid(51.5,\-0.0833)); \& $gridref = format_grid_GPS(ll_to_grid(51.5,\-0.0833)); \& $gridref = format_grid_landranger(ll_to_grid(51.5,\-0.0833)); .Ve .Sp However if you call \f(CW\*(C`ll_to_grid\*(C'\fR in a scalar context, it will automatically call \f(CW\*(C`format_grid_trad\*(C'\fR for you. .Sp It is not needed for any normal work, but \f(CW\*(C`ll_to_grid()\*(C'\fR also takes an optional argument that sets the ellipsoid model to use. This normally defaults to `\s-1OSGB36\s0', the name of the normal model for working with British maps. If you are working with the highly accurate \s-1OSTN02\s0 conversions supplied in the companion module in this distribution, then you will need to produce pseudo-grid references as input to those routines. For these purposes you should call \f(CW\*(C`ll_to_grid()\*(C'\fR like this: .Sp .Vb 1 \& my $pseudo_gridref = ll_to_grid(51.2, \-0.4, \*(AqWGS84\*(Aq); .Ve .Sp and then transform this to a real grid reference using \f(CW\*(C`ETRS89_to_OSGB36()\*(C'\fR from the companion module. This is explained in more detail below. .IP "format_grid_trad(e,n)" 4 .IX Item "format_grid_trad(e,n)" Formats an (easting, northing) pair into traditional `full national grid reference' with two letters and two sets of three numbers, like this `\s-1TQ 102 606\s0'. If you want to remove the spaces, just apply \f(CW\*(C`s/\es//g\*(C'\fR to it. .Sp .Vb 2 \& $gridref = format_grid_trad(533000, 180000); # TQ 330 800 \& $gridref =~ s/\es//g; # TQ330800 .Ve .Sp If you want the individual components call it in a list context. .Sp .Vb 1 \& ($sq, $e, $n) = format_grid_trad(533000, 180000); # (TQ,330,800) .Ve .Sp Note the easting and northing are truncated to hectometers (as the \s-1OS\s0 system demands), so the grid reference refers to the lower left corner of the relevant 100m square. .IP "format_grid_GPS(e,n)" 4 .IX Item "format_grid_GPS(e,n)" Users who have bought a \s-1GPS\s0 receiver may initially have been puzzled by the unfamiliar format used to present coordinates in the British national grid format. On my Garmin Legend C it shows this sort of thing in the display. .Sp .Vb 2 \& TQ 23918 \& bng 00972 .Ve .Sp and in the track logs the references look like this \f(CW\*(C`TQ 23918 00972\*(C'\fR. .Sp These are just the same as the references described on the \s-1OS\s0 sheets, except that the units are metres rather than hectometres, so you get five digits in each of the easting and northings instead of three. So in a scalar context \&\f(CW\*(C`format_grid_GPS()\*(C'\fR returns a string like this: .Sp .Vb 1 \& $gridref = format_grid_GPS(533000, 180000); # TQ 33000 80000 .Ve .Sp If you call it in a list context, you will get a list of square, easting, and northing, with the easting and northing as metres within the grid square. .Sp .Vb 1 \& ($sq, $e, $n) = format_grid_GPS(533000, 180000); # (TQ,33000,80000) .Ve .Sp Note that, at least until \s-1WAAS\s0 is working in Europe, the results from your \&\s-1GPS\s0 are unlikely to be more accurate than plus or minus 5m even with perfect reception. Most \s-1GPS\s0 devices can display the accuracy of the current fix you are getting, but you should be aware that all normal consumer-level \s-1GPS\s0 devices can only ever produce an approximation of an \s-1OS\s0 grid reference, no matter what level of accuracy they may display. The reasons for this are discussed below in the section on Theory. .IP "format_grid_landranger(e,n)" 4 .IX Item "format_grid_landranger(e,n)" This routine does the same as \f(CW\*(C`format_grid_trad\*(C'\fR, but it appends the number of the relevant \s-1OS\s0 Landranger 1:50,000 scale map to the traditional grid reference. Note that there may be several or no sheets returned. This is because many (most) of the Landranger sheets overlap, and many other valid grid references are not on any of the sheets (because they are in the sea or a remote island. This module does not yet cope with the detached insets on some sheets. .Sp In a list context you will get back a list like this: (square, easting, northing, sheet) or (square, easting, northing, sheet1, sheet2) etc. There are a few places where three sheets overlap, and one corner of Herefordshire which appears on four maps (sheets 137, 138, 148, and 149). If the \s-1GR\s0 is not on any sheet, then the list of sheets will be empty. .Sp In a scalar context you will get back the same information in a helpful string form like this \*(L"\s-1NN 241 738\s0 on \s-1OS\s0 Sheet 44\*(R". Note that the easting and northing will have been truncated to the normal hectometre three digit form. The idea is that you'll use this form for people who might actually want to look up the grid reference on the given map sheet, and the traditional \&\s-1GR\s0 form is quite enough accuracy for that purpose. .IP "parse_trad_grid(grid_ref)" 4 .IX Item "parse_trad_grid(grid_ref)" Turns a traditional grid reference into a full easting and northing pair in metres from the point of origin. The \fIgrid_ref\fR can be a string like \&\f(CW\*(AqTQ203604\*(Aq\fR or \f(CW\*(AqSW 452 004\*(Aq\fR, or a list like this \f(CW\*(C`(\*(AqTV\*(Aq, \*(Aq435904\*(Aq)\*(C'\fR or a list like this \f(CW\*(C`(\*(AqNN\*(Aq, \*(Aq345\*(Aq, \*(Aq208\*(Aq)\*(C'\fR. .IP "parse_GPS_grid(grid_ref)" 4 .IX Item "parse_GPS_grid(grid_ref)" Does the same as \f(CW\*(C`parse_trad_grid\*(C'\fR but is looking for five digit numbers like \f(CW\*(AqSW 45202 00421\*(Aq\fR, or a list like this \f(CW\*(C`(\*(AqNN\*(Aq, \*(Aq34592\*(Aq, \*(Aq20804\*(Aq)\*(C'\fR. .IP "parse_landranger_grid(sheet, e, n)" 4 .IX Item "parse_landranger_grid(sheet, e, n)" This converts an \s-1OS\s0 Landranger sheet number and a local grid reference into a full easting and northing pair in metres from the point of origin. .Sp The \s-1OS\s0 Landranger sheet number should be between 1 and 204 inclusive (but I may extend this when I support insets). You can supply \f(CW\*(C`(e,n)\*(C'\fR as 3\-digit hectometre numbers or 5\-digit metre numbers. In either case if you supply any leading zeros you should 'quote' the numbers to stop Perl thinking that they are octal constants. .Sp This module will croak at you if you give it an undefined sheet number, or if the grid reference that you supply does not exist on the sheet. .Sp In order to get just the coordinates of the \s-1SW\s0 corner of the sheet, just call it with the sheet number. It is easy to work out the coordinates of the other corners, because all \s-1OS\s0 Landranger maps cover a 40km square (if you don't count insets or the occasional sheet that includes extra details outside the formal margin). .IP "parse_grid(grid_ref)" 4 .IX Item "parse_grid(grid_ref)" Attempts to match a grid reference some form or other in the input string and will then call the appropriate grid parsing routine from those defined above. In particular it will parse strings in the form \f(CW\*(Aq176\-345210\*(Aq\fR meaning grid ref 345 210 on sheet 176, as well as \f(CW\*(AqTQ345210\*(Aq\fR and \f(CW\*(AqTQ 34500 21000\*(Aq\fR etc. You can in fact always use \*(L"parse_grid\*(R" instead of the more specific routines unless you need to be picky about the input. .IP "grid_to_ll(e,n) or grid_to_ll(grid_ref)" 4 .IX Item "grid_to_ll(e,n) or grid_to_ll(grid_ref)" When called in list context \f(CW\*(C`grid_to_ll()\*(C'\fR returns a pair of numbers representing longitude and latitude coordinates, as real numbers. Following convention, positive numbers are North and East, negative numbers are South and West. The fractional parts of the results represent fractions of degrees. .Sp When called in scalar context it returns a string in \s-1ISO\s0 longitude and latitude form, such as \f(CW\*(Aq+5025\-00403/\*(Aq\fR with the result rounded to the nearest minute (the formulae are not much more accurate than this). In a void context it does nothing. .Sp The arguments must be an (easting, northing) pair representing the absolute grid reference in metres from the point of origin. You can get these from a grid reference string by calling \f(CW\*(C`parse_grid()\*(C'\fR first. .Sp An optional last argument defines the geoid model to use just as it does for \&\f(CW\*(C`ll_to_grid()\*(C'\fR. This is only necessary is you are working with the pseudo-grid references produced by the \s-1OSTN02\s0 routines. See Theory for more discussion. .IP "format_ll_trad(lat, lon)" 4 .IX Item "format_ll_trad(lat, lon)" Takes latitude and longitude in decimal degrees as arguments and returns a string like this .Sp .Vb 1 \& N52:12:34 W002:30:27 .Ve .Sp In a list context it returns all 8 elements (hemisphere, degrees, minutes, seconds for each of lat and lon) in a list. In a void context it does nothing. .IP "format_ll_ISO(lat, lon)" 4 .IX Item "format_ll_ISO(lat, lon)" Takes latitude and longitude in decimal degrees as arguments and returns a string like this .Sp .Vb 1 \& +5212\-00230/ .Ve .Sp In a list context it returns all 6 elements (sign, degrees, minutes for each of lat and lon) in a list. In a void context it does nothing. .IP "parse_ISO_ll(ISO_string)" 4 .IX Item "parse_ISO_ll(ISO_string)" Reads an \s-1ISO 6709\s0 formatted location identifier string such as '+5212\-00230/'. To learn exactly what is matched by this last option, read the source of the module and look for the definition of \f(CW$ISO_LL_PATTERN\fR. Note that the neither the \f(CW\*(C`+\*(C'\fR or \f(CW\*(C`\-\*(C'\fR signs at the beginning and in the middle, nor the trailing \f(CW\*(C`/\*(C'\fR may be omitted. These strings can also include the altitude of a point, in metres, like this: '+5212\-00230+140/'. If you omit the altitude, 0 is assumed. .Sp In a list context it returns ($lat, \f(CW$lon\fR, \f(CW$altitude\fR). So if you don't want or don't need the altitude, you should just drop it, for example like this: .Sp .Vb 1 \& my ($lat, $lon) = parse_ISO_ll(\*(Aq+5212\-00230/\*(Aq) .Ve .Sp In normal use you won't notice this. In particular you don't need to worry about it when passing the results on to \f(CW\*(C`ll_to_grid\*(C'\fR, as that routine looks for an optional altitude after the lat/lon. .IP "shift_ll_from_WGS84(lat, lon, altitude)" 4 .IX Item "shift_ll_from_WGS84(lat, lon, altitude)" Takes latitude and longitude in decimal degrees (plus an optional altitude in metres) from a \s-1WGS84\s0 source (such as your \s-1GPS\s0 handset or Google Earth) and returns an approximate equivalent latitude and longitude according to the \&\s-1OSGM02\s0 model. To determine the \s-1OSGB\s0 grid reference for given \s-1WGS84\s0 lat/lon coordinates, you should call this before you call \f(CW\*(C`ll_to_grid\*(C'\fR. Like so: .Sp .Vb 2 \& ($lat, $lon, $alt) = shift_ll_from_WGS84($lat, $lon, $alt); \& ($e, $n) = ll_to_grid($lat,$lon); .Ve .Sp You don't need to call this to determine a grid reference from lat/lon coordinates printed on \s-1OSGB\s0 maps (the so called \*(L"graticule intersections\*(R" marked in pale blue on the Landranger series). .Sp This routine provide a fast approximation; for a slower, more accurate approximation use the companion Geo::Coordinates::OSTN02 modules. .IP "shift_ll_into_WGS84(lat, lon, altitude)" 4 .IX Item "shift_ll_into_WGS84(lat, lon, altitude)" Takes latitude and longitude in decimal degrees (plus an optional altitude in metres) from an \s-1OSGB\s0 source (such as coordinates you read from a Landranger map, or more likely coordinates returned from \f(CW\*(C`grid_to_ll()\*(C'\fR) and adjusts them to fit the \s-1WGS84\s0 model. .Sp To determine \s-1WGS84\s0 lat/lon coordinates (for use in Wikipedia, or Google Earth etc) for a given \s-1OSGB\s0 grid reference, you should call this after you call \&\f(CW\*(C`grid_to_ll()\*(C'\fR. Like so: .Sp .Vb 2 \& ($lat, $lon) = grid_to_ll($e, $n); \& ($lat, $lon, $alt) = shift_ll_into_WGS84($lat, $lon, $alt); .Ve .Sp This routine provide a fast approximation; for a slower, more accurate approximation use the companion Geo::Coordinates::OSTN02 modules. .SH "THEORY" .IX Header "THEORY" The algorithms and theory for these conversion routines are all from \&\fIA Guide to Coordinate Systems in Great Britain\fR published by the \s-1OSGB,\s0 April 1999 (Revised Dec 2010) and available at http://www.ordnancesurvey.co.uk/. .PP You may also like to read some of the other introductory material there. Should you be hoping to adapt this code to your own custom Mercator projection, you will find the paper called \fISurveying with the National \s-1GPS\s0 Network\fR, especially useful. .PP The routines are intended for use in Britain with the Ordnance Survey's National Grid, however they are written in an entirely generic way, so that you could adapt them to any other ellipsoid model that is suitable for your local area of the earth. There are other modules that already do this that may be more suitable (which are referenced in the \*(L"See Also\*(R" section), but the key parameters are all defined at the top of the module. .PP .Vb 6 \& $ellipsoid_shapes{OSGB36} = [ 6377563.396, 6356256.910 ]; \& use constant ORIGIN_LONGITUDE => RAD * \-2; # lon of grid origin \& use constant ORIGIN_LATITUDE => RAD * 49; # lat of grid origin \& use constant ORIGIN_EASTING => 400000; # Easting for origin \& use constant ORIGIN_NORTHING => \-100000; # Northing for origin \& use constant CONVERGENCE_FACTOR => 0.9996012717; # Convergence factor .Ve .PP The ellipsoid model is defined by two numbers that represent the major and minor radius measured in metres. The Mercator grid projection is then defined by the other five parameters. The general idea is that you pick a suitable point to start the grid that minimizes the inevitable distortion that is involved in a Mercator projection from spherical to Euclidean coordinates. Such a point should be on a meridian that bisects the area of interest and is nearer to the equator than the whole area. So for Britain the point of origin is 2W and 49N (in the \s-1OSGB\s0 geoid model) which is near the Channel Islands. This point should be set as the \f(CW\*(C`ORIGIN_LONGITUDE\*(C'\fR and \f(CW\*(C`ORIGIN_LATITUDE\*(C'\fR parameters (as above) measured in radians. Having this True Point of Origin in the middle and below (or above if you are antipodean) minimizes distortion but means that some of the grid values would be negative unless you then also adjust the grid to make sure you do not get any negative values in normal use. This is done by defining the grid coordinates of the True Point of Origin to be such that all the coordinates in the area of interest will be positive. These are the parameters \f(CW\*(C`ORIGIN_EASTING\*(C'\fR and \f(CW\*(C`ORIGIN_NORTHING\*(C'\fR. For Britain the coordinates are set as 400000 and \-100000, so the that point (0,0) in the grid is just to the south west of the Scilly Isles. This (0,0) point is called the False Point of Origin. The fifth parameter affects the convergence of the Mercator projection as you get nearer the pole; this is another feature designed to minimize distortion, and if in doubt set it to 1 (which means it has no effect). For Britain, being so northerly it is set to slightly less than 1. .SS "The British National Grid" .IX Subsection "The British National Grid" One consequence of the True Point of Origin of the British Grid being set to \&\f(CW\*(C`+4900\-00200/\*(C'\fR is that all the vertical grid lines are parallel to the 2W meridian; you can see this on the appropriate \s-1OS\s0 maps (for example Landranger sheet 184), or on the \f(CW\*(C`plotmaps.pdf\*(C'\fR picture supplied with this package. The effect of moving the False Point of Origin to the far south west is that all grid references always positive. .PP Strictly grid references are given as whole numbers of metres from this point, with the easting always given before the northing. For everyday use however, the \s-1OSGB\s0 suggest that grid references need only to be given within the local 100km square as this makes the numbers smaller. For this purpose they divide Britain into a series of 100km squares identified in pair of letters: \s-1TQ, SU, ND,\s0 etc. The grid of the big squares actually used is something like this: .PP .Vb 10 \& HP \& HU \& HY \& NA NB NC ND \& NF NG NH NJ NK \& NL NM NN NO NP \& NR NS NT NU \& NW NX NY NZ \& SC SD SE TA \& SH SJ SK TF TG \& SM SN SO SP TL TM \& SR SS ST SU TQ TR \& SV SW SX SY SZ TV .Ve .PP \&\s-1SW\s0 covers most of Cornwall, \s-1TQ\s0 London, and \s-1HU\s0 the Shetlands. Note that it has the neat feature that N and S are directly above each other, so that most Sx squares are in the south and most Nx squares are in the north. .PP Within each of these large squares, we only need five digit coordinates \-\-\- from (0,0) to (99999,99999) \-\-\- to refer to a given square metre. For daily use however we don't generally need such precision, so the normal recommended usage is to use units of 100m (hectometres) so that we only need three digits for each easting and northing \-\-\- 000,000 to 999,999. If we combine the easting and northing we get the familiar traditional six figure grid reference. Each of these grid references is repeated in each of the large 100km squares but for local use with a particular map, this does not usually matter. Where it does matter, the \s-1OS\s0 suggest that the six figure reference is prefixed with the identifier of the large grid square to give a `full national grid reference', such as \s-1TQ330800. \s0 This system is described in the notes of in the corner of every Landranger 1:50,000 scale map. .PP Modern \s-1GPS\s0 receivers can all display coordinates in the \s-1OS\s0 grid system. You just need to set the display units to be `British National Grid' or whatever similar name is used on your unit. Most units display the coordinates as two groups of five digits and a grid square identifier. The units are metres within the grid square (although beware that the \s-1GPS\s0 fix is unlikely to be accurate down to the last metre). .SS "Geoid models" .IX Subsection "Geoid models" This section explains the fundamental problems of mapping a spherical earth onto a flat piece of paper (or computer screen). A basic understanding of this material will help you use these routines more effectively. It will also provide you with a good store of ammunition if you ever get into an argument with someone from the Flat Earth Society. .PP It is a direct consequence of Newton's law of universal gravitation (and in particular the bit that states that the gravitational attraction between two objects varies inversely as the square of the distance between them) that all planets are roughly spherical. (If they were any other shape gravity would tend to pull them into a sphere). On the other hand, most useful surfaces for displaying large scale maps (such as pieces of paper or screens) are flat. There is therefore a fundamental problem in making any maps of the earth that its curved surface being mapped must be distorted at least slightly in order to get it to fit onto the flat map. .PP This module sets out to solve the corresponding problem of converting latitude and longitude coordinates (designed for a spherical surface) to and from a rectangular grid (for a flat surface). This projection is in itself is a fairly lengthy bit of maths, but what makes it extra complicated is that the earth is not quite a sphere. Because our planet spins about a vertical axis, it tends to bulge out slightly in the middle, so it is more of an oblate spheroid than a sphere. This makes the maths even longer, but the real problem is that the earth is not a regular oblate spheroid either, but an irregular lump that closely resembles an oblate spheroid and which is constantly (if slowly) being rearranged by plate tectonics. So the best we can do is to pick an imaginary regular oblate spheroid that provides a good fit for the region of the earth that we are interested in mapping. The British Ordnance Survey did this back in 1830 and have used it ever since as the base on which the National Grid for Great Britain is constructed. You can also call an oblate spheroid an ellipsoid if you like. The general term for an ellipsoid model of the earth is a \*(L"geoid\*(R". .PP The first standard \s-1OSGB\s0 geoid is known as \*(L"Airy 1830\*(R" after the year of its first development. It was revised in 1936, and that version, generally known as \s-1OSGB36,\s0 is the basis of all current \s-1OSGB\s0 mapping. In 2002 the model was redefined (but not functionally changed) as a transformation from the international geoid model \s-1WGS84. \s0 This redefinition is called \s-1OSGM02.\s0 For the purposes of these modules (and most other purposes) \s-1OSGB36\s0 and \&\s-1OSGM02\s0 may be treated as synonyms. .PP The general idea is that you can establish your latitude and longitude by careful observation of the sun, the moon, the planets, or your \s-1GPS\s0 handset, and that you then do some clever maths to work out the corresponding grid reference using a suitable geoid. These modules let you do the clever maths, and the geoid they use is the \s-1OSGM02\s0 one. This model provides a good match to the local shape of the Earth in the British Isles, but is not designed for use in the rest of the world; there are many other models in use in other countries. .PP In the mid\-1980s a new standard geoid model was defined to use with the fledgling global positioning system (\s-1GPS\s0). This model is known as \s-1WGS84,\s0 and is designed to be a compromise model that works equally well for all parts of the globe (or equally poorly depending on your point of view \-\-\- for one thing \s-1WGS84\s0 defines the Greenwich observatory in London to be not quite on the zero meridian). Nevertheless \s-1WGS84\s0 has grown in importance as \s-1GPS\s0 systems have become consumer items and useful global mapping tools (such as Google Earth) have become freely available through the Internet. Most latitude and longitude coordinates quoted on the Internet (for example in Wikipedia) are \&\s-1WGS84\s0 coordinates. .PP One thing that should be clear from the theory is that there is no such thing as a single definitive set of coordinates for every unique spot on earth. There are only approximations based on one or other of the accepted geoid models, however for most practical purposes good approximations are all you need. In Europe the official definition of \s-1WGS84\s0 is sometime referred to as \s-1ETRS89. \s0 For all practical purposes in Western Europe the \s-1OS\s0 advise that one can regard \s-1ETRS89\s0 as identical to \s-1WGS84 \s0(unless you need to worry about tectonic plate movements). .SS "Practical implications" .IX Subsection "Practical implications" If you are working exclusively with British \s-1OS\s0 maps and you merely want to convert from the grid to the latitude and longitude coordinates printed (as faint blue crosses) on those maps, then all you need from these modules are the plain \f(CW\*(C`grid_to_ll()\*(C'\fR and \f(CW\*(C`ll_to_grid()\*(C'\fR routines. On the other hand if you want to produce latitude and longitude coordinates suitable for Google Earth or Wikipedia from a British grid reference, then you need an extra step. Convert your grid reference using \f(CW\*(C`grid_to_ll()\*(C'\fR and then shift it from the \s-1OSGB\s0 model to the \s-1WGS84\s0 model using \f(CW\*(C`shift_ll_into_WGS84()\*(C'\fR. To go the other way round, shift your \s-1WGS84\s0 lat/lon coordinated into \s-1OSGB,\s0 using \f(CW\*(C`shift_ll_from_WGS84()\*(C'\fR, before you convert them using \&\f(CW\*(C`ll_to_grid()\*(C'\fR. .PP If you have a requirement for really accurate work (say to within a millimetre or two) then you need to use the \s-1OS\s0's transformation matrix called \s-1OSTN02. \s0 This monumental work published in 2002 re-defined the British grid in terms of offsets from \s-1WGS84\s0 to allow really accurate grid references to be determined from really accurate \s-1GPS\s0 readings (the sort you get from professional fixed base stations, not from your car's sat nav or your hand-held device). The problem with it is that it defines the grid in terms of a deviation in three dimensions from a pseudo-grid based on \s-1WGS84\s0 and it does this separately for every square km of the country, so the data set is huge and takes a second or two to load even on a fast machine. Nevertheless a Perl version of \s-1OSTN02\s0 is included as a separate module in this distribution just in case you really need it (but you don't need it for any \*(L"normal\*(R" work). Because of the way \s-1OSTN02\s0 is defined, the sequence of conversion and shifting works differently from the approximate routines described above. .PP Starting with a really accurate lat/lon reading in \s-1WGS84\s0 terms, you need to transform it into a pseudo-grid reference using \f(CW\*(C`ll_to_grid()\*(C'\fR using an optional argument to tell it to use the \s-1WGS84\s0 geoid parameters instead of the default \s-1OSGB\s0 parameters. The Geo::Coordinates::OSTN02 package provides a routine called \f(CW\*(C`ETRS89_to_OSGB36()\*(C'\fR which will shift this pseudo-grid reference into an accurate \s-1OSGB\s0 grid reference. To go back the other way, you use \f(CW\*(C`OSGB36_to_ETRS89()\*(C'\fR to make a pseudo-grid reference, and then call \&\f(CW\*(C`grid_to_ll()\*(C'\fR with the \s-1WGS84\s0 parameter to get \s-1WGS84\s0 lat/long coordinates. .PP .Vb 3 \& ($lat, $lon, $height) = (51.5, \-1, 10); \& ($x, $y) = ll_to_grid($lat, $lon, \*(AqWGS84\*(Aq); \& ($e, $n, $elevation) = ETRS89_to_OSGB36($x, $y, $height); \& \& ($x, $y, $z) = OSGB36_to_ETRS89($e, $n, $elevation); \& ($lat, $lon) = grid_to_ll($x, $y, \*(AqWGS84\*(Aq); .Ve .SH "EXAMPLES" .IX Header "EXAMPLES" .Vb 2 \& # to import everything try... \& use Geo::Coordinates::OSGB \*(Aq:all\*(Aq; \& \& # Get full coordinates in metres from GR \& ($e,$n) = parse_trad_grid(\*(AqTQ 234 098\*(Aq); \& \& # Latitude and longitude according to the OSGB geoid (as \& # printed on OS maps), if you want them to work in Google \& # Earth or some other tool that uses WGS84 then adjust results \& ($lat, $lon) = grid_to_ll($e, $n); \& ($lat, $lon, $alt) = shift_ll_into_WGS84($lat, $lon, $alt); \& # and to go the other way \& ($lat, $lon, $alt) = shift_ll_from_WGS84($lat, $lon, $alt); \& ($e, $n) = ll_to_grid($lat,$lon); \& # In both cases the elevation is in metres (default=0m) \& \& # Reading and writing grid references \& # Format full easting and northing into traditional formats \& $gr1 = format_grid_trad($e, $n); # "TQ 234 098" \& $gr1 =~ s/\es//g; # "TQ234098" \& $gr2 = format_grid_GPS($e, $n); # "TQ 23451 09893" \& $gr3 = format_grid_landranger($e, $n);# "TQ 234 098 on Sheet 176" \& # or call in list context to get the individual parts \& ($sq, $e, $n) = format_grid_trad($e, $n); # (\*(AqTQ\*(Aq, 234, 98) \& \& # parse routines to convert from these formats to full e,n \& ($e,$n) = parse_grid(\*(AqTQ 234 098\*(Aq); \& ($e,$n) = parse_grid(\*(AqTQ234098\*(Aq); # spaces optional \& ($e,$n) = parse_grid(\*(AqTQ\*(Aq,234,98); # or even as a list \& ($e,$n) = parse_grid(\*(AqTQ 23451 09893\*(Aq); # as above.. \& \& # You can also get grid refs from individual maps. \& # Sheet between 1..204; gre & grn must be 3 or 5 digits long \& ($e,$n) = parse_grid(176,123,994); \& \& # With just the sheet number you get GR for SW corner \& ($e,$n) = parse_grid(184); \& \& # Reading and writing lat/lon coordinates \& ($lat, $lon) = parse_ISO_ll("+52\-002/"); \& $iso = format_ll_ISO($lat,$lon); # "+520000\-0020000/" \& $str = format_ll_trad($lat,$lon); # "N52:00:00 W002:00:00" .Ve .SH "BUGS AND LIMITATIONS" .IX Header "BUGS AND LIMITATIONS" The conversions are only approximate. So after .PP .Vb 1 \& ($a1,$b1) = grid_to_ll(ll_to_grid($a,$b)); .Ve .PP neither \f(CW\*(C`$a==$a1\*(C'\fR nor \f(CW\*(C`$b==$b1\*(C'\fR. However \f(CW\*(C`abs($a\-$a1)\*(C'\fR and \f(CW\*(C`abs($b\-$b1)\*(C'\fR should be less than \f(CW0.00001\fR which will give you accuracy to within a metre. In the middle of the grid 0.00001 degrees is approximately 1 metre. Note that the error increases the further away you are from the central meridian of the grid system. .PP The \f(CW\*(C`format_grid_landranger()\*(C'\fR does not take account of inset areas on the sheets. So if you feed it a reference for the Scilly Isles, it will tell you that the reference is not on any Landranger sheet, whereas in fact the Scilly Isles are on an inset in the \s-1SW\s0 corner of Sheet 203. There is nothing in the design that prevents me adding the insets, they just need to be added as extra sheets with names like \*(L"Sheet 2003 Inset 1\*(R" with their own reference points and special sheet sizes. Collecting the data is another matter. .PP Not enough testing has been done. I am always grateful for the feedback I get from users, but especially for problem reports that help me to make this a better module. .SH "DIAGNOSTICS" .IX Header "DIAGNOSTICS" Should this software not do what you expect, then please first read this documentation, secondly verify that you have installed it correctly and that it passes all the installation tests on your set up, thirdly study the source code to see what it's supposed to be doing, fourthly get in touch to ask me about it. .SH "CONFIGURATION AND ENVIRONMENT" .IX Header "CONFIGURATION AND ENVIRONMENT" There is no configuration required either of these modules or your environment. It should work on any recent version of perl, on any platform. .SH "DEPENDENCIES" .IX Header "DEPENDENCIES" None. .SH "INCOMPATIBILITIES" .IX Header "INCOMPATIBILITIES" None known. .SH "LICENSE AND COPYRIGHT" .IX Header "LICENSE AND COPYRIGHT" Copyright (C) 2002\-2013 Toby Thurston .PP \&\s-1OSTN02\s0 transformation data is freely available but remains Crown Copyright (C) 2002 .PP This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. .SH "AUTHOR" .IX Header "AUTHOR" Toby Thurston \*(-- 04 Oct 2013 .PP toby@cpan.org .SH "SEE ALSO" .IX Header "SEE ALSO" The \s-1UK\s0 Ordnance Survey's theory paper referenced above. .PP See Geo::Coordinates::Convert for a general approach (not based on the above paper). .PP See Geo::Coordinates::Lambert for a French approach.