Code
<- read_delim(file = gnaf_core, delim = "|")
gnaf <- gnaf %>%
nsw_addresses rename_with(tolower) %>%
filter(state == "NSW")
write.fst(nsw_addresses, path = here("data/gnaf_subset.fst"))
rm(gnaf)
Not included in this repo for size and/or license restrictions:
NSW Electoral Commission MapInfo file
List of NSW addresses from the August 2022 G-NAF (the Geocoded National Address File).
For making maps, we use the (very good) approximations to postcode geographies provided in shapefiles published by the Australian Bureau of Statistics.
MapInfo
file and ABS postal area shape files.sf
R package.leaflet
to build a JS mapping/visualization application.quarto
assembles this document and the web application, in turn utilising Observable JS
for table building and the call to leaflet
(Section 8).<- read_delim(file = gnaf_core, delim = "|")
gnaf <- gnaf %>%
nsw_addresses rename_with(tolower) %>%
filter(state == "NSW")
write.fst(nsw_addresses, path = here("data/gnaf_subset.fst"))
rm(gnaf)
<- read.fst(path = here("data/gnaf_subset.fst"))
nsw_addresses <- nsw_addresses %>%
nsw_geo distinct(postcode,longitude,latitude)
<- st_as_sf(nsw_geo,
nsw_geo coords = c('longitude', 'latitude'),
crs = st_crs(CRS("+init=EPSG:7843"))
)
We subset the G-NAF “core” file to NSW addresses, yielding 4,772,933 addresses, spanning 622 postcodes and 2,894,120 distinct geo-codes (latitude/longitude pairs).
We read the MapInfo file supplied by the NSW Electoral Commission; this employs the GDA2020
CRS and contain the boundaries for NSW’s 93 state legislative districts, to be used in the 2023 election (gazetted and proclaimed on 26 August 2021).
<-
nsw_shp st_read(
dsn = here("data/StateElectoralDistrict2021_GDA2020.MID"),
query = sprintf(
"SELECT objectid, cadid, districtna from StateElectoralDistrict2021_GDA2020"
)
)<- st_transform(nsw_shp, crs = CRS("+init=EPSG:7843"))
nsw_shp <- as(nsw_shp, "sf") %>% st_make_valid()
nsw_shp ::geojson_write(nsw_shp,file = here("data/nsw_shp.json")) geojsonio
For map-making later on, we read the ABS postal areas (postcode) shapefiles; the coordinates use the GDA2020
geodetic CRS, which corresponds to EPSG:7843
CRS used in the other data sets we utilize.
<-
poa_shp st_read(
here("data/POA_2021_AUST_GDA2020_SHP/POA_2021_AUST_GDA2020.shp"),
)st_crs(poa_shp) <- CRS("+init=EPSG:7843")
<- st_transform(poa_shp, crs = CRS("+init=EPSG:7843"))
poa_shp <- as(poa_shp, "sf") %>% st_make_valid() poa_shp
We now match the unique, geo-coded NSW addresses to districts, the real “work” of this exercise being done by the call to sf::st_intersects
in the function pfunc
; we group the data by postcode
and use process these batches of data in parallel via furrr::future_map
.
<- function(obj){
pfunc <- st_intersects(obj$geometry,nsw_shp)
z return(as.integer(z))
}
<- nsw_geo %>%
nsw_geo group_nest(postcode) %>%
mutate(
intersection = future_map(.x = data,
.f = ~pfunc(.x))
%>%
) ungroup()
<- nsw_geo %>%
nsw_geo unnest(c(data,intersection))
<- nsw_geo %>%
nsw_geo mutate(district = nsw_shp$districtna[intersection])
We merge the results back against the GNAF addresses for NSW:
<- st_coordinates(nsw_geo$geometry)
coords <- nsw_geo %>%
nsw_geo mutate(longitude = coords[,1],
latitude = coords[,2]) %>%
select(-geometry)
<- left_join(nsw_addresses,
nsw_addresses
nsw_geo,by = c("postcode", "longitude", "latitude"))
rm(nsw_geo,coords)
We also filter down to the subset of Australian postcodes that intersect NSW legislative districts:
<- poa_shp %>%
poa_shp semi_join(nsw_addresses %>% distinct(postcode),
by = c("POA_CODE21" = "postcode"))
<- poa_shp %>%
poa_shp_small st_simplify(preserveTopology = TRUE, dTolerance = 5)
::geojson_write(poa_shp_small,file = here("data/nsw_poa_shp.json")) geojsonio
We compute counts of addresses with postcodes within districts; we also compute
per_of_district
)per_of_postcode
)<- nsw_addresses %>%
out count(district,postcode) %>%
group_by(district) %>%
mutate(per_of_district = n/sum(n)*100) %>%
ungroup() %>%
group_by(postcode) %>%
mutate(per_of_postcode = n/sum(n)*100) %>%
ungroup() %>%
arrange(district,desc(per_of_district))
rm(nsw_addresses)
ojs_define(out_raw=out)
= transpose(out_raw)
out = Inputs.select(out.map(d => d.district),
viewof theDistrict
{label: "District: ",
sort: true,
unique: true
}
)= out.filter(d => d.district == theDistrict) out_small
.table(
Inputs,
out_small
{format: {
per_of_district: x => x.toFixed(1),
per_of_postcode: x => x.toFixed(1)
,
}rows: out_small.length + 10
} )
= await FileAttachment("data/nsw_shp.json").json() nsw_shp_json
= 800
width = 650
height = FileAttachment("data/nsw_poa_shp.json").json()
poa_shp_json
= out_small.map(d => d.postcode) thePostcodes
= require('leaflet@1.9.2')
L
= {
map2 let container = DOM.element ('div', { style: `width:${width}px;height:${height}px` });
yield container;
let map = L.map(container)
let osmLayer = L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.{ext}', {
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> — Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
subdomains: 'abcd',
minZoom: 0,
maxZoom: 25,
ext: 'png'
.addTo(map);
})
function districtFilter(feature) {
if(feature.properties.districtna === theDistrict) return true
}
function postCodeFilter(feature) {
if(thePostcodes.includes(feature.properties.POA_CODE21)) return true;
}
function style(feature) {
return {
fillColor: "blue",
weight: 2,
opacity: 0.7,
color: 'blue',
fillOpacity: 0.0
;
}
}
// highlight function
function highlightFeature(e) {
var layer = e.target;
.setStyle({
layerfillOpacity: 0.5,
opacity: 1.0,
weight: 3
;
})
.openTooltip();
layer
.bringToFront();
layer
}
// mouseout
function resetHighlight(e) {
.resetStyle(e.target);
poaLayer
}
function onEachFeature(feature, layer) {
.bindTooltip("<div style='background:white; padding:1px 3px 1px 3px'><b>" + feature.properties.POA_CODE21 + "</b></div>",
layer
{direction: 'right',
permanent: false,
sticky: true,
offset: [10, 0],
opacity: 1,
className: 'leaflet-tooltip-own'
;
}).on({
layermouseover: highlightFeature,
mouseout: resetHighlight
;
})
}
let DistLayer = L.geoJson(nsw_shp_json,
{filter: districtFilter,
weight: 5,
color: "#cc00007f",
.bindPopup(function (Layer) {
})return Layer.feature.properties.districtna;
.addTo(map);
})
let poaLayer = L.geoJson(poa_shp_json,
{filter: postCodeFilter,
style : style,
onEachFeature: onEachFeature
}).addTo(map);
.fitBounds(DistLayer.getBounds());
map }
write.csv(out,
file = here("data/district_postcode_counts.csv"),
row.names = FALSE)