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.

Four lookups per candidate, whatever its size An ink-density grid with a candidate rectangle drawn on it. Four corner points of the summed-area table are marked A, B, C and D, and the total ink inside the rectangle is given by D minus B minus C plus A. A note records that the cost is constant regardless of the window size, so a large inset is no more expensive to evaluate than a small one. A B C D total = D − B − C + A Four array reads, independent of how many cells the window covers. A naive scan over a 200×200 grid with a 40×40 window costs about 41 million additions per sheet. Building the table costs one pass over the grid, after which every candidate is free. That is what makes exhaustive search affordable rather than clever.
The table is built once per sheet and then every candidate position, at every size, is four reads. This is why an exhaustive search is the simple option here rather than the expensive one.

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.
When the extent indicator is too small to be a rectangle Three inset panels. In the first the parent extent renders as a 6 millimetre rectangle and is drawn true to scale. In the second the parent extent renders at 0.4 millimetres and is invisible against the coastline. In the third a fixed 2.5 millimetre marker is drawn centred on the extent, with a caption noting that the marker is not to scale. true scale, visible 6 mm — draw as-is true scale, invisible 0.4 mm — lost in the linework fixed marker, declared marker not to scale — say so The middle panel is the failure that ships, because the indicator is present in the file and absent from the printed page, so no structural check reports it.
Enlarging the rectangle to make it visible would state an area ten times the truth. A declared marker is honest about being a symbol rather than a measurement.

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.

An inset needs its own style, not a shrunk copy of the main one A comparison of the main style at 1 to 25 000 and the inset style at 1 to 2 500 000. The main style carries fourteen layers and labels down to hamlet level. The inset carries four layers — coastline, national boundary, major river and one thematic fill — and labels only capitals. A note records that reusing the main style at inset scale produces a black rectangle of overlapping linework. main map — 1:25 000 inset — 1:2 500 000 14 layers 4 layers labels to hamlet level capitals only minimum mark 0.4 mm minimum mark 0.4 mm buildings, minor roads omitted entirely The minimum mark size is the one figure that does not change — it is a property of the printed page, not of the scale, and it is what forces every other row to differ. Reusing the main style at inset scale renders a near-solid rectangle of overlapping linework.
The constant across both columns is the minimum mark size on paper. Holding that fixed is precisely what forces the inset to drop ten layers.

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.


Back to Map Layout and Composition Automation