Identifying UK Country from Eastings and Northings
Recently when working with the UK Biobank (UKB) dataset, I had to determine from a location’s Eastings and Northings whether it is located in England, Scotland, or Wales. I have very little background in working with geospatial data, and I figured that some other people working with the dataset might not have either, so here I have documented how I approached the problem in Python. My approach involved transforming the Eastings and Northings to longitudes and latitudes, and intersecting them with the boundaries of England, Scotland, and Wales.
The first thing to note is that there are many different co-ordinate reference systems (CRS). The UKB co-ordinates are given in Eastings and Northings, which are a type of CRS that uses a 2D map as its basis. In contrast, the more commonly encountered longitudes and latitudes define a point on the 3D globe. The map used to define UKB Eastings and Northings is Ordnance Survey Great Britain 1936 (OSGB36).
The code block below creates a DataFrame containing OSGB36 Eastings and Northings for locations within the capitals of England, Scotland, and Wales in a similar format to that provided by UKB.
import pandas as pd
locs = pd.DataFrame({
"northing": [180381, 673842, 176329],
"easting": [530034, 325265, 318371],
"city": ["london", "edinburgh", "cardiff"]
})
I used a Python library called geopandas, which has lots of useful things for working with geospatial data.
import geopandas as gpd
Geopandas has a class called a Point
, which is exactly what it sounds like: a point defined by a pair of xy co-ordinates. We can transform our co-ordinates into this class using the points_from_xy()
function. We must tell geopandas what CRS the co-ordinates belong to. Each different CRS has a unique identifier, and you can find out which is relevant to you by looking it up at https://epsg.io/. Searching for “ordnance survey great britain”, we see that the relevant identifier is EPSG:27700.
coords = gpd.points_from_xy(
locs["easting"], locs["northing"],
crs="EPSG:27700"
)
We can check that this is right CRS identifier by checking the crs
attribute of the Point
array we have created using:
coords.crs
Which gives the output:
<Projected CRS: EPSG:27700>
Name: OSGB36 / British National Grid
Axis Info [cartesian]:
- E[east]: Easting (metre)
- N[north]: Northing (metre)
Area of Use:
- name: United Kingdom (UK) - offshore to boundary of UKCS within 49°45'N to 61°N and 9°W to 2°E; onshore Great Britain (England, Wales and Scotland). Isle of Man onshore.
- bounds: (-9.0, 49.75, 2.01, 61.01)
Coordinate Operation:
- name: British National Grid
- method: Transverse Mercator
Datum: Ordnance Survey of Great Britain 1936
- Ellipsoid: Airy 1830
- Prime Meridian: Greenwich
The locations I was working with were rounded to the nearest kilometre. This rounding could place a co-ordinate off-shore, which would cause problems when I tried to determine whether they were within the borders of a country. To account for this rounding, I transformed my point co-ordinates into circles with a 500 metre diameter using the buffer()
method. The units of the value passed to this method depend on the CRS. As we can see from the output of coords.crs
, this unit is metres for OSGB36.
polys = coords.buffer(500)
In geopandas, the Polygon
class is used to represent areas like the circles created here. My goal was to identify whether these areas are located in England, Scotland, or Wales. The boundaries of these countries can be obtained from the Office of National Statistics’ Open Geography Portal. Navigate to Boundaries > OECD/Eurostat Boundaries > NUTS1 > 2018 Boundaries. NUTS stands for Nomenclature of Territorial Units for Statistics, which describes territory boundaries at various levels. NUTS1 is the highest level, in which Scotland, Northern Ireland, and Wales are represented by a single region, and there are a number of regions that make up England. From this site you can download these boundaries in geojson format and read them into Python as GeoDataFrames using geopandas.
uk = gpd.read_file("nuts1_boundaries.geojson")
Below is the output of uk.head(n=2)
:
objectid nuts118cd nuts118nm bng_e bng_n long lat st_areashape st_lengthshape geometry
1 UKC North East (England) 417313 600358 -1.72890 55.297031 8.675727e+09 7.954563e+05 MULTIPOLYGON (((-2.03012 55.80992, -2.03019 55...
2 UKD North West (England) 350015 506280 -2.77237 54.449451 1.491948e+10 1.089508e+06 POLYGON ((-2.67559 55.17332, -2.67504 55.17351...
You can produce the following plot with uk.plot()
:
The UK boundaries are given in longitudes and latitudes (specifically the WGS84 system, which is the co-ordinate system used by GPS). You can see this by executing uk.crs
, which gives the following output:
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich
To intersect these regions with the capital city locations, they must be using the same co-ordinate system. I transformed the capital city locations from OSGB36 to WGS84.
locs["geometry"] = polys.to_crs("EPSG:4326")
locs = gpd.GeoDataFrame(locs)
Geopandas provides sjoin()
for performing intersections between two GeoDataFrames. In the example below, for each set of city co-ordinates, this function determines which of the NUTS1 UK regions they are located in.
intersection = gpd.sjoin(
locs,
uk,
how = "left",
predicate = "intersects"
)
intersection[["nuts118nm", "city"]].head()
should give you:
nuts118nm city
London london
Scotland edinburgh
Wales cardiff
From the output above, you can see that the London location is located in the NUTS1 region London, Edinburgh in Scotland, and Cardiff in Wales, as we’d expect. And just like that, you can figure out the UK country that any set of co-ordinate locations are in.
References
Ordnance Survey (2015) A guide to co-ordinate systems in Great Britain. https://www.bnhs.co.uk/2019/technology/grabagridref/OSGB.pdf, accessed 19/03/2022.