Generating Atlas Page Grids with Guaranteed Overlap
Derive the sheet overlap from the longest rendered label in the coverage rather than choosing a round number, because the overlap’s job is to guarantee that a feature crossing a boundary is fully readable on at least one page — and the length of its label is what determines whether that is true.
Core Algorithm and Workflow
A page grid is a lattice of sheet-sized rectangles over the coverage, stepped by less than a full sheet so that neighbours share a band. Three decisions make it correct:
The overlap floor. A feature whose label extends past a sheet edge is clipped there. If the neighbouring sheet’s extent begins exactly where the first ended, the label is clipped on that sheet too, and the feature is named nowhere. The overlap must therefore exceed the longest label’s ground extent — measured, not estimated.
Which cells to keep. Testing each cell against the coverage geometry rather than its bounding box removes the blank pages. On a coastal or irregular coverage this routinely removes a fifth of the volume.
The containment proof. Once the grid exists, assert it: every labelled point feature, together with its label box, must be fully inside at least one sheet. This is a cheap check and it converts the overlap from an assumption into a verified property.
Production-Ready Python Implementation
from shapely.geometry import box
def overlap_floor(max_label_mm: float, scale_denominator: float,
safety: float = 1.5) -> float:
"""Ground-unit overlap guaranteeing the longest label fits on a neighbour."""
if max_label_mm <= 0 or scale_denominator <= 0:
raise ValueError("label extent and scale must be positive")
return (max_label_mm / 1000.0) * scale_denominator * safety
def page_grid(coverage, sheet_w: float, sheet_h: float, overlap: float) -> list:
"""Regular lattice of sheet extents over a coverage polygon.
Cells that do not intersect the coverage are dropped, so a coastal atlas
does not print pages of open sea.
"""
if overlap <= 0:
raise ValueError("overlap must be positive; a zero overlap loses features")
if overlap * 2 >= min(sheet_w, sheet_h):
raise ValueError("overlap consumes the sheet — check the units")
step_x, step_y = sheet_w - overlap, sheet_h - overlap
minx, miny, maxx, maxy = coverage.bounds
cells, y = [], miny
while y < maxy:
x = minx
while x < maxx:
cell = box(x, y, x + sheet_w, y + sheet_h)
if cell.intersects(coverage):
cells.append(cell)
x += step_x
y += step_y
return cells
def assert_containment(cells, labelled_points, label_box_size: float) -> list:
"""Return features that no single sheet fully contains, label included."""
orphans = []
for pt in labelled_points:
halo = pt.buffer(label_box_size / 2, cap_style=3) # square envelope
if not any(cell.contains(halo) for cell in cells):
orphans.append(pt)
return orphans
assert_containment deliberately takes point features only. A river or a motorway cannot be contained by one sheet and does not need to be — a line is continuous across an overlap by construction. Applying the assertion to every geometry type turns a useful check into one that always fails, and a check that always fails is quickly disabled.
Performance Tuning and Cartographic Best Practices
- Measure the label extent with the real font. A character count times an average advance is wrong by a factor of two on strings full of capitals or narrow letters. Use the same font metrics the renderer will use — the approach set out in Typography Rules for Maps.
- Treat the floor as a floor. Overlap costs pages: ten per cent on both axes is roughly twenty per cent more sheets, and every extra sheet is print cost and render time. Exceeding the derived floor buys nothing.
- Prepare the coverage geometry once. The intersection test runs per cell, so simplify the coverage to the sheet scale and build a prepared geometry before the loop; on a detailed national outline this is the difference between seconds and minutes.
- Recompute per scale in a mixed-scale atlas. The overlap floor is a ground distance derived from a page distance at a specific scale. An atlas with 1:25 000 town sheets and 1:100 000 rural sheets needs two floors.
- Emit the page count before rendering. The overlap changes it, and a stakeholder discovering the volume grew from 380 to 460 pages after the render is a much worse conversation than before.
Integration and Next Steps
The grid this produces is the input to sheet numbering and to the neighbour stamps described in Atlas and Map Series Automation, and the extent it assigns each sheet feeds the frame sizing from Map Layout and Composition Automation — which means a change to the legend changes the frame, which changes the sheet ground size, which changes the grid. Compute in that order, and treat the page count as an output of the whole chain rather than something set at the start.
Frequently Asked Questions
Why can the overlap not just be a round number like 5 mm?
Because what has to be guaranteed is a ground distance, and a page distance converts to a different ground distance at every scale. Five millimetres is 125 metres at 1:25 000 and 500 metres at 1:100 000. In a single-scale atlas a page-distance constant is workable once it has been derived from the longest label rather than chosen; in a mixed-scale atlas the ground overlap must be recomputed per scale, or the coarse sheets will drop features the fine sheets keep.
Does more overlap ever hurt?
In two ways. It costs pages — ten per cent on both axes is roughly twenty per cent more sheets, which is print cost, render time and binding thickness. And it increases the number of features appearing on more than one sheet, which increases the work the cross-sheet consistency pass has to do and the number of places an inconsistency can show. The derived floor is a floor; exceeding it does not make containment more guaranteed than guaranteed.
What about features larger than one sheet?
They cannot be contained and the assertion must not treat that as failure. A motorway or a river crosses many sheets by nature, and the property that matters for a linear feature is continuity across the boundary, which any positive overlap provides. Restrict the containment check to labelled point features and small polygons — those are the ones that can genuinely be lost between pages, and the ones the overlap exists to protect.
Should the grid follow the coverage shape or a regular lattice?
A regular lattice with empty cells dropped. A grid that follows the coverage outline produces sheets at irregular positions, which makes neighbour references awkward to compute and page numbering arbitrary to justify. The lattice keeps both trivial — a sheet’s neighbours are its lattice neighbours — and dropping cells that miss the coverage removes the only real cost, which is printing pages of empty sea.
Related
- Building a Map Series Index Sheet with Page Numbers — what the numbered grid becomes.
- Debugging Inconsistent Extents Across Atlas Pages — when the rendered extents do not match the grid.
Back to Atlas and Map Series Automation