Choosing Export DPI from Minimum Mark Size
Find the narrowest mark the style actually draws, require at least two device dots across it, and take the resulting resolution — because 300 dpi is the correct answer for a mid-range product and both wasteful and insufficient at the two ends of the range.
Core Algorithm and Workflow
The requirement is not “how sharp should this be” but “what is the smallest thing this map draws, and how many dots does it need”. Two dots across the narrowest mark is the practical floor: one dot renders a mark that appears and disappears depending on where it falls on the grid, and three offers no visible improvement over two on a solid edge.
So the derivation is:
- Scan the style for the narrowest mark. Thinnest stroke, smallest halo radius, narrowest dash gap, and the stem width of the smallest type. The last two are the ones usually missed, and they are typically narrower than any line deliberately specified.
- Require two dots across it. Required DPI = 2 / (mark in mm) × 25.4.
- Cap at the device. No output benefit exists above the imagesetter or printer resolution.
- Sanity-check against viewing distance. A design read from two metres should not contain sub-half-millimetre marks in the first place.
Production-Ready Python Implementation
MM_PER_INCH = 25.4
DEVICE_CEILING_DPI = {"offset_imagesetter": 2400, "large_format_inkjet": 720,
"office_laser": 600, "screen": 96}
def required_dpi(narrowest_mark_mm: float, dots_across: int = 2) -> float:
"""Resolution needed to render the narrowest mark with `dots_across` dots."""
if narrowest_mark_mm <= 0:
raise ValueError("narrowest mark must be positive")
return dots_across * MM_PER_INCH / narrowest_mark_mm
def choose_export_dpi(style_marks_mm: dict, device: str,
ladder=(96, 150, 200, 300, 400, 600, 1200)) -> dict:
"""Pick an export resolution from the design and the output device.
`style_marks_mm` maps a description to a physical size — thinnest stroke,
smallest halo, narrowest dash gap, smallest type stem.
"""
if not style_marks_mm:
raise ValueError("no marks supplied; the design has to state its own limits")
if device not in DEVICE_CEILING_DPI:
raise KeyError(f"unknown device {device!r}")
name, mark = min(style_marks_mm.items(), key=lambda kv: kv[1])
needed = required_dpi(mark)
ceiling = DEVICE_CEILING_DPI[device]
usable = [d for d in ladder if d >= needed and d <= ceiling]
chosen = usable[0] if usable else min(max(ladder[0], min(ceiling, needed)), ceiling)
return {"driver": name, "narrowest_mark_mm": mark,
"required_dpi": round(needed), "device_ceiling": ceiling,
"chosen_dpi": int(chosen),
"capped": needed > ceiling}
Returning driver — the name of the mark that set the requirement — is what makes the result actionable. “600 dpi because the dash gap on the footpath style is 0.08 mm” invites a conversation about whether that dash pattern is worth quadrupling the render cost; a bare number does not.
The capped flag matters too: when the design asks for more than the device can deliver, the correct response is to widen the mark, not to export at an impossible resolution and hope.
Performance Tuning and Cartographic Best Practices
- Include dash gaps and type stems in the scan. Both are routinely narrower than any deliberately specified line, and both are what actually breaks up on press.
- Remember the memory cost. Doubling DPI quadruples the pixel count and therefore peak rasterisation memory, which is what sets worker concurrency — see Parallelizing Map Sheet Rendering with Celery Workers.
- Widen the mark rather than raising the resolution. A 0.08 mm hairline that forces 600 dpi across a 400-sheet atlas is an expensive detail; 0.12 mm is visually equivalent at reading distance and halves the job.
- Cap at the device. Exporting above the imagesetter resolution produces larger files and identical print.
- Re-derive when the style changes. A new layer with a finer hairline silently raises the requirement, which is why the narrowest mark belongs in the style manifest rather than in an operator’s memory.
Integration and Next Steps
The narrowest mark is the same figure that drives feature thresholds in Choosing Minimum Feature Size Thresholds by Output Medium, so a style that records it gets both its resolution and its generalisation tolerance for free. Feed the chosen DPI into the export settings validated in DPI and Resolution Management, and assert the delivered pixel dimensions afterwards rather than trusting the setting.
Frequently Asked Questions
Why not simply always use 300 dpi?
Because it is right for one common case and wrong in both directions elsewhere. A large-format wall map read from two metres is visually identical at 150 and renders four times faster with a quarter of the memory; a hydrographic chart with 0.08 millimetre hairlines loses them at 300 and needs 600. Three hundred is a sensible default because most products sit in the middle of the range, not because it satisfies a requirement — and the products at the ends are exactly the ones where the cost or the defect is largest.
What counts as the narrowest mark?
The smallest dimension anything in the style is drawn at: the thinnest stroke width, the smallest halo radius, the narrowest gap in a dash pattern, and the stem width of the smallest type. The last two are the ones usually forgotten and they are typically narrower than any line that was consciously specified — a 0.2 millimetre dashed line often has a 0.1 millimetre gap, and 6 point type has stems finer than that.
Does higher DPI ever make a map worse?
Not in appearance, but the second-order costs are real. Doubling resolution quadruples pixel count, and therefore peak rasterisation memory, which is the quantity that sets how many render workers a host can run. A job that fitted at 300 and does not fit at 600 does not render more slowly — it fails, or forces the pool down to one worker. Storage and transfer for raster deliverables scale the same way.
How does viewing distance enter the calculation?
Through the mark size rather than directly. A mark that must be legible from two metres has to be physically larger, and once the design reflects that the resolution requirement follows from the same two-dots rule. In practice a wall map contains nothing below about half a millimetre, which puts the requirement near 100 dpi and means anything above about 150 buys nothing a reader at that distance could perceive.
Related
- Choosing Minimum Feature Size Thresholds by Output Medium — the same figure applied to generalisation.
- Debugging DPI Mismatch in Headless Matplotlib Exports — making the chosen resolution actually reach the file.
Back to DPI and Resolution Management