OSGB function reference

The OSGB package provides three modules: convert, gridder, and legacy_interface, which are documented below.

Each section shows how to call each function, and explains the arguments and outputs. The documentation shows the long form of each call, with the module names as prefixes to each function, like this:

import osgb
(e, n) = osgb.gridder.parse_grid("NH231424")
(lat, lon) = osgb.convert.grid_to_ll(122414, 123412)

But if you can’t remember which function is in which module, or you just get tired of typing the prefix names, you can optionally omit the module names, thanks to a bit of syntactic-sugar magic, so the above snippet should work the same if you put:

import osgb
(e, n) = osgb.parse_grid("NH231424")
(lat, lon) = osgb.grid_to_ll(122414, 123412)

which is a bit neater. This applies to all the documented functions in this section.

OSGB Convert

Conversion between latitude/longitude coordinates and OSGB grid references.

This module provides the core routines that implement the OS conversion formulae.

osgb.convert.grid_to_ll(easting, northing=None, model='WGS84', rounding=None)

Convert OSGB (easting, northing) to latitude and longitude.

Input

an (easting, northing) pair in metres from the false point of origin of the grid.

Note that if you are starting with a tradtional grid reference string like TQ183506, you need to parse it into an (easting, northing) pair using the parse_grid() function from osgb.gridder before you can pass it to this function:

lat, lon = osgb.grid_to_ll(osgb.parse_grid("TQ183506"))
Output

a (latitude, longitude) pair in decimal degrees; postive North/East, negative South/West (that is, following ISO 6709 conventions).

An optional argument model defines the graticule model to use. The default is WGS84, the standard model used for the GPS network and for references given on Google Earth or Wikipedia, etc. The only other valid value is OSGB36 which is the traditional model used in the UK before GPS. Latitude and longitude coordinates marked around the edges of OS maps published before 2015 are given in the OSGB36 model.

An optional argument rounding controls how many decimal places are provided in the output. Grid references rounded to whole metres will give lat/lon that are accurate to about 5 decimal places. But Grid references in millimetres will give results accurate to 8 decimal places. The default value of rounding is None, and the default behaviour is to round appropriately according to the number of decimal places given in the input.

Effectively if your input is in whole metres you will get 6 places, but if your input has any decimal places of metres you will get 9 places. You can supply your own value of rounding if required. The allowed values are the same as the second argument to the Python built-in round function.

Note that more decimal places do not make the conversions more accurate! And that in the UK, 0.000001 of a degree of latitude is about 7cm, 0.000001 of a degree of longitude is about 10cm.

>>> # Glendessary, the graticule marker on Sheet 33
>>> grid_to_ll(197574, 794793, model='OSGB36')
(57.000002, -5.33332)
>>> # Scorriton
>>> grid_to_ll(269995, 68361, model='OSGB36', rounding=5)
(50.5, -3.83333)
>>> # Cranbourne Chase, on the central meridian
>>> grid_to_ll(400000, 122350.0439, model='OSGB36')
(51.0, -2.0)
>>> # The example from the OSGB documentation
>>> grid_to_ll(651409.903, 313177.27, model='OSGB36', rounding=8)
(52.6575703, 1.71792158)

The routines will produce lots more decimal places, and you can choose what rounding you want, although they aren’t really meaningful beyond nine places, since the conversion routines supplied by the OS are only designed to be accurate to about 1mm (8 places).

>>> # Hoy (Orkney)
>>> grid_to_ll(323223, 1004000, model='OSGB36', rounding=10)
(58.9168015046, -3.3333320036)
>>> # Glen Achcall
>>> grid_to_ll(217380, 896060, model='OSGB36', rounding=10)
(57.9167163329, -5.083330214)

You can provide a tuple instead of two separate args:

>>> grid_to_ll((217380, 896060))
(57.916378, -5.084588)

But you will get an error if the single argument is anything but a tuple

>>> grid_to_ll("NH 345 213") 
Traceback (most recent call last):
    ...
MissingArgumentError: This should have been a tuple: NH 345 213

You need to do parse_grid first to avoid this of course.

Finally here is an example of how to use the optional keyword arguments:

>>> grid_to_ll(easting=217380, northing=896060, model='OSGB36')
(57.916716, -5.08333)

And a check that conversion works outside the defined OSTN15 polygon:

>>> grid_to_ll(easting=-100, northing=-100, rounding=8)
(49.76584553, -7.55843918)
>>> grid_to_ll(easting=1, northing=1, rounding=8)
(49.76681683, -7.55714701)

But beware that this only works for the area immediately around the British Isles.

osgb.convert.ll_to_grid(lat, lon, model='WGS84', rounding=None)

Convert a (latitude, longitude) pair to an OSGB grid (easting, northing) pair.

Output

a tuple containing (easting, northing) in metres from the grid origin.

Input

The arguments should be supplied as real numbers representing decimal degrees, like this:

>>> ll_to_grid(51.5, -2.1)
(393154.813, 177900.607)

Following the normal convention, positive arguments 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:

>>> ll_to_grid(51+25/60, 0-5/60-2/3600)
(533338.156, 170369.238)
>>> ll_to_grid(52 + 39/60 + 27.2531/3600, 1 + 43/60 + 4.5177/3600, model='OSGB36')
(651409.903, 313177.27)

But if you are still using python2 then be sure to import division so that you get the correct semantics for division when both numerator and denominator are integers.

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. However since reasonable latitudes for the OSGB are in the range 49 to 61, and reasonable longitudes in the range -9 to +2, the ll_to_grid function accepts argument in either order. If your longitude is larger than your latitude, then the values of the arguments will be silently swapped:

>>> ll_to_grid(-2.1, 51.5)
(393154.813, 177900.607)

But you can always give the arguments as named keywords if you prefer:

>>> ll_to_grid(lon=-2.1, lat=51.5)
(393154.813, 177900.607)

The easting and northing will be returned as the distance in metres from the ‘false point of origin’ of the British Grid (which is a point some way to the south-west of the Scilly Isles). If you want the result presented in a more traditional grid reference format you should pass the results to format_grid() from osgb.gridder.

If the coordinates you supply are in the area covered by the OSTN transformation data, then the results will be rounded to 3 decimal places, which corresponds to the nearest millimetre. If they are outside the coverage then the conversion is automagically done using a Helmert transformation instead of the OSTN data. The results will be rounded to the nearest metre in this case, although you probably should not rely on the results being more accurate than about 5m.

>>> # Somewhere in London
>>> ll_to_grid(51.3, 0)
(539524.836, 157551.913)
>>> # Far north
>>> ll_to_grid(61.3, 0)
(507242.0, 1270342.0)

The coverage extends quite a long way off shore.

>>> # A point in the sea, to the north-west of Coll
>>> ll_to_grid(56.75, -7)
(94469.613, 773209.471)

The numbers returned may be negative if your latitude and longitude are far enough south and west, but beware that the transformation is less and less accurate or useful the further you get from the British Isles.

>>> ll_to_grid(51.3, -10)
(-157250.0, 186110.0)

ll_to_grid also takes an optional argument that sets the ellipsoid model to use. This defaults to WGS84, the name of the normal model for working with normal GPS coordinates, but if you want to work with the traditional latitude and longitude values printed on OS maps before 2015 then you should add an optional model argument

>>> ll_to_grid(49, -2, model='OSGB36')
(400000.0, -100000.0)

Incidentally, the grid coordinates returned by this call are the coordinates of the ‘true point of origin’ of the British grid. You should get back an easting of 400000.0 for any point with longitude 2W since this is the central meridian used for the OSGB projection. However you will get a slightly different value unless you specify model='OSGB36' since the WGS84 meridians are not quite the same as OSGB36.

>>> ll_to_grid(52, -2, model='OSGB36')
(400000.0, 233553.731)
>>> ll_to_grid(52, -2, model='WGS84')
(400096.274, 233505.403)

If the model is not OSGB36 or WGS84 you will get an UndefinedModelError exception:

>>> ll_to_grid(52, -2, model='EDM50') 
Traceback (most recent call last):
    ...
UndefinedModelError: EDM50

You can also control the rounding directly if you need to, but be aware that asking for more decimal places does not make the conversion any more accurate; the formulae used are only designed to be accurate to 1mm.

>>> ll_to_grid(52, -2, rounding=4)
(400096.2738, 233505.4033)

The allowed values are the same as the second argument to the Python built-in round function. So you can round to lower precision if you want.

>>> ll_to_grid(52, -2, rounding=-2)
(400100.0, 233500.0)

Notice that the Python BIF round is used to do the rounding here, so the grid coordinates will be given to the nearest metre or mm or whatever size you choose. This follows the sample data given by the OS, but it is slightly at odds with the OSGB National Grid convention that a given point is represented by the coordinates of the SW corner of the square that encloses it. So your best strategy is to use the default rounding here, and rely on osgb.gridder.format_grid to do the correct rounding down when it formats the coordinates into a grid reference string.

OSGB Gridder

Parse and format OSGB grid reference strings.

This module provides functions to parse and format grid references, and to tell you which maps include a given reference.

osgb.gridder.format_grid(easting, northing=None, form='SS EEE NNN')

Formats an (easting, northing) pair into traditional grid reference.

This routine formats an (easting, northing) pair into a traditional grid reference with two letters and two sets of three numbers, like this: SU 387 147.

>>> print(format_grid(438710.908, 114792.248))
SU 387 147

If you want the individual components, apply split() to it.

>>> print('-'.join(format_grid(438710.908, 114792.248).split()))
SU-387-147

and note that the results are strings not integers. Note also that rather than being rounded, the easting and northing are truncated (as the OS system demands), so the grid reference refers to the lower left corner of the relevant square. The system is described below the legend on all OS Landranger maps.

The format_grid routine takes an optional keyword argument form, that controls the form of grid reference returned.

>>> print(format_grid(438710.908, 114792.248, form='SS EEE NNN'))
SU 387 147
>>> print(format_grid(438710.908, 114792.248, form='SS EEEEE NNNNN'))
SU 38710 14792
>>> print(format_grid(438710.908, 114792.248, form='SS'))
SU
>>> print(format_grid(438710.908, 114792.248, form='SSEN'))
SU31
>>> print(format_grid(438710.908, 114792.248, form='SSEENN'))
SU3814
>>> print(format_grid(438710.908, 114792.248, form='SSEEENNN'))
SU387147
>>> print(format_grid(438710.908, 114792.248, form='SSEEEENNNN'))
SU38711479
>>> print(format_grid(438710.908, 114792.248, form='SSEEEEENNNNN'))
SU3871014792
>>> print(format_grid(438710.908, 114792.248, form='SS EN'))
SU 31
>>> print(format_grid(438710.908, 114792.248, form='SS EE NN'))
SU 38 14
>>> print(format_grid(438710.908, 114792.248, form='SS EEE NNN'))
SU 387 147
>>> print(format_grid(438710.908, 114792.248, form='SS EEEE NNNN'))
SU 3871 1479
>>> print(format_grid(400010.908, 114792.248, form='SS EEEEE NNNNN'))
SU 00010 14792

You can’t leave out the SS, you can’t have N before E, and there must be the same number of Es and Ns. Except for the two special formats:

>>> print(format_grid(438710.908, 114792.248, form='TRAD'))
SU 387 147
>>> print(format_grid(438710.908, 114792.248, form='GPS'))
SU 38710 14792

The format can be given as upper case or lower case or a mixture.

>>> print(format_grid(438710.908, 114792.248, form='trad'))
SU 387 147

but in general the form argument must match "SS E* N*" (spaces optional)

>>> format_grid(432800, 250000, form='TT') 
Traceback (most recent call last):
...
FaultyFormError: This form argument was not matched --> form='TT'

Here are some more extreme examples:

>>> print(format_grid(314159, 271828, form='SS'))
SO
>>> print(format_grid(0, 0, form='SS'))
SV
>>> print(format_grid(432800, 1250000, form='SS'))
HP

The arguments can be negative…

>>> print(format_grid(-5, -5, form='SS'))
WE

…but must not be too far away from the grid:

>>> format_grid(-1e12, -5) 
Traceback (most recent call last):
...
FarFarAwayError: The spot with coordinates (-1e+12, -5) is too far from the OSGB grid

You can also supply the easting and northing arguments as a single tuple, which is more convenient if you happen to have the grid reference stored in a variable.

>>> gr = (460003, 180542)
>>> print(format_grid(gr))
SU 600 805
>>> print(format_grid(gr, form='GPS'))
SU 60003 80542
osgb.gridder.get_sheet(key)

Fetch a map from the locker:

>>> print(get_sheet("A:1").title)
Shetland - Yell, Unst and Fetlar
osgb.gridder.parse_grid(*grid_elements, **kwargs)

Parse a grid reference from a range of inputs.

The parse_grid routine extracts a (easting, northing) pair from a string, or a list of arguments, representing a grid reference. The pair returned are in units of metres from the false origin of the grid.

The arguments should be in one of the following three forms

  • A single string representing a grid reference

    >>> parse_grid("TA 123 678")
    (512300, 467800)
    >>> parse_grid("TA 12345 67890")
    (512345, 467890)
    

    The string can also refer to 100km, 10km, 1km, or even 10m squares:

    >>> parse_grid('TA')
    (500000, 400000)
    >>> parse_grid('TA15')
    (510000, 450000)
    >>> parse_grid('TA 12 56')
    (512000, 456000)
    >>> parse_grid('TA 1234 5678')
    (512340, 456780)
    

    The spaces are optional in all cases:

    >>> parse_grid(" TA 123 678 ")
    (512300, 467800)
    >>> parse_grid(" TA123 678 ")
    (512300, 467800)
    >>> parse_grid(" TA 123678")
    (512300, 467800)
    >>> parse_grid("TA123678")
    (512300, 467800)
    >>> parse_grid("TA1234567890")
    (512345, 467890)
    

    Here are some more extreme examples:

    >>> parse_grid('SV9055710820') # St Marys lifeboat station
    (90557, 10820)
    >>> parse_grid('HU4795841283') # Lerwick lifeboat station
    (447958, 1141283)
    >>> parse_grid('WE950950') # At sea, off the Scillies
    (-5000, -5000)
    

    Note in the last one that we are “off” the grid proper. This lets you work with “pseudo-grid-references” like these:

    >>> parse_grid('XD 61191 50692') # St Peter Port the Channel Islands
    (361191, -49308)
    >>> parse_grid('MC 03581 16564') # Rockall
    (-296419, 916564)
    
  • A two or three element list representing a grid reference

    >>> parse_grid('TA', 0, 0)
    (500000, 400000)
    >>> parse_grid('TA', 123, 678)
    (512300, 467800)
    >>> parse_grid('TA', 12345, 67890)
    (512345, 467890)
    >>> parse_grid('TA', '123 678')
    (512300, 467800)
    >>> parse_grid('TA', '12345 67890')
    (512345, 467890)
    >>> parse_grid('TA', '1234567890')
    (512345, 467890)
    

    Or even just two numbers (primarily included for testing purposes). Note that this allows floats, and that the results will come back as floats

    >>> parse_grid(314159, 271828)
    (314159.0, 271828.0)
    >>> parse_grid('314159 271828')
    (314159.0, 271828.0)
    >>> parse_grid(231413.123, 802143.456)
    (231413.123, 802143.456)
    

    If you are processing grid references from some external data source beware that if you use a list with bare numbers you may lose any leading zeros for grid references close to the SW corner of a grid square. This can lead to some ambiguity. Either make the numbers into strings to preserve the leading digits or supply a keyword argument figs to define how many figures are supposed to be in each easting and northing. Like this:

    >>> parse_grid('TA', 123, 8)
    (512300, 400800)
    >>> parse_grid('TA', 123, 81, figs=5)
    (500123, 400081)
    

    The default setting of figs is 3, which assumes you are using hectometres as in a traditional grid reference. The maximum is 5 and the minimum is the length of the longer of easting or northing.

  • A string or a list representing a map and a local grid reference, corresponding to the following examples:

    >>> parse_grid('176/224711') # Caesar's Camp
    (522400, 171100)
    >>> parse_grid(176, 224, 711)
    (522400, 171100)
    >>> parse_grid('A:164/352194') # Charlbury Station
    (435200, 219400)
    >>> parse_grid('B:OL43E/914701') # map Chesters Bridge
    (391400, 570100)
    >>> parse_grid('B:OL43E 914 701') # map Chesters Bridge
    (391400, 570100)
    >>> parse_grid('B:OL43E', '914701') # map 2-arg Chesters Bridge
    (391400, 570100)
    >>> parse_grid('B:OL43E', 914, 701) # map 3-arg Chesters Bridge
    (391400, 570100)
    >>> parse_grid(164, 513, 62) # Carfax
    (451300, 206200)
    >>> parse_grid('B:119/480103') # map with dual name
    (448000, 110300)
    >>> parse_grid('B:OL3/480103') # map with dual name
    (448000, 110300)
    >>> parse_grid('B:309S.a 26432 34013') # inset on B:309
    (226432, 534013)
    >>> parse_grid('B:368W', 723, 112) # 3-arg, dual name
    (272300, 711200)
    >>> parse_grid('B:OL47W', 723, 112) # 3-arg, dual name
    (272300, 711200)
    

    or finally just a sheet name; this will show the SW corner:

    >>> parse_grid('A:82')
    (195000, 530000)
    

    with the usual rule about assuming you meant Landrangers:

    >>> parse_grid(161)
    (309000, 205000)
    

    A map sheet with a grid ref that does not actually coincide will raise a SheetMismatchError error

    >>> parse_grid('176/924011') 
    Traceback (most recent call last):
    ...
    SheetMismatchError: Grid point (592400, 201100) is not on sheet A:176
    

    A map sheet that does not exist will raise an UndefinedSheetError error

    >>> parse_grid('B:999/924011') 
    Traceback (most recent call last):
    ...
    UndefinedSheetError: Sheet B:999 is not known here.
    

If there’s no matching input then a GarbageError error is raised.

>>> parse_grid('Somewhere in London') 
Traceback (most recent call last):
...
GarbageError: I can't read a grid reference from this -> Somewhere in London
osgb.gridder.sheet_keys(easting, northing=None, series='ABCHJ')

Return a list of map sheet keys that show the (easting, northing) point given.

The optional argument “series” controls which maps are included in the list. The default is to include maps from all defined series.

>>> print(' '.join(sheet_keys(438710.908, 114792.248, series='AB')))
A:196 B:OL22E

Currently the series included are:

A:

OS Landranger 1:50000 maps

B:

OS Explorer 1:25000 maps (some of these are designated as “Outdoor Leisure” maps)

C:

OS Seventh Series One-Inch 1:63360 maps

H:

Harvey British Mountain maps - mainly at 1:40000

J:

Harvey Super Walker maps - mainly at 1:25000

Note that the numbers returned for the Harvey maps have been invented for the purposes of this module. They do not appear on the maps themselves; instead the maps have titles.

You can use the numbers returned as an index to the maps data to find the appropriate title. To get a sheet object use: get_sheet(key)

>>> print(get_sheet(sheet_keys(314159, 271828, series='C')[0]).title)
Montgomery and Llandrindod Wells
>>> print(' '.join(sheet_keys(314159, 271828)))
A:136 A:148 B:200E B:214E C:128

You can restrict the list to certain series. So if you only want Explorer maps use: series=’B’, and if you want only Explorers and Landrangers use: series=’AB’, and so on.

>>> print(''.join(sheet_keys(651537, 313135, series='A')))
A:134

If the (easting, northing) pair is not covered by any map sheet you’ll get an empty list

>>> sheet_keys(0, 0)
[]

You can pass a tuple as argument if you prefer, so that you can call sheet_keys directly with parse_grid

>>> print(' '.join(sheet_keys(parse_grid("SZ294849"))))
A:195 A:196 B:OL29W C:180
>>> print(' '.join(sheet_keys(parse_grid("SZ294849"), series='A')))
A:195 A:196

But we don’t call parse_grid automatically for you…

>>> sheet_keys("SZ294849")
[]

Gridder also provides two dictionaries with data about British maps. If you have import osgb at the top of your Python script you can refer to the dictionaries as:

osgb.name_for_map_series
osgb.map_locker

The first has just five entries, as follows:

{
    'A': 'OS Landranger',
    'B': 'OS Explorer',
    'C': 'OS One-Inch 7th series',
    'H': 'Harvey British Mountain Maps',
    'J': 'Harvey Superwalker',
}

The map_locker is rather larger; it has an entry for each sheet (and sub-sheet) in the five series. The keys are the map labels consisting of the series letter + : + the sheet number. The values are named tuples with a typename of “Sheet” with data for the map. Here is an example:

{
    "A:4" : Sheet(
        bbox = [[420000, 1107000], [460000, 1147000]],
        area = 1600,
        series = 'A',
        number = '4',
        parent = '',
        title = 'Shetland – South Mainland',
        polygon = [[420000,1107000],[460000,1107000],[460000,1147000],[420000,1147000],[420000,1107000]]
    ),
}

These are the field names:

  • bbox is a list of two (easting, northing) pairs that give the LL and UR corners of the bounding box of the map.

  • area is a string giving the area of the sheet in square km.

  • series is one of the keys from osgb.name_for_map_series given above.

  • number is a string giving the sheet number/label

  • parent is a the key of the parent sheet. This is only relevant for insets and subsheets. For regular sheets, the parent is set to the empty string.

  • title a version of the title printed on the front of the map

  • polygon a list of (easting, northing) pairs that define the boundary of the map. Note that the last pair should always equal the first pair.

Example:

for m in osgb.map_locker.values():
    print(m.number, m.title)

Legacy interface

osgb.legacy_interface.lonlat_to_osgb(lon, lat, digits=3, formatted=True, model='OSGB36')

Convert a longitude and latitude to Ordnance Survey grid reference.

Parameters:
lon

Longitude, assumed to be in OSGB36 degrees (unless you set model=’WGS84’).

lat

Latitude, ditto.

digits

The number of digits to use for each direction in the final grid reference. Default 3.

formatted

Should the OSGB reference be nicely formatted (with whitespace)? Default true.

model

‘OSGB36’ or ‘WGS84’, default ‘OSGB36’

Returns:

A string giving a formatted OSGB reference.

For example:

>>> print(lonlat_to_osgb(1.088978, 52.129892))
TM 114 525
>>> print(lonlat_to_osgb(1.088978, 52.129892, formatted=False))
TM114525
>>> print(lonlat_to_osgb(1.088978, 52.129892, 5))
TM 11400 52500

In the re-implemented version you can reverse arguments if you want to…

>>> print(lonlat_to_osgb(52.129892, 1.088978, 5))
TM 11400 52500
osgb.legacy_interface.osgb_to_lonlat(osgb_str, model='OSGB36')

Convert an Ordinance Survey reference to a longitude and latitude.

Parameters:
osgb_str

An Ordnance Survey grid reference in “letter-number” format. Case and spaces are cleaned up by this function, and resolution automatically detected, so that so that TM114 525, TM114525, and TM 11400 52500 are all recognised and identical.

model

‘OSGB36’ or ‘WGS84’. Default ‘OSGB36’.

Returns:

The longitude and latitude of the grid reference, according to the chosen model.

For example:

# just outside Ipswich, about 1.088975 52.129892
>>> osgb_to_lonlat('TM114 525')
(1.088975, 52.129892)

# accepts poor formating
>>> osgb_to_lonlat(' TM 114525 ')
(1.088975, 52.129892)

# accepts higher resolution
>>> osgb_to_lonlat('TM1140052500')
(1.088975, 52.129892)