mapycusmaximus brings fisheye transformations to R’s
spatial ecosystem. Just as ggplot2 transforms how we
visualize data and dplyr transforms how we manipulate it,
mapycusmaximus transforms how we view geographic space—allowing you to
magnify local detail while preserving regional context.
The package implements the Focus-Glue-Context (FGC) model, a three-zone radial transformation that:
This vignette will show you how to use mapycusmaximus with real spatial data, following tidyverse principles of working with data.
Let’s start with the built-in Victoria LGA dataset. The goal is to magnify Melbourne while keeping the rest of Victoria visible.
# Examine the data
data(vic)
vic
#> Simple feature collection with 79 features and 1 field
#> Geometry type: MULTIPOLYGON
#> Dimension: XY
#> Bounding box: xmin: 140.9619 ymin: -39.13442 xmax: 149.9762 ymax: -33.98064
#> Geodetic CRS: WGS 84
#> First 10 features:
#> LGA_NAME geometry
#> 1 ALPINE MULTIPOLYGON (((146.7238 -3...
#> 2 ARARAT MULTIPOLYGON (((143.0904 -3...
#> 3 BALLARAT MULTIPOLYGON (((143.9239 -3...
#> 4 BANYULE MULTIPOLYGON (((145.1342 -3...
#> 5 BASS COAST MULTIPOLYGON (((145.3214 -3...
#> 6 BAW BAW MULTIPOLYGON (((145.7643 -3...
#> 7 BAYSIDE MULTIPOLYGON (((144.986 -37...
#> 8 BENALLA MULTIPOLYGON (((146.1131 -3...
#> 9 BOROONDARA MULTIPOLYGON (((145.1039 -3...
#> 10 BRIMBANK MULTIPOLYGON (((144.8829 -3...The simplest fisheye uses defaults and automatically determines the center:
# Apply fisheye transformation
vic_warped <- sf_fisheye(
vic,
r_in = 0.3, # Focus radius
r_out = 0.6, # Glue boundary
zoom_factor = 2, # Magnification strength
squeeze_factor = 0.35
)
# Visualize with ggplot2
ggplot(vic_warped) +
geom_sf(fill = "grey90", color = "white", linewidth = 0.3) +
labs(title = "Victoria LGAs with Default Fisheye")A basic fisheye transformation of Victoria’s LGAs
That’s it! But to really control the transformation, you’ll want to specify a focus point.
The fisheye center determines what gets magnified. There are several ways to specify it:
The most natural approach—pass an sf object and
mapycusmaximus uses its centroid:
# Extract Melbourne CBD as the focus
melbourne <- vic[vic$LGA_NAME == "MELBOURNE", ]
vic_melbourne <- sf_fisheye(
vic,
center = melbourne, # Centroid becomes the warp center
r_in = 0.34,
r_out = 0.60,
zoom_factor = 15,
squeeze_factor = 0.35
)
ggplot() +
geom_sf(data = vic_melbourne, fill = "grey92", color = "white", linewidth = 0.2) +
geom_sf(data = melbourne, fill = NA, color = "tomato", linewidth = 0.8) +
labs(title = "Melbourne CBD Magnified",
subtitle = "Focus defined by Melbourne LGA geometry")Fisheye centered on Melbourne CBD
Specify coordinates directly in WGS84:
# Melbourne CBD coordinates (WGS84)
melb_coords <- c(144.9631, -37.8136)
vic_coords <- sf_fisheye(
vic,
center = melb_coords,
center_crs = "EPSG:4326", # Explicitly state CRS
r_in = 0.30,
r_out = 0.55,
zoom_factor = 12,
squeeze_factor = 0.30
)
ggplot(vic_coords) +
geom_sf(fill = "grey92", color = "white", linewidth = 0.2) +
labs(title = "Center Specified as Lon/Lat",
subtitle = "Coordinates: 144.96°E, 37.81°S")Fisheye using lon/lat coordinates
If you’re already working in a projected CRS, pass coordinates directly:
The transformation is controlled by four key parameters:
r_in and r_out define the transformation
zones in normalized space (roughly -1 to 1):
# Small focus, narrow glue
vic_tight <- sf_fisheye(vic, center = melbourne, r_in = 0.2, r_out = 0.3,
zoom_factor = 8, squeeze_factor = 0.35)
# Large focus, wide glue
vic_wide <- sf_fisheye(vic, center = melbourne, r_in = 0.4, r_out = 0.7,
zoom_factor = 8, squeeze_factor = 0.35)
p1 <- ggplot(vic_tight) +
geom_sf(fill = "grey90", color = "white", linewidth = 0.2) +
labs(title = "Tight Focus (r_in=0.2, r_out=0.3)")
p2 <- ggplot(vic_wide) +
geom_sf(fill = "grey90", color = "white", linewidth = 0.2) +
labs(title = "Wide Focus (r_in=0.4, r_out=0.7)")
# Display side by side (requires patchwork or cowplot)
# p1 + p2
p1Effect of different radius settings
Effect of different radius settings
Controls magnification strength inside the focus:
# Gentle zoom
vic_gentle <- sf_fisheye(vic, center = melbourne, r_in = 0.3, r_out = 0.5,
zoom_factor = 3, squeeze_factor = 0.35)
# Aggressive zoom
vic_aggressive <- sf_fisheye(vic, center = melbourne, r_in = 0.3, r_out = 0.5,
zoom_factor = 20, squeeze_factor = 0.35)
ggplot(vic_gentle) +
geom_sf(fill = "grey90", color = "white", linewidth = 0.2) +
labs(title = "Gentle Magnification (zoom = 3)")Effect of zoom factor
ggplot(vic_aggressive) +
geom_sf(fill = "grey90", color = "white", linewidth = 0.2) +
labs(title = "Strong Magnification (zoom = 20)")Effect of zoom factor
Controls compression in the glue zone (0 to 1):
# Minimal squeeze (wider glue transition)
vic_loose <- sf_fisheye(vic, center = melbourne, r_in = 0.3, r_out = 0.5,
zoom_factor = 8, squeeze_factor = 0.1)
# Strong squeeze (narrow glue transition)
vic_tight_squeeze <- sf_fisheye(vic, center = melbourne, r_in = 0.3, r_out = 0.5,
zoom_factor = 8, squeeze_factor = 0.8)
ggplot(vic_loose) +
geom_sf(fill = "grey90", color = "white", linewidth = 0.2) +
labs(title = "Loose Squeeze (0.1)")
ggplot(vic_tight_squeeze) +
geom_sf(fill = "grey90", color = "white", linewidth = 0.2) +
labs(title = "Tight Squeeze (0.8)")When you have multiple layers (points, lines, polygons), transform them together to ensure alignment:
# Create centroids as a point layer
centroids <- st_centroid(vic)
# Add a layer identifier to each
vic_layer <- vic |>
dplyr::mutate(layer = "polygon")
centroids_layer <- centroids |>
dplyr::mutate(layer = "centroid")
# Combine layers before transformation
both_layers <- rbind(
vic_layer[, c("LGA_NAME", "geometry", "layer")],
centroids_layer[, c("LGA_NAME", "geometry", "layer")]
)
# Apply fisheye once to combined data
both_warped <- sf_fisheye(
both_layers,
center = melbourne,
r_in = 0.34,
r_out = 0.60,
zoom_factor = 12,
squeeze_factor = 0.35
)
# Separate for plotting
polygons_warped <- both_warped[both_warped$layer == "polygon", ]
points_warped <- both_warped[both_warped$layer == "centroid", ]
# Plot together
ggplot() +
geom_sf(data = polygons_warped, fill = "grey92", color = "white",
linewidth = 0.2) +
geom_sf(data = points_warped, color = "#2b6cb0", size = 1.2, alpha = 0.7) +
labs(title = "Aligned Layers: Polygons and Centroids",
subtitle = "Transformed together to ensure perfect alignment")Multiple aligned layers with fisheye transformation
Why combine first? When layers are transformed separately, they may have different bounding boxes, leading to slightly different normalized coordinates and misalignment.
mapycusmaximus is projection-aware and handles CRS transformations automatically:
If your data is in longitude/latitude, the package automatically selects an appropriate projected CRS:
# Create data in WGS84
vic_lonlat <- st_transform(vic, "EPSG:4326")
st_crs(vic_lonlat)$proj4string
#> [1] "+proj=longlat +datum=WGS84 +no_defs"
# Apply fisheye - auto-projects to GDA2020/MGA55 for Victoria
vic_auto <- sf_fisheye(
vic_lonlat,
center = melbourne,
r_in = 0.3,
r_out = 0.5,
zoom_factor = 10,
squeeze_factor = 0.35
)
# Original CRS is restored
st_crs(vic_auto)$proj4string
#> [1] "+proj=longlat +datum=WGS84 +no_defs"The package uses sensible defaults: - Victoria region → EPSG:7855 (GDA2020 / MGA Zone 55) - Other areas → UTM zones based on centroid
For understanding the transformation itself, use the low-level
fisheye_fgc() function with test grids:
# Create a regular grid
grid <- create_test_grid(range = c(-1, 1), spacing = 0.1)
# Apply transformation
warped <- fisheye_fgc(
grid,
r_in = 0.34,
r_out = 0.5,
zoom_factor = 1.3,
squeeze_factor = 0.5
)
# Visualize the transformation
plot_fisheye_fgc(grid, warped, r_in = 0.34, r_out = 0.5)Understanding the FGC transformation with a test grid
The visualization shows: - Red zone: Focus (magnified) - Blue zone: Glue (transitional compression) - Yellow zone: Context (unchanged)
Here’s a complete workflow showing how to emphasize a metro area while maintaining state context:
# 1. Define the metropolitan region
metro_lgas <- c("MELBOURNE", "PORT PHILLIP", "STONNINGTON", "YARRA",
"MARIBYRNONG", "MOONEE VALLEY", "BOROONDARA",
"GLEN EIRA", "BAYSIDE")
metro_region <- vic[vic$LGA_NAME %in% metro_lgas, ]
metro_center <- st_union(metro_region) |> st_centroid()
# 2. Add a population indicator (example)
vic_pop <- vic |>
dplyr::mutate(is_metro = LGA_NAME %in% metro_lgas)
# 3. Apply fisheye
vic_focused <- sf_fisheye(
vic_pop,
center = metro_center,
r_in = 0.25,
r_out = 0.40,
zoom_factor = 12,
squeeze_factor = 0.35
)
# 4. Create publication-ready plot
ggplot(vic_focused) +
geom_sf(aes(fill = is_metro), color = "white", linewidth = 0.2) +
scale_fill_manual(
values = c("TRUE" = "#d95f02", "FALSE" = "grey85"),
labels = c("Metropolitan", "Regional"),
name = NULL
) +
theme_minimal() +
theme(
legend.position = c(0.85, 0.15),
legend.background = element_rect(fill = "white", color = "grey70"),
panel.grid = element_blank()
) +
labs(
title = "Metropolitan Melbourne with Regional Context",
subtitle = "Fisheye magnification preserves spatial relationships",
caption = "Transformation: r_in = 0.25, r_out = 0.40, zoom = 12"
)Complete workflow: Metropolitan Melbourne in Victorian context
Start conservative and iterate:
# Start here
sf_fisheye(data, r_in = 0.3, r_out = 0.5, zoom_factor = 5, squeeze_factor = 0.35)
# Too distorted? Reduce zoom or widen glue
sf_fisheye(data, r_in = 0.3, r_out = 0.6, zoom_factor = 3, squeeze_factor = 0.35)
# Need more magnification? Increase zoom gradually
sf_fisheye(data, r_in = 0.3, r_out = 0.5, zoom_factor = 10, squeeze_factor = 0.35)Always combine layers before transformation:
Be explicit about parameters for reproducible analyses:
For large datasets:
Problem: Points and polygons don’t line up after transformation.
Solution: Transform together (see “Working with Multiple Layers”).
Problem: Features look overly warped.
Solution: Reduce zoom_factor or
increase r_out to widen the glue zone.
Problem: Too much magnification causes overlap.
Solution: This is expected behavior. Reduce
zoom_factor if problematic.
revolution parameter for rotational effectsshiny_fisheye() for interactive parameter tuning?sf_fisheye,
?fisheye_fgc?vic for built-in datasetsThe Focus-Glue-Context model is based on:
Remember: Fisheye transformations distort distances and areas. Use them for visualization and exploration, but perform quantitative analyses on the original geometries.