Turquoise Hexagon Sun - Boards of Canada
As the election(s) loom, I thought I would try my hand at making some hexagon maps which have been all the rage for recent votes.
First, download the constituency borders from Ordnance Survey (these are missing the Northern Ireland seats, but it’s not a big deal for now)
library(rgdal)
library(dplyr)
url <- "https://github.com/RobWHickman/Databases-and-Files/raw/master/GE2017/west.zip"
download.file(url, temp <- tempfile())
unzip(temp, exdir=tempdir())
WestminsterMap <- readOGR(tempdir(), "west", encoding='UTF-8')
## OGR data source with driver: ESRI Shapefile
## Source: "/tmp/RtmptCiqIr", layer: "west"
## with 632 features
## It has 1 fields
Westminster_data <- read.csv("https://raw.githubusercontent.com/RobWHickman/Databases-and-Files/master/GE2017/names.csv", stringsAsFactors = FALSE) %>%
filter(!country_name %in% "Northern Ireland") %>%
arrange(ordnance_order)
WestminsterMap@data <- Westminster_data
head(WestminsterMap@data)
## order fullname shortname shorter
## 1 1 Aberavon Co Const Aberavon Aba
## 2 2 Aberconwy Co Const Aberconwy Abc
## 3 3 Aberdeen North Burgh Const Aberdeen North Abn
## 4 5 Airdrie and Shotts Co Const Airdrie and Shotts Ais
## 5 6 Aldershot Boro Const Aldershot Ald
## 6 7 Aldridge-Brownhills Boro Const Aldridge-Brownhills Alb
## region_name country_name ordnance_order
## 1 Wales Wales 0
## 2 Wales Wales 1
## 3 Scotland Scotland 2
## 4 Scotland Scotland 3
## 5 South East England 4
## 6 West Midlands England 5
plot(WestminsterMap)
Now we have a base map, we want to resize all constituencies to be equal. This map has been compressed by me so has no actual data attached to it.
The first thing to do is remove the islands. These will mess with the grid we overlay. You might need to play with the min_area, but basically anything where you can just move a single polygon across later: get rid of it.
library(rmapshaper)
Mainland <- ms_filter_islands(WestminsterMap, min_area = 100000000000)
plot(Mainland)
Time to start properly reshaping the map. First we’ll simplify the cartogram using ms_simplify from the rmapshaper package. This is just to smooth out the borders which will be useful for plotting a grid later. Try and get rid of as much that sticks out whilst keeping as many polygons as possible (losing some makes the cartogram less accurate but ~10% is definitely fine).
Main_simple <- ms_simplify(Mainland, 0.0018)
plot(Main_simple)
Main_simple@data$weight <- 100000
Then produce a cartogram where every constituency is given even weight. The cartogram function takes some playing around with and will take a while to run, just let it do it’s thing. Ideally we’re looking for a mean error of less than 2. I’ve used 6 iterations here as after that it starts producing more errors.
EDIT this didn’t work so well when I tried it, though it is definitely possible. The Gastner-Newmann algorithm for cartograms seems to keep more uniform shapes and so will work much better when inheriting data from layers later on. I don’t know of an R package that can do this as of April 2017, but Scapetoad will work fine and is easy to use on any OS.
Use writeOGR() to export the Main_simple shapefile to do this.
#library(cartogram)
#Main_simplecart <- cartogram(Main_simple, "weight", itermax = 6)
#plot(Main_simplecart)
url2 <- "https://github.com/RobWHickman/Databases-and-Files/raw/master/GE2017/scapetoad%20westminster.zip"
download.file(url2, temp <- tempfile())
unzip(temp, exdir=tempdir())
Main_cart <- readOGR(tempdir(), "scapetoad westminster", encoding='UTF-8')
## OGR data source with driver: ESRI Shapefile
## Source: "/tmp/RtmptCiqIr", layer: "scapetoad westminster"
## with 628 features
## It has 11 fields
## Integer64 fields read as strings: order ordnnc_ rmpshpr
Main_simplecart <- ms_simplify(Main_cart, 0.001)
plot(Main_simplecart)
With this weighted distortion of the UK, we can start guessing how many hexagons should be in each location. I stole code from the excellent http://strimas.com/spatial/hexagonal-grids/ to do this.
We know a priori how many hexagons should be in our finished map - 632 (there are 650 constituencies but the map from Ordnance leaves off Northern Ireland which is politically a bit weird). This includes the islands we removed earlier but we can simply drag hexagons around later to give the impression of those.
The function should converge on the number of hexagons equal to the number we want. You’ll want to start with a fairly close approximation (mostly to save computational resources).
hex_number <- 632
size = 20000
guessed_hex_centre <- spsample(Main_simplecart, type = "hexagonal", cellsize = size)
guessed_hex <- HexPoints2SpatialPolygons(guessed_hex_centre, dx = size)
while(hex_number != length(guessed_hex)){
ifelse(hex_number > length(guessed_hex), size <- size * 0.99, size <- size * 1.01)
guessed_hex_centre <- spsample(Main_simplecart, type = "hexagonal", cellsize = size)
guessed_hex <- HexPoints2SpatialPolygons(guessed_hex_centre, dx = size)
print(length(guessed_hex))
}
whole_shape <- ms_dissolve(guessed_hex)
plot(whole_shape)
Attach some data to it and turn it into a SpatialPolygonDatFrame and then lets project all our maps so we can plot them in leaflet.
code <- data.frame("code" = 1:length(guessed_hex@polygons))
Hexagram <- SpatialPolygonsDataFrame(guessed_hex, code, match.ID = FALSE)
head(Hexagram@data)
## code
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
## 6 6
proj4string(WestminsterMap) <- "+init=epsg:27700 +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs +ellps=airy +towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894"
proj4string(Hexagram) <- proj4string(WestminsterMap)
proj4string(Main_simplecart) <- proj4string(WestminsterMap)
Using the polygon IDs as labels now means we can check where each hexagon is relative to the original map
library(leaflet)
getLeafletOptions <- function(minZoom, maxZoom, ...) {
leafletOptions(
crs = leafletCRS("L.CRS.Simple"),
minZoom = minZoom, maxZoom = maxZoom,
dragging = TRUE, zoomControl = TRUE,
tap = FALSE,
attributionControl = FALSE , ...)
}
#to get a look at it vs the original isles
leaflet(options = getLeafletOptions(-11, -11)) %>%
addPolygons(data = WestminsterMap, weight = 1, color = "#000000", fillColor = "#FFFFFF", fillOpacity = 1, label = ~as.character(fullname)) %>%
addPolygons(data = Hexagram, weight = 1, color = "#000000", fillColor = "#00FF00", label = ~as.character(code), highlightOptions = highlightOptions(weight = 3), fillOpacity = 0.3)
We can then attach data from the underlying cartogram to each hexagram using the over() function
library(rgeos)
## rgeos version: 0.3-21, (SVN revision 540)
## GEOS runtime version: 3.5.1-CAPI-1.9.1 r4246
## Linking to sp version: 1.2-3
## Polygon checking: TRUE
Hex_centres <- gCentroid(Hexagram, byid = TRUE)
Data <- over(Hex_centres, Main_simplecart)
and so on and so forth
Hexagram@data$fullname <- Data$fullnam
leaflet(options = getLeafletOptions(-11, -11)) %>%
addPolygons(data = Hexagram, weight = 1, color = "#000000", fillColor = "#00FF00", label = ~paste(fullname, as.character(code)), highlightOptions = highlightOptions(weight = 3), fillOpacity = 0.3)
You’ll notice some names are duplicated, especially in larger edge constituencies. To see which names are over-present and missing we can quickly use R
#over-represented
Hexagram@data$fullname[which(duplicated(Hexagram@data$fullname))]
## [1] South West Devon Co Const
## [2] Chichester Co Const
## [3] Arundel and South Downs Co Const
## [4] Arundel and South Downs Co Const
## [5] Arundel and South Downs Co Const
## [6] Lewes Co Const
## [7] Lewes Co Const
## [8] South Dorset Co Const
## [9] New Forest East Co Const
## [10] Mole Valley Co Const
## [11] East Surrey Co Const
## [12] Bexhill and Battle Co Const
## [13] Torridge and West Devon Co Const
## [14] Central Devon Co Const
## [15] Central Devon Co Const
## [16] West Dorset Co Const
## [17] Christchurch Co Const
## [18] Meon Valley Co Const
## [19] South West Surrey Co Const
## [20] Mole Valley Co Const
## [21] Bridgwater and West Somerset Co Const
## [22] Bridgwater and West Somerset Co Const
## [23] North East Hampshire Co Const
## [24] Mitcham and Morden Boro Const
## [25] Beckenham Boro Const
## [26] Maidstone and The Weald Co Const
## [27] Ashford Co Const
## [28] Surrey Heath Co Const
## [29] Dover Co Const
## [30] Devizes Co Const
## [31] Spelthorne Boro Const
## [32] Dulwich and West Norwood Boro Const
## [33] Old Bexley and Sidcup Boro Const
## [34] Gravesham Co Const
## [35] Canterbury Co Const
## [36] Reading West Co Const
## [37] Ealing, Southall Boro Const
## [38] Filton and Bradley Stoke Co Const
## [39] Wantage Co Const
## [40] Cities of London and Westminster Boro Const
## [41] Poplar and Limehouse Boro Const
## [42] Pontypridd Co Const
## [43] The Cotswolds Co Const
## [44] Beaconsfield Co Const
## [45] Uxbridge and South Ruislip Boro Const
## [46] Castle Point Boro Const
## [47] Tewkesbury Co Const
## [48] Tewkesbury Co Const
## [49] Witney Co Const
## [50] Henley Co Const
## [51] Holborn and St. Pancras Boro Const
## [52] Hackney South and Shoreditch Boro Const
## [53] Dagenham and Rainham Boro Const
## [54] Rochford and Southend East Co Const
## [55] Llanelli Co Const
## [56] Torfaen Co Const
## [57] South West Hertfordshire Co Const
## [58] Harrow East Boro Const
## [59] Finchley and Golders Green Boro Const
## [60] North Herefordshire Co Const
## [61] Kenilworth and Southam Co Const
## [62] Buckingham Co Const
## [63] Brecon and Radnorshire Co Const
## [64] Stourbridge Boro Const
## [65] Kenilworth and Southam Co Const
## [66] South Northamptonshire Co Const
## [67] Hitchin and Harpenden Co Const
## [68] Broxbourne Boro Const
## [69] Broxbourne Boro Const
## [70] Ludlow Co Const
## [71] Dudley North Boro Const
## [72] Birmingham, Ladywood Boro Const
## [73] Mid Bedfordshire Co Const
## [74] Hitchin and Harpenden Co Const
## [75] Hertford and Stortford Co Const
## [76] Saffron Walden Co Const
## [77] Harwich and North Essex Co Const
## [78] The Wrekin Co Const
## [79] North Warwickshire Co Const
## [80] North East Bedfordshire Co Const
## [81] South Suffolk Co Const
## [82] Central Suffolk and North Ipswich Co Const
## [83] Suffolk Coastal Co Const
## [84] North Shropshire Co Const
## [85] Walsall North Boro Const
## [86] North Warwickshire Co Const
## [87] Harborough Co Const
## [88] South Cambridgeshire Co Const
## [89] South East Cambridgeshire Co Const
## [90] South Norfolk Co Const
## [91] Stone Co Const
## [92] Lichfield Co Const
## [93] Corby Co Const
## [94] North East Cambridgeshire Co Const
## [95] South Norfolk Co Const
## [96] Great Yarmouth Co Const
## [97] Aberconwy Co Const
## [98] Ellesmere Port and Neston Co Const
## [99] South Derbyshire Co Const
## [100] Erewash Co Const
## [101] Rushcliffe Co Const
## [102] Rutland and Melton Co Const
## [103] South Holland and The Deepings Co Const
## [104] Clwyd West Co Const
## [105] Tatton Co Const
## [106] Staffordshire Moorlands Co Const
## [107] Halton Co Const
## [108] Macclesfield Co Const
## [109] Ashfield Co Const
## [110] Sleaford and North Hykeham Co Const
## [111] Liverpool, Wavertree Boro Const
## [112] North East Derbyshire Co Const
## [113] Louth and Horncastle Co Const
## [114] Stretford and Urmston Boro Const
## [115] Sheffield, Hallam Co Const
## [116] Bassetlaw Co Const
## [117] Gainsborough Co Const
## [118] Knowsley Boro Const
## [119] Worsley and Eccles South Co Const
## [120] Cleethorpes Co Const
## [121] Makerfield Co Const
## [122] Colne Valley Co Const
## [123] Brigg and Goole Co Const
## [124] Bolton West Co Const
## [125] Morley and Outwood Co Const
## [126] South Ribble Co Const
## [127] Rossendale and Darwen Boro Const
## [128] Rochdale Co Const
## [129] Blackpool South Boro Const
## [130] Halifax Boro Const
## [131] Selby and Ainsty Co Const
## [132] Wyre and Preston North Co Const
## [133] Burnley Boro Const
## [134] Thirsk and Malton Co Const
## [135] Scarborough and Whitby Co Const
## [136] Keighley Co Const
## [137] Morecambe and Lunesdale Co Const
## [138] Richmond (Yorks) Co Const
## [139] Sedgefield Co Const
## [140] Stockton North Boro Const
## [141] Houghton and Sunderland South Boro Const
## [142] North West Durham Co Const
## [143] Washington and Sunderland West Boro Const
## [144] Dumfries and Galloway Co Const
## [145] Hexham Co Const
## [146] Blyth Valley Boro Const
## [147] Central Ayrshire Co Const
## [148] Lanark and Hamilton East Co Const
## [149] Midlothian Co Const
## [150] North Ayrshire and Arran Co Const
## [151] Glasgow South West Burgh Const
## [152] Cumbernauld, Kilsyth and Kirkintilloch East Co Const
## [153] Cumbernauld, Kilsyth and Kirkintilloch East Co Const
## [154] Dunfermline and West Fife Co Const
## [155] Perth and North Perthshire Co Const
## [156] Perth and North Perthshire Co Const
## [157] West Aberdeenshire and Kincardine Co Const
## [158] Ross, Skye and Lochaber Co Const
## 628 Levels: Aberavon Co Const ... York Outer Co Const
#missing
Westminster_data$fullname[!Westminster_data$fullname %in% Hexagram@data$fullname]
## [1] "Aberdeen North Burgh Const"
## [2] "Aldershot Boro Const"
## [3] "Arfon Co Const"
## [4] "Ashton-under-Lyne Boro Const"
## [5] "Barnsley Central Boro Const"
## [6] "Bath Boro Const"
## [7] "Bedford Boro Const"
## [8] "Birmingham, Hodge Hill Boro Const"
## [9] "Blackpool North and Cleveleys Boro Const"
## [10] "Blaenau Gwent Co Const"
## [11] "Bognor Regis and Littlehampton Co Const"
## [12] "Bolton South East Boro Const"
## [13] "Bournemouth West Boro Const"
## [14] "Bradford West Boro Const"
## [15] "Braintree Co Const"
## [16] "Brighton, Pavilion Boro Const"
## [17] "Bristol North West Boro Const"
## [18] "Bromley and Chislehurst Boro Const"
## [19] "Bury St. Edmunds Co Const"
## [20] "Camberwell and Peckham Boro Const"
## [21] "Cambridge Boro Const"
## [22] "Cannock Chase Co Const"
## [23] "Carlisle Boro Const"
## [24] "Chatham and Aylesford Co Const"
## [25] "Cheltenham Boro Const"
## [26] "Chesham and Amersham Co Const"
## [27] "Chesterfield Boro Const"
## [28] "Chorley Co Const"
## [29] "Clacton Co Const"
## [30] "Coventry North East Boro Const"
## [31] "Crawley Boro Const"
## [32] "Crewe and Nantwich Co Const"
## [33] "Croydon South Boro Const"
## [34] "Cynon Valley Co Const"
## [35] "Darlington Boro Const"
## [36] "Dartford Co Const"
## [37] "Daventry Co Const"
## [38] "Delyn Co Const"
## [39] "Derby South Boro Const"
## [40] "Doncaster Central Boro Const"
## [41] "Dudley South Boro Const"
## [42] "Dundee West Burgh Const"
## [43] "Eastbourne Boro Const"
## [44] "East Devon Co Const"
## [45] "East Ham Boro Const"
## [46] "East Yorkshire Co Const"
## [47] "Edinburgh South Burgh Const"
## [48] "Enfield North Boro Const"
## [49] "Exeter Boro Const"
## [50] "Falkirk Co Const"
## [51] "Fareham Co Const"
## [52] "Faversham and Mid Kent Co Const"
## [53] "Feltham and Heston Boro Const"
## [54] "Folkestone and Hythe Co Const"
## [55] "Fylde Co Const"
## [56] "Garston and Halewood Boro Const"
## [57] "Glasgow North Burgh Const"
## [58] "Glasgow South Burgh Const"
## [59] "Gloucester Boro Const"
## [60] "Gower Co Const"
## [61] "Grantham and Stamford Co Const"
## [62] "Great Grimsby Boro Const"
## [63] "Guildford Co Const"
## [64] "Hackney North and Stoke Newington Boro Const"
## [65] "Harlow Co Const"
## [66] "Harrow West Boro Const"
## [67] "Havant Boro Const"
## [68] "Hayes and Harlington Boro Const"
## [69] "Hemel Hempstead Co Const"
## [70] "Hendon Boro Const"
## [71] "Hereford and South Herefordshire Co Const"
## [72] "Hornchurch and Upminster Boro Const"
## [73] "Horsham Co Const"
## [74] "Huddersfield Boro Const"
## [75] "Hyndburn Boro Const"
## [76] "Ilford South Boro Const"
## [77] "Inverclyde Co Const"
## [78] "Ipswich Boro Const"
## [79] "Islington South and Finsbury Boro Const"
## [80] "Jarrow Boro Const"
## [81] "Kingston upon Hull East Boro Const"
## [82] "Lancaster and Fleetwood Co Const"
## [83] "Leeds East Boro Const"
## [84] "Leicester South Boro Const"
## [85] "Leigh Co Const"
## [86] "Lewisham West and Penge Boro Const"
## [87] "Lincoln Boro Const"
## [88] "Liverpool, Walton Boro Const"
## [89] "Luton North Boro Const"
## [90] "Maldon Co Const"
## [91] "Mansfield Co Const"
## [92] "Middlesbrough South and East Cleveland Co Const"
## [93] "Mid Norfolk Co Const"
## [94] "Mid Sussex Co Const"
## [95] "Milton Keynes South Boro Const"
## [96] "Motherwell and Wishaw Burgh Const"
## [97] "Newton Abbot Co Const"
## [98] "New Forest West Co Const"
## [99] "Northampton South Boro Const"
## [100] "North Devon Co Const"
## [101] "North Durham Co Const"
## [102] "North West Cambridgeshire Co Const"
## [103] "Norwich South Boro Const"
## [104] "Nottingham South Boro Const"
## [105] "Nuneaton Co Const"
## [106] "Oldham West and Royton Boro Const"
## [107] "Oxford East Boro Const"
## [108] "Peterborough Boro Const"
## [109] "Reading East Boro Const"
## [110] "Romsey and Southampton North Co Const"
## [111] "Runnymede and Weybridge Co Const"
## [112] "Salford and Eccles Boro Const"
## [113] "Scunthorpe Co Const"
## [114] "Sheffield, Heeley Boro Const"
## [115] "Skipton and Ripon Co Const"
## [116] "Southend West Boro Const"
## [117] "South Basildon and East Thurrock Co Const"
## [118] "South Swindon Co Const"
## [119] "South Thanet Co Const"
## [120] "Stoke-on-Trent South Boro Const"
## [121] "St. Albans Co Const"
## [122] "St. Helens South and Whiston Boro Const"
## [123] "Tamworth Co Const"
## [124] "Taunton Deane Co Const"
## [125] "Telford Boro Const"
## [126] "Tynemouth Boro Const"
## [127] "Vale of Glamorgan Co Const"
## [128] "Walsall South Boro Const"
## [129] "Wansbeck Co Const"
## [130] "Warley Boro Const"
## [131] "Warrington South Boro Const"
## [132] "Warwick and Leamington Boro Const"
## [133] "Waveney Co Const"
## [134] "Wealden Co Const"
## [135] "Welwyn Hatfield Co Const"
## [136] "Westminster North Boro Const"
## [137] "Wigan Co Const"
## [138] "Wirral West Co Const"
## [139] "Witham Co Const"
## [140] "Wokingham Co Const"
## [141] "Wolverhampton North East Boro Const"
## [142] "Worcester Boro Const"
## [143] "Wrexham Co Const"
## [144] "York Central Boro Const"
## [145] "Dwyfor Meirionnydd Co Const"
## [146] "Ynys Mon Co Const"
## [147] "Cardiff South and Penarth Boro Const"
## [148] "Isle of Wight Co Const"
## [149] "Plymouth, Sutton and Devonport Boro Const"
## [150] "St. Ives Co Const"
## [151] "Caithness, Sutherland and Easter Ross Co Const"
## [152] "Orkney and Shetland Co Const"
## [153] "Na h-Eileanan an Iar Co Const"
## [154] "Argyll and Bute Co Const"
## [155] "Kirkcaldy and Cowdenbeath Co Const"
## [156] "Edinburgh West Burgh Const"
## [157] "East Lothian Co Const"
## [158] "Carmarthen West and South Pembrokeshire Co Const"
Or alternatively, coloured in:
Hexagram@data$duplicate <- NA
Hexagram@data$duplicate[which(duplicated(Hexagram@data$fullname))] <- "Y"
Duplicated <- Hexagram[which(Hexagram@data$duplicate == "Y"),]
leaflet(options = getLeafletOptions(-11, -11)) %>%
addPolygons(data = Hexagram, weight = 1, color = "#000000", fillColor = "#00FF00", label = ~paste(fullname, as.character(code)), highlightOptions = highlightOptions(weight = 3), fillOpacity = 0.3) %>%
addPolygons(data = Duplicated, weight = 1, color = "#000000", fillColor = "#FF0000", label = ~paste(fullname, as.character(code)), highlightOptions = highlightOptions(weight = 3), fillOpacity = 0.3)
df <- Hexagram@data
missing <- Westminster_data$fullname[!Westminster_data$fullname %in% Hexagram@data$fullname]
write.csv(missing, "Missing Hexes.csv")
write.csv(df, "Hexagram Data.csv")