Designing a Configuration-Driven Map Overlay Generation Pipeline
A case study in evolving a single-purpose rendering script into a spreadsheet-configurable, standards-compliant overlay generation system, covering the architecture decisions, data modeling choices, and interface tradeoffs behind tooling that non-engineers can operate independently.
May 24, 2026
software architecture, python, systems design, data pipelines, tooling, automation

The initial scope was narrow: render a single map overlay from a fixed set of coordinates. What that scope left out became apparent almost immediately, and the system that resulted is a useful case study in how real requirements surface through use rather than specification.

From a Stateless Script to a Data Model
The first version was a stateless script: positions in, a rendered image out. It had no persistence layer and no way to represent state between runs, so every change to a unit's position, every added phase line, meant re-supplying the entire input set and re-running the render from scratch. That constraint forced the actual design question: the problem wasn't rendering, it was data modeling. The fix splits the system into two layers that don't know about each other's internals: an Excel workbook as the persistent data store, and a set of Python scripts that treat that workbook as their only input contract. But the harder problem sat one layer upstream of that: where does the register's data actually come from. The source material is an OPORD, a FRAGORD, and their annexes and task organization, not a clean list of coordinates someone types in. So before any editing happens, those source documents get parsed for the entities a map actually needs: unit designations and their task-organized parent/subordinate relationships, named phase lines and control measures, objective names and grids, decision-point triggers. This isn't generic summarization, it's targeted extraction, cross-referencing multiple source documents for the same named phase line or grid reference to determine which one is authoritative, and explicitly declining to populate a field the order doesn't specify rather than inferring a plausible value. That distinction matters: a wrong grid on a maneuver graphic isn't a cosmetic bug, and the extraction logic treats an unconfirmed field as a flagged gap, not a guess. That's what turns "read this order" into "here's what belongs on this map, in this phase, at this position" instead of requiring someone to manually transcribe forty grid references before the first draft even exists.

Symbol Generation and Coordinate Translation
Symbol generation implements FM 1-02.2 tick-mark conventions as a lookup table: team maps to no tick, squad to a single dot, section to two, platoon to three, company to a bar, battalion to two bars, regiment to three, brigade to an X, division to XX, corps to XXX, with affiliation controlling frame shape and a branch code selecting the interior icon, and reinforced/reduced modifiers composing on top rather than branching into separate code paths. The batch generator reads a units tab through openpyxl with a case-insensitive, header-driven column index, and skips instructional rows using a heuristic caught by inspecting actual output rather than assumed correct. Positioning is where three coordinate systems that don't natively agree had to be reconciled: MGRS grid references from the order, latitude/longitude, and the pixel space the renderer draws in. The mechanism is an affine transform derived from anchor points on the registered base map, two known pixel-to-grid pairs are enough to compute a linear mapping between MGRS easting/northing and pixel X/Y, so a unit's grid from the task organization or graphics annex converts to a screen position without anyone hand-plotting it. That transform was validated empirically, not assumed correct: testing it against four crossing sites with independently known grids and pixel positions showed the math was exact at the two anchor points but seventy to a hundred pixels off at the other two. That error traced back to the original pixel positions having been eyeballed rather than derived from their real grids in the first place, not to a bug in the transform. The fix was a least-squares affine fit across every known point instead of a bare two-point solve, which reconciles all sites to their true geographic relationship instead of privileging whichever two happened to be picked as anchors. Lat/long inputs run through the same pipeline after a standard grid conversion, so whichever coordinate format the source order actually uses becomes a live position source: fill in a grid or a lat/long and pixel position resolves automatically, with manual pixel entry still available as an override. Label placement runs on top of that resolved geometry: an occupancy-tracking engine tests eight fixed candidate offsets around each marker via axis-aligned rectangle intersection, falls back to an expanding radial search when all eight are occupied, and draws a leader line back to the marker whenever a label had to move. On the first fully-loaded phase this resolved thirty-four labels, nineteen requiring displacement, zero pairwise overlaps, verified against the occupancy rectangles directly rather than by inspection.
Designing for a Non-Technical Operator
Because positions and content originate from parsing the actual order rather than manual transcription, the operator's job shifts from typing in forty grid references to reviewing and correcting a pre-populated draft, a meaningfully faster workflow under time pressure and one less place for transcription error to enter the product. The rest of the interface is built around the same principle of removing silent failure modes for a non-engineer: editable and reference-only cells are visually distinguished at the formatting level, an in-workbook README documents the coordinate system directly, and confirmed values are kept visibly distinct from planning estimates, with anything owed but unconfirmed by the source order flagged explicitly rather than backfilled with a plausible-looking placeholder. The named-color fallback and the header-driven column mapping close the same gap from a different angle, removing the two easiest ways to get silently wrong output. The operating loop stays a single command: edit a row, or fix a flagged field, rerun the renderer, get an updated overlay.
System Design in Retrospect
The broader takeaway is that the register, the symbol builder, the geo-transform, and the collision-avoidance renderer weren't planned upfront as a system, they were added sequentially, each one addressing a limitation that only became visible once the previous version was in use against a real order. Treating both document understanding and tooling accessibility as first-class design constraints, not afterthoughts, is what turned a one-off script into something that could be handed off and operated independently.