The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Getting started with explodemap

Overview

explodemap generates hierarchical exploded-view maps for dense administrative boundary data. It applies rigid-body translations to polygon geometries so that features are visually separated while each feature’s internal geometry is preserved exactly.

The basic two-level workflow is:

  1. Group units into regions using a column in your data.
  2. Displace units within and across those regions using a centroid-driven vector field with analytically derived parameters.

For the two-level core described in the paper, the package is designed to satisfy three key properties:

Input requirements

explodemap expects an sf object with:

Geographic coordinates such as EPSG:4326 (longitude/latitude) must be transformed before use. For U.S. work, a state plane, UTM, or Albers-type projected CRS is usually appropriate.

library(sf)
#> Warning: package 'sf' was built under R version 4.5.2
#> Linking to GEOS 3.13.1, GDAL 3.11.4, PROJ 9.7.0; sf_use_s2() is TRUE
library(explodemap)

A minimal example

We create a small synthetic dataset with four square polygons in two regions.

sq <- function(xmin, ymin, size = 1000) {
  st_polygon(list(matrix(
    c(xmin, ymin,
      xmin + size, ymin,
      xmin + size, ymin + size,
      xmin, ymin + size,
      xmin, ymin),
    ncol = 2,
    byrow = TRUE
  )))
}

geom <- st_sfc(
  sq(0, 0), sq(3000, 0),       # Region A
  sq(12000, 0), sq(15000, 0),  # Region B
  crs = 3857
)

x <- st_sf(
  id     = c("a1", "a2", "b1", "b2"),
  region = c("A", "A", "B", "B"),
  geometry = geom
)

x
#> Simple feature collection with 4 features and 2 fields
#> Geometry type: POLYGON
#> Dimension:     XY
#> Bounding box:  xmin: 0 ymin: 0 xmax: 16000 ymax: 1000
#> Projected CRS: WGS 84 / Pseudo-Mercator
#>   id region                       geometry
#> 1 a1      A POLYGON ((0 0, 1000 0, 1000...
#> 2 a2      A POLYGON ((3000 0, 4000 0, 4...
#> 3 b1      B POLYGON ((12000 0, 13000 0,...
#> 4 b2      B POLYGON ((15000 0, 16000 0,...

Running the explosion

The simplest entry point is explode_sf(). Pass your sf object and the name of the grouping column:

result <- explode_sf(x, region_col = "region", plot = FALSE)

In Shiny or other non-interactive pipelines, add quiet = TRUE to suppress progress messages:

quiet_result <- explode_sf(x, region_col = "region", plot = FALSE, quiet = TRUE)
class(quiet_result)
#> [1] "exploded_map" "list"

The returned object is an S3 object of class exploded_map:

class(result)
#> [1] "exploded_map" "list"
names(result)
#>  [1] "sf_orig"         "sf_exp"          "sf_exp_wgs"      "stats"          
#>  [5] "params"          "gamma_r_implied" "gamma_l_implied" "plots"          
#>  [9] "refinement"      "diagnostics"

It contains the original and exploded geometries, a WGS84 export-ready version, derived statistics and parameters, plots, and diagnostics.

Diagnostics

print() shows geometry statistics and derived parameters:

print(result)
#> 
#> -- Custom Dataset ----------------------------------------
#>   n units   :  4 
#>   n regions :  2 
#>   w_bar     :  1.1 km 
#>   R_local   :  1.5 km 
#>   n_bar     :  2 
#>   R_local/w :  1.33 
#>   alpha_r   :  1.7 km 
#>   alpha_l   :  2.4 km 
#>   p         :  1.25 
#>   max ||t|| :  4.1 km   (Proposition 3 bound)

summary() adds implied gamma coefficients that are useful for calibration work:

summary(result)
#> 
#> Exploded Map Summary
#> ====================
#> Dataset:      Custom Dataset 
#> Units:        4 
#> Regions:      2 
#> Grouped by:   region 
#> 
#> Geometry Statistics
#>   Characteristic diameter (w_bar):  1.1 km 
#>   Regional radius (R_local):        1.5 km 
#>   Median units/region (n_bar):      2 
#>   Tightness ratio (R_local/w_bar):  1.33 
#> 
#> Parameters
#>   alpha_r:  1.7 km   (regional separation)
#>   alpha_l:  2.4 km   (local expansion)
#>   p:        1.25 
#> 
#> Implied Gamma Coefficients
#>   gamma_r:  3 
#>   gamma_l:  1.136

Plotting

plot(result)

You can also view both original and exploded layouts side by side:

plot(result, "both")

Calibration output

calibration_row() returns a one-row data frame suitable for combining across datasets when building a cross-dataset calibration table:

calibration_row(result)
#>            label n_units n_regions w_bar_km R_local_km ratio alpha_r alpha_l
#> 1 Custom Dataset       4         2     1.13        1.5  1.33    1693    2410
#>   gamma_r_implied gamma_l_implied
#> 1               3           1.136

Manual parameter overrides

By default, explodemap derives displacement parameters analytically from dataset geometry using the paper’s two closed-form results. You can also supply parameters directly. Overrides may be supplied independently, so you can tune regional separation without changing local expansion, or vice versa:

manual <- explode_sf(
  x,
  region_col = "region",
  alpha_r = 100,
  alpha_l = 200,
  plot = FALSE
)
#> Using manual alpha_r = 100 m
#> Using manual alpha_l = 200 m

manual$params
#> $alpha_r
#> [1] 100
#> 
#> $alpha_l
#> [1] 200
#> 
#> $p
#> [1] 1.25
#> 
#> $gamma_r
#> [1] NA
#> 
#> $gamma_l
#> [1] NA
#> 
#> $refine
#> [1] FALSE
more_region_gap <- explode_sf(
  x,
  region_col = "region",
  alpha_r = result$params$alpha_r * 1.5,
  plot = FALSE
)
#> Using manual alpha_r = 2538.8531259649 m

more_region_gap$params
#> $alpha_r
#> [1] 2538.853
#> 
#> $alpha_l
#> [1] 2409.82
#> 
#> $p
#> [1] 1.25
#> 
#> $gamma_r
#> [1] NA
#> 
#> $gamma_l
#> [1] 1.136
#> 
#> $refine
#> [1] FALSE

Optional collision refinement

The two-level algorithm is the clean paper model. For dense municipal cores, you can add a bounded refinement pass that nudges close same-region neighbors apart after the analytical displacement:

refined <- explode_sf(
  x,
  region_col = "region",
  refine = TRUE,
  refine_min_gap = 0.15,
  refine_max_shift = 0.10,
  plot = FALSE
)

refined$refinement
#> Collision refinement: corrected 0 pair-visits; max shift = 0.0 m.
#> $enabled
#> [1] TRUE
#> 
#> $min_gap
#> [1] 0.15
#> 
#> $max_shift
#> [1] 0.1
#> 
#> $max_iter
#> [1] 20
#> 
#> $step
#> [1] 0.5
#> 
#> $within
#> [1] "region"
#> 
#> $iterations
#> [1] 1
#> 
#> $corrected_pairs
#> [1] 0
#> 
#> $active_pairs_last
#> [1] 0
#> 
#> $max_shift_observed
#> [1] 0

refine_max_shift caps the extra correction per feature, so the refinement remains a small display adjustment rather than a replacement for the displacement model. Use refine_within = "all" when the remaining crowding crosses region boundaries.

Centroid options

For irregular or multipart polygons, "point_on_surface" may be preferable to the default geometric centroid:

pos <- explode_sf(
  x,
  region_col = "region",
  centroid_fun = "point_on_surface",
  plot = FALSE
)

Using TIGER/Line data for U.S. states

explode_state() downloads U.S. Census TIGER/Line boundaries automatically and groups municipalities by a county-to-region mapping:

nj <- explode_state(
  state_fips = "34", crs = 32118,
  region_map = list(
    North   = c("Bergen", "Essex", "Hudson", "Morris",
                "Passaic", "Sussex", "Union", "Warren"),
    Central = c("Hunterdon", "Mercer", "Middlesex",
                "Monmouth", "Somerset"),
    South   = c("Atlantic", "Burlington", "Camden", "Cape May",
                "Cumberland", "Gloucester", "Ocean", "Salem")
  ),
  label = "New Jersey"
)

Downloaded data is cached locally so subsequent runs are faster.

Use quiet = TRUE in app code if downloads and region assignment happen inside a reactive expression.

Using a lookup table

explode_sf_with_lookup() joins an external lookup table to your sf object before exploding:

groups <- read.csv("region_assignments.csv")

result <- explode_sf_with_lookup(
  my_sf,
  join_col   = "GEOID",
  lookup     = groups,
  lookup_key = "geoid",
  region_col = "region"
)

Unmatched units are labelled "Other". This is useful when a lookup table is incomplete. You can include or exclude those units using allow_other.

Export

The export parameter supports three modes:

result <- explode_sf(
  my_sf,
  region_col = "region",
  export = "output.geojson"
)

Interactive focus maps and Shiny

focus_map() renders raw sf, exploded_map, or grouped_exploded_map objects as an interactive htmlwidget. Click a polygon to zoom and lift it into focus; right-click or press Escape to reset. Information cards can show selected fields without blocking the map.

focus_map(
  result,
  label_col = "id",
  id_col = "id",
  group_col = "region",
  group_palette = c(A = "#4C78A8", B = "#F58518", C = "#54A24B"),
  info_cols = c("id", "region"),
  info_card_scale = 0.95
)

In Shiny, use focusmapOutput() and renderFocusmap(). The widget emits input$<outputId>_selected, which includes the selected feature ID, label, group, and properties.

ui <- shiny::fluidPage(
  focusmapOutput("map", height = "650px"),
  shiny::verbatimTextOutput("selected")
)

server <- function(input, output, session) {
  exploded <- explode_sf(x, "region", plot = FALSE, quiet = TRUE)

  output$map <- renderFocusmap({
    focus_map(
      exploded,
      label_col = "id",
      id_col = "id",
      group_col = "region",
      group_palette = c(A = "#4C78A8", B = "#F58518", C = "#54A24B")
    )
  })

  output$selected <- shiny::renderPrint(input$map_selected)
}

For section drill-downs, use explode_section() before rendering. It explodes the selected section and marks the rest of the layer as context:

focused <- explode_section(
  x,
  section_col = "region",
  section = "A",
  region_col = "county",
  alpha_r = 900,
  alpha_l = 600,
  plot = FALSE,
  quiet = TRUE
)

focus_map(
  focused,
  label_col = "id",
  context_col = ".explodemap_role",
  context_mode = "fade"
)

Small municipal features can use adaptive focus sizing so the selected polygon is not left visually distant after the camera moves:

focus_map(
  focused,
  label_col = "id",
  context_col = ".explodemap_role",
  context_mode = "fade",
  min_focus_width = 115,
  min_focus_height = 88,
  tiny_feature_threshold = 52,
  tiny_feature_boost = 1.45,
  origin_context = "inset",
  origin_context_position = "bottom-left",
  focus_context_opacity = 0.14,
  show_drag_zoom = TRUE
)

Notes

Next steps

See vignette("grouped-layouts") for the three-level extension using explode_grouped(), which adds region-block anchor placement for larger multi-region or national layouts.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.