---
title: "Getting started with 'ggmapinset'"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting started with 'ggmapinset'}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
suppressPackageStartupMessages(library(dplyr))
```

```{r setup}
library(ggmapinset)
library(ggplot2)

nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
```

This article provides some recipes for working with insets.

## Example usage
This example uses the mosquito surveillance dataset `ggmapinset::mozzies_nsw2301`.
It includes points from across New South Wales.

```{r}
head(mozzies_nsw2301)
```
Firstly, we can recreate the basic maps from the report accompanying the dataset.
Since the dataset has latitude and longitude coordinates, it can easily be
converted into a spatial data frame with `sf::st_as_sf()`.

```{r fig.width=9}
library(dplyr)
library(sf)

# just take the total count from a single week of the data
mozzies <- mozzies_nsw2301 |>
  filter(species == "total", week_ending == as.Date("2023-01-07")) |>
  st_as_sf(coords = c("long", "lat"), crs = st_crs("WGS84"))

labels <- c("Low (<50)", "Medium (50-100)", "High (101-1,000)",
            "Very High (1,001-10,000)", "Extreme (>10,000)")

scale1 <- scale_colour_manual(
  name = NULL,
  values = c("green", "gold", "darkorange", "red", "black"),
  labels = labels,
  na.value = "grey",
  drop = FALSE
)
scale2 <- scale_size_ordinal(
  name = NULL,
  labels = labels,
  range = c(3, 5),
  na.value = 2,
  drop = FALSE
)

ggplot(mozzies) +
  geom_sf(data = nswgeo::nsw, fill = NA) +
  geom_sf(aes(size = count, colour = count)) +
  geom_sf_text(aes(label = location), hjust = 0, nudge_x = 0.25, size = 3) +
  coord_sf(xlim = c(NA, 158)) +
  scale1 + scale2 +
  theme_void()
```

The warning about `sf::st_point_on_surface` can be disregarded. Any errors due
to the coordinate system are unlikely to make much visual difference to where
text is placed in this case.

This plot looks a little congested. We can improve things a bit by using the
repulsive version of the label geom from `{ggrepel}`:

```{r fig.width=10}
library(ggrepel)

ggplot(mozzies) +
  geom_sf(data = nswgeo::nsw, fill = NA) +
  geom_sf(aes(size = count, colour = count)) +
  geom_text_repel(
    aes(label = location, geometry = geometry),
    hjust = 0,
    nudge_x = 0.25,
    size = 3,
    max.overlaps = 15,
    point.padding = 0,
    min.segment.length = 1,
    stat = "sf_coordinates"
  ) +
  coord_sf(xlim = c(NA, 158)) +
  scale1 + scale2 +
  theme_void()
```

The main thing to note above is that `geom_text_repel()` is not hooked into the
`{ggplot2}`'s geospatial integration, so it needs to be told to use
`stat_sf_coordinates()` to compute the coordinates, and it needs an explicit
mapping for the `geometry` aesthetic.

That improved most of the map except for the Sydney region where most of the
labels are missing since they would overlap. The `max.overlaps` parameter to
`geom_text_repel()` can help, but doesn't address the overcrowding issue.
This is where an inset can help.

First we define the inset we want. We can collect up all the points from the
dataset that were labelled with `type == "sydney"` and use some standard
geospatial functions to get the diameter and centre of a circle that will cover
all those points. We then specify that we want this circle to be enlarged by a
factor of 4, and shifted to south and east:

```{r}
sydney <- filter(mozzies, type == "sydney")
sydney_size <- st_distance(sydney, sydney) |> max() |> as.numeric() / 1000
sydney_centre <- st_union(sydney) |> st_centroid()

sydney_inset <- configure_inset(
  shape_circle(centre = sydney_centre, radius = sydney_size),
  translation = c(400, -200),
  scale = 4,
  units = "km"
)
```

Finally, we can repeat the previous plot with the `_inset` version of the relevant
layers. The inset configuration is passed to the coord. The only other change is
that to make sure the labels for Sydney sites appear in the inset instead of the
base map, we need to remap the `x` and `y` aesthetics to the versions computed by
the underlying stat.

```{r fig.width=10}
ggplot(mozzies) +
  geom_sf_inset(data = nswgeo::nsw, fill = NA) +
  geom_sf_inset(aes(size = count, colour = count), map_base = "clip") +
  geom_text_repel(
    aes(
      x = after_stat(x_inset),
      y = after_stat(y_inset),
      label = location,
      geometry = geometry
    ),
    hjust = 0,
    nudge_x = 0.25,
    size = 3,
    force_pull = 2,
    max.overlaps = Inf,
    point.padding = 0,
    min.segment.length = 1,
    stat = "sf_coordinates_inset"
  ) +
  geom_inset_frame() +
  coord_sf_inset(xlim = c(NA, 158), inset = sydney_inset) +
  scale1 + scale2 +
  theme_void()
```

Further tweaks of label placement can be achieved by playing around with the
parameters of `geom_text_repel()`, or by passing vectors of positions into the
`nudge_x` and `nudge_y` parameters.

## Different aesthetics for inset layer
By default, `geom_sf_inset()` creates two copies of the map layer: one for the
base map and the other for the inset map. The inset is transformed and clipped,
but uses the same underlying aesthetics mapping and parameters.

If you want to have different aesthetics for the two layers, you'll need to turn
off this copying with `map_base = "none"`. With this parameter set and an
`inset` parameter provided, only the inset layer will be drawn. To draw only the
base layer, you can use `map_inset = "none"`, `inset = NULL`, or simply use the
normal `geom_sf()`.

```{r separate, fig.width=7, fig.height=3.5}
ggplot(nc) +
  # this is equivalent to the following line:
  # geom_sf_inset(fill = "white", map_inset = "none") +
  geom_sf(fill = "white") +
  geom_sf_inset(aes(fill = AREA), map_base = "none") +
  geom_inset_frame() +
  coord_sf_inset(configure_inset(
    shape_circle(
      centre = sf::st_centroid(sf::st_geometry(nc)[nc$NAME == "Bladen"]),
      radius = 50
    ),
    scale = 1.5, translation = c(-180, -50),  units = "mi"
  ))
```

## Inset frame backgrounds
By default, the inset frame is transparent, although often it makes sense to add
a solid background so that the inset is distinguishable from any overlapping part
of the base map. The aesthetics of the two parts of the frame and the burst lines
connecting them can be controlled separately.

Note that when the background is filled, we need to specify the base and inset
maps in separate layers so that the frame can slip in between them.
```{r frame_fill, fig.width=7, fig.height=3}
ggplot(nc) +
  geom_sf(aes(fill = AREA)) +
  geom_inset_frame(target.aes = list(fill = "white")) +
  geom_sf_inset(aes(fill = AREA), map_base = "none") +
  coord_sf_inset(configure_inset(
    shape_circle(
      centre = st_centroid(st_geometry(nc)[nc$NAME == "Yancey"]),
      radius = 50
    ),
    scale = 2, translation = c(100, -120), units = "mi"
  ))
```

## Multiple insets
For multiple insets, the appropriate inset configuration just needs to be passed to each
layer separately. It's probably clearer to avoid providing an inset to the coordinate
system in this case.

Since the inset-aware layers will duplicate themselves for the base and inset maps,
you will probably want to disable that behaviour with `map_base = "none"` to avoid
having multiple identical copies of the base map.
```{r multiple, fig.width=7, fig.height=5}
inset1 <- configure_inset(
  shape_rectangle(
    centre = sf::st_centroid(nc[nc$NAME == "Bladen", ]),
    hwidth = 50
  ),
  scale = 1.5, translation = c(150, -50), units = "mi"
)
inset2 <- configure_inset(
  shape_circle(
    centre = sf::st_centroid(nc[nc$NAME == "Orange", ]),
    radius = 30
  ),
  scale = 3, translation = c(30, 120), units = "mi"
)
inset3 <- configure_inset(
  shape_sf(nc[nc$NAME == "Gates", ]),
  scale = 6, translation = c(90, 70), units = "mi"
)

ggplot(nc) +
  # base map
  geom_sf_inset() +
  # inset 1
  geom_sf_inset(map_base = "none", inset = inset1) +
  geom_inset_frame(inset = inset1, colour = "red") +
  # inset 2
  geom_sf_inset(map_base = "none", inset = inset2) +
  geom_inset_frame(inset = inset2, colour = "blue") +
  # inset 3
  geom_sf_inset(map_base = "none", inset = inset3, colour = NA) +
  geom_inset_frame(inset = inset3, colour = "magenta")
```