Converting Ordnance Survey coordinates
In which OS positions are transformed into something saner and less UK-centric.
The Ordnance Survey grid for the UK and hwo is is calculated is discussed elsewhere (and elsewhere on this site). This page will discuss how to convert from OSGB coordinates to the standard, and explain the attached script.
A few warnings and caveats:
- Traditional cartographers tend to talk about lat-lon, i.e. north and east. Computer cartographers - and this document - tend to use lon-lat, i.e. east and north, which fits naturally with the Cartesian grid x-y (and later z for altitude).
- The lon-lats are returned are OSGB36 as that is the system OSGB is based up, not the more modern WGS84. The difference should be minimal (i.e. less than 100 metres). For example, the OS grid reference 'TM114 525' (just outside Ipswich) should convert to the lon-lat 1.088975 52.129892 in OSGB36, which is 1.087203 52.130350 in WGS84.
- All degrees are given in radians
The attached file contains all the code for this project.
Implementation
Assumimg a coordinate passed as a string something like this:
'TM114 525' 'TM 114525' 'TM1140052500'
OS references come in a variety of formats and resolutions, so the first step is to clean them up and reduce them to a canonical form:
def osgb_to_lonlat (osgb_str):
osgb_str = osgb_str.replace (' ', '').upper()
osgb_zone = osgb_str[:2]
osgb_coords = osgb_str[2:]
Thus we now have the two-letter code for the 500 and 100 kilometre squares and the easting-northing within that square. To find the 500 km square, as only 6 of them are actually used, it's easiest to just take the first letter and do a lookup:
def oszone_to_eastnorth (ossquare):
mainsq = ossquare[0]
if (mainsq is 'S'):
x, y = 0, 0
elif (mainsq is 'T'):
# ... etc.
easting = x * 500
northing = y * 500
To find the minor (100km) squares, take the second letter and lookup on a oddly ordered alphabet:
grid = "VWXYZQRSTULMNOPFGHJKABCDE" posn = grid.find (minorsq) easting += (posn % 5) * 100 northing += (posn / 5) * 100
By modding and diving the position in this alphabet, we can find how far a minor square is from the southwest corner of the major square, and thus the east-north of the zone from the OS anchor point in kilometres. (There's no point in using meters at this stage.)
The numerical east-north part can be simply split into even parts:
def zonecoord_to_eastnorth (coord_str): len_str = len (coord_str) # calc size & resolution of numeric portion, split rez = len_str / 2 osgb_easting = coord_str[:rez] osgb_northing = coord_str[rez:]
The number of digits in each gives us the resolution - with 3 digits the lowest is 100 metres, with 4 it is 10 metres etc. - so we can calculate what that lowest digit is in metres and multiple the components by that to get the east-north from the SW corner of the zone:
# what is each digit (in metres) rez_unit = 10.0**(5-rez) return int (osgb_easting) * rez_unit, int (osgb_northing) * rez_unit
Adding this to the zone offset, we have the total easting-northing to the OSGB reference point.
Now it gets ugly.
Taking the Ordnance Survey constants and formulae, we construct a formulae to convert the easting-northing to lon-lats. This is a gruesome and long bit of math, with a iterative approximation. There's little room for any cleverness here, as it has to be straight conversion of the formulae. Thankfully, it's fast and gives lon-lats within metres of the original position. Note the answers are in radians.
References
- Chris Veness' Javascript implementation was a useful source for this code.
- I discuss the OSGB system elsewhere on this site.

