Code
library(dplyr)
library(stringr)
library(sf)
library(ggplot2)
library(ltc)
nz <- st_read("nz-2193.gpkg")Belatedly delivering on a promise I made on Linkedin…
David O’Sullivan
July 23, 2026
I’ve not posted much of anything for a couple of months, as life in the shape of moving house, took over. The aftermath of moving continues to loom large, so this is just a quick post delivering on a promise I made a month or two ago on Linkedin to post code for how I made maps like those below.
I made the maps because Richard Law reposted a similar map of names for rivers in the UK by Tom Peterken. My maps are based on a LINZ river names pilot dataset and the analysis, as will be clear from the code below is very slight. Hopefully the maps themselves are pretty enough to compensate.
That’s pretty much it. I’m travelling for work for the next 10 days or so, and post-house move activity continues to dominate life, so it may remain quieter than it has been in the past on here for a while longer.
Per usual we need some libraries, and a basic New Zealand map.
Next we have to prep the data. That requires a function to determine if the name of any given river includes various Te Reo Māori words for water, or (as is more common) English generic terms for ‘river’.
anglo_generics <- c("Brook", "Burn", "Creek", "River", "Stream")
te_reo_generics <- c("Wai", "Awa", "Manga")
classify_waterways <- function(
gdf,
anglo = anglo_generics,
te_reo = te_reo_generics) {
gdf |>
filter(!is.na(name_ascii)) |>
select(name, name_ascii) |>
mutate(
anglo_generic = str_extract(name_ascii, str_flatten(anglo, "|")),
te_reo_generic = str_extract(
name_ascii,
str_c("(?i)", str_flatten(te_reo, "|"))
) |>
str_to_title()
) |>
filter(!is.na(anglo_generic) | !is.na(te_reo_generic))
}The only real mystery here is the (?i) prefix used in the regular expression supplied to str_extract. This makes the search case-insensitive, which for the Māori parts of words which may not appear at the beginning of a name is important. The keen eyed will spot here that the ordering of the Māori ‘water words’ combined with using str_extract which only finds the first match, will mean that finding instances of Wai, Awa, and Manga are prioritised in that order. A more sophisticated approach would be required to unpack cases where more than one of these word parts shows up in a toponym.
Next we read in the river names datasets and pass them through the function we just made.
This yields data that look like this:
Rows: 194,012
Columns: 6
$ name <chr> "Lee River", "Avon River", "Ōhinemahuta River", "Pigeon…
$ name_ascii <chr> "Lee River", "Avon River", "Ohinemahuta River", "Pigeon…
$ anglo_generic <chr> "River", "River", "River", "Creek", "Stream", "Stream",…
$ te_reo_generic <chr> NA, NA, NA, NA, NA, NA, NA, "Wai", NA, NA, NA, NA, "Awa…
$ size <dbl> 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, …
$ geom <LINESTRING [m]> LINESTRING (1613247 5416000..., LINESTRING (…
And we’re ready to map them.
First, the appearances of Te Reo Māori words for water in river names. The result is not exactly that because, in addition to the order of priority noted above, all the code is doing is identify those sequences of letters in the names, and sometimes those might occur with some other meaning.
ggplot() +
geom_sf(data = nz, fill = "#aaa", colour = NA) +
geom_sf(
data = all_rivers |> filter(!is.na(te_reo_generic)),
aes(colour = te_reo_generic, linewidth = size),
show.legend = "line"
) +
scale_colour_manual(
"Te Reo water word",
values = ltc("hat")[c(10, 2, 4)]
) +
scale_linewidth_identity() +
guides(colour = guide_legend(override.aes = list(linewidth = 3))) +
theme_void() +
theme(
legend.position = "inside",
legend.position.inside = c(0.25, 0.75)
)And finally the English language ‘generic’ words for rivers.
ggplot() +
geom_sf(data = nz, fill = "#aaa", colour = NA) +
geom_sf(
data = all_rivers |> filter(!is.na(anglo_generic)),
aes(colour = anglo_generic, linewidth = size),
show.legend = "line"
) +
scale_colour_manual(
"English river word",
values = ltc("hat")[c(10, 2, 4, 5, 7)]
) +
scale_linewidth_identity() +
guides(colour = guide_legend(override.aes = list(linewidth = 3))) +
theme_void() +
theme(
legend.position = "inside",
legend.position.inside = c(0.25, 0.75)
)