GB postcode polygons – open data


Postcode district and area polygons available.

These days I’m working with a lot of data about people. Often surveys, but also administrative data. I usually want to represent these on a map. To preserve anonymity I aggregate dataset before plotting them. This might be at a local authority level, but more often it is via postcode.

In the UK a postcode is used to define a small group of addresses and is of the form EH1 2NG (Edinburgh Castle), where the first half is the postcode district and the first letters (either 1 or 2) from the postcode district is the postcode area.

The Ordnance Survey release postcode point data as part of their Open Data offering, but to access postcode polygons one either needs to be an academic with an Edina account or pay a license fee for the Ordnance Survey product. I don’t often need access to individual postcode polygons, often aggregate ones are best. I sometimes download these from web sources, if I can find them, but the creation method isn’t that transparent – nor their licensing.

This post is announcing a new, derived dataset I’ve produced: postcode area and district polygons for GB. These have been created by making voronoi polygons of all GB postcodes and then dissolving boundaries based on postcode area and district. I’ve published this dataset on Figshare:

Download postcode polygons

The Figshare site also includes code to produce the polygons, but I’ll repeat it here too. The first code chunk uses R to prepare the postcode point data. The second code chunk uses GRASS to create the spatial dataset.


df = read_csv("~/GIS/OS/postcode.csv")

df = df %>%
   mutate(pc_district = str_sub(Postcode, 1, nchar(Postcode)-3)) %>%
   mutate(pc_district = trimws(pc_district)) %>%
   mutate(pc_area = gsub('[0-9]+', '', pc_district)) %>%
   mutate(pc_area = str_sub(pc_area, 1, 2))

write_csv(df, "~/GIS/OS/postcode_extra.csv")

And the GRASS commands used:

# GRASS commands for making postcode areas

v.voronoi input=postcodes@postcode output=pc_voronoi@postcode

v.dissolve input=pc_voronoi@postcode column=str_9 output=pc_area
v.dissolve input=pc_voronoi@postcode column=str_8 output=pc_district

v.overlay ainput=pc_area@postcode binput=GB_coast@postcode operator=and output=pc_area_clip
v.overlay ainput=pc_district@postcode binput=GB_coast@postcode operator=and output=pc_district_clip

v.db.renamecolumn map=pc_district_clip@postcode column=a_str_8,pc_district
v.db.renamecolumn map=pc_area_clip@postcode column=a_str_9,pc_area

db.dropcolumn -f table=pc_area_clip column=a_cat
db.dropcolumn -f table=pc_area_clip column=b_cat
db.dropcolumn -f table=pc_area_clip column=b_ID
db.dropcolumn -f table=pc_area_clip column=b_AREA
db.dropcolumn -f table=pc_area_clip column=b_PERIMETER
db.dropcolumn -f table=pc_area_clip column=b_ACRES
db.dropcolumn -f table=pc_district_clip column=a_cat
db.dropcolumn -f table=pc_district_clip column=b_cat
db.dropcolumn -f table=pc_district_clip column=b_ID
db.dropcolumn -f table=pc_district_clip column=b_AREA
db.dropcolumn -f table=pc_district_clip column=b_PERIMETER
db.dropcolumn -f table=pc_district_clip column=b_ACRES<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;">&#65279;</span>