Automating Inset Map Placement and Extent Indicators
Place an inset by scoring candidate rectangles against a measured ink-density grid and choosing the emptiest one, because a hard-coded corner will eventually land on the subject of the map and nothing downstream will notice.
Core Algorithm and Workflow
An inset has two jobs — showing where the main extent sits in a wider context, and doing so without hiding anything the main extent exists to show. The second is a placement problem with a measurable objective: minimise the information covered.
The measurement is a coarse raster of the composed frame. Render the map without labels at roughly a twentieth of the output resolution and treat per-cell ink coverage as a proxy for information density. Open water scores near zero; a dense street network scores high. The best inset position is then the candidate rectangle whose total score is lowest, subject to a few hard constraints.
Evaluating every candidate naively is quadratic in the frame size and cubic in practice once window sizes vary. A summed-area table reduces each candidate to four lookups, which brings the whole search to a few milliseconds even at atlas scale.
Production-Ready Python Implementation
import numpy as np
def place_inset(ink: np.ndarray, frame_w: float, frame_h: float,
inset_w: float, inset_h: float,
forbidden: list | None = None) -> dict:
"""Choose the inset position that obscures the least map information.
ink : coarse (rows, cols) density raster of the composed frame
frame_* : frame size in millimetres; the raster maps linearly onto it
forbidden : list of (x, y, w, h) millimetre rectangles to exclude
"""
rows, cols = ink.shape
cell_w, cell_h = frame_w / cols, frame_h / rows
span_c = max(1, round(inset_w / cell_w))
span_r = max(1, round(inset_h / cell_h))
if span_c > cols or span_r > rows:
raise ValueError("inset does not fit inside the frame")
integral = np.pad(ink.cumsum(0).cumsum(1), ((1, 0), (1, 0)))
def blocked(x: float, y: float) -> bool:
for fx, fy, fw, fh in forbidden or []:
if x < fx + fw and fx < x + inset_w and y < fy + fh and fy < y + inset_h:
return True
return False
best_score, best = None, None
for r in range(rows - span_r + 1):
for c in range(cols - span_c + 1):
x, y = c * cell_w, r * cell_h
if blocked(x, y):
continue
score = float(integral[r + span_r, c + span_c]
- integral[r, c + span_c]
- integral[r + span_r, c]
+ integral[r, c])
if best_score is None or score < best_score:
best_score, best = score, {"x": x, "y": y,
"w": inset_w, "h": inset_h}
if best is None:
raise LookupError("no acceptable inset position on this sheet")
return {**best, "score": best_score}
Raising LookupError rather than falling back to a corner is the important design choice. A sheet with genuinely nowhere to put an inset exists, and the correct responses — move it into the marginalia, shrink it, or omit it and record that — are all decisions for the caller. Silently placing it over the densest part of the map is the only response that produces an unusable sheet.
Performance Tuning and Cartographic Best Practices
- Score on a coarse grid. A 60 × 40 density raster ranks candidates identically to a 600 × 400 one for this purpose, and costs a thousandth as much to render. Omit labels from the density render: they move with the placement decision and would make the objective circular.
- Weight the subject heavily. If the sheet has a named subject — the feature the page exists for — give its cells a large multiplier rather than adding a forbidden rectangle. That way a sheet with no clear space still avoids the subject rather than failing outright.
- Reserve the corners other elements use. Scale bars, north arrows and sheet numbers usually occupy specific corners. Pass them as forbidden rectangles rather than hoping the density score keeps the inset away, because an empty corner is exactly what the scorer will choose.
- Cache the density raster per sheet, not per candidate size. If several inset sizes are tried, build the summed-area table once and query it at each size.
- Enlarge the marker, never the extent. When the parent extent renders below the visible minimum on the inset, draw a fixed-size marker and say so. Enlarging the extent rectangle to make it visible states a false area.
Integration and Next Steps
Inset placement is the one genuinely per-sheet decision in a series, which makes it the natural companion to the frozen publication-wide decisions described in Atlas and Map Series Automation. The forbidden rectangles come from the marginalia allocation in Map Layout and Composition Automation, and the inset’s own style needs its own scale-derived thresholds for the reasons set out in Scale Mapping for Web and Print.
Frequently Asked Questions
Why not just put the inset in a fixed corner?
Because in any series long enough to matter, one sheet has its subject in that corner. A fixed position is fine for a single map that somebody reviewed before it shipped, and it fails silently across a four-hundred-sheet atlas where nobody looks at every page. The scoring approach costs a few milliseconds per sheet and eliminates the failure mode rather than reducing its probability, which is the appropriate standard for an unattended pipeline.
How small can an extent indicator be before it stops working?
Below roughly two millimetres on the page a rectangle reads as a dot: it conveys position but not shape or extent. Below about half a millimetre it disappears into the line weight of the inset’s own coastline. When the parent extent would render smaller than the visible minimum, draw a fixed-size marker centred on it and note in the caption that the marker is not to scale. Enlarging the rectangle instead makes the map assert an area many times larger than the truth, which is a worse error than the one being fixed.
Should the inset use the same style as the main map?
No. An inset is typically ten to a hundred times smaller in scale, so every threshold in the main style — minimum feature size, label priority cut-off, line weight — is wrong for it. Give the inset its own style with a handful of layers and its own scale-derived thresholds. Reusing the main style at inset scale produces a near-solid block of overlapping linework, because features sized for 1:25 000 are drawn a hundred times closer together.
What if no candidate position is acceptable?
That is a genuine outcome on a dense sheet and it should be reported, not resolved by force. The reasonable responses are to move the inset into the marginalia beside the legend, to try a smaller footprint, or to omit the inset on that sheet and record the omission in the run report. Placing it over the subject anyway is the only option that produces a sheet the reader cannot use, and it is the one a silent fallback to a fixed corner effectively chooses.
Related
- Computing Neatline and Margin Geometry for Variable Page Sizes — where the forbidden rectangles come from.
- Generating North Arrows and Graticules Programmatically — the other reference elements competing for the same corners.