Map sheets ​
@orbat-mapper/tactical-map-sheet prints tactical graphics on paper at a true scale. Give it a page size, a center, a scale such as 1:25,000, and your graphics. It returns a deterministic SVG (or PDF) with an MGRS grid, marginalia, and every control measure and symbol kept as vector artwork.
Sheets are grid-north-up on a UTM or UPS projection, so distances measured with a ruler on the printout match the ground. The package does no network or file I/O and does not depend on a map engine: it runs in the browser or in Node.
Install ​
pnpm add @orbat-mapper/tactical-map-sheetnpm install @orbat-mapper/tactical-map-sheetThe package depends on @orbat-mapper/control-measures and @orbat-mapper/tactical-draw for rendering, but not on a map engine.
Create a sheet ​
import { createTacticalMapSheet } from "@orbat-mapper/tactical-map-sheet";
const { svg, warnings, footprint, metadata } = createTacticalMapSheet({
page: { widthMm: 297, heightMm: 210, marginMm: 10 }, // A4 landscape
center: [-0.866, 49.37],
scaleDenominator: 25_000,
grid: {}, // MGRS grid with an automatic interval
marginalia: {}, // scale, scale bar, grid and north data
metadata: { title: "OPORD 12-3 overlay", classification: "UNCLASSIFIED" },
layers: [
{
id: "friendly",
graphics: measures.map((graphic) => ({ graphic })),
},
],
});
download(new Blob([svg], { type: "image/svg+xml" }), "sheet.svg");The sheet covers whatever area fits in the map frame at that scale. You do not pass an extent: footprint returns the covered area as a GeoJSON polygon, so you can preview it on your map before exporting.
Identical requests always produce byte-identical output, independent of the browser, viewport, or device pixel ratio.
The request ​
| Field | Required | Description |
|---|---|---|
page | yes | widthMm, heightMm, and optional marginMm, orientation, and CSS background (default transparent). |
center | yes | [longitude, latitude] of the sheet center. |
scaleDenominator | yes | Nominal true-ground scale at the center, for example 50_000 for 1:50,000. |
mapFrame | { xMm, yMm, widthMm, heightMm } for the map area on the page. Use this or page.marginMm, not both. | |
projection | "auto" (default), { kind: "utm", zone, hemisphere }, or { kind: "ups", hemisphere }. | |
grid | MGRS grid options, or omit / false for none. | |
layers | Ordered layers of graphics. See Layers. | |
pointSymbols | Renderer for unit symbols. See Point symbols. | |
marginalia | Marginal information options, or omit / false for none. | |
metadata | title, subtitle, classification, and preparedBy text for the marginalia. | |
rasterBasemap | Imagery beneath the grid. See Raster basemaps. | |
registration | MGRS registration marks for a transparent overlay. See Registered overlays. | |
printerCalibration | A calibration line to check print scale. See Registered overlays. |
Paper sizes ​
resolvePaperSize(name, orientation?) returns { widthMm, heightMm } for a named paper size, ready to spread into page:
import { resolvePaperSize } from "@orbat-mapper/tactical-map-sheet";
const page = { ...resolvePaperSize("a3", "landscape"), marginMm: 12 };Available names are a0 to a4, letter, legal, tabloid, ledger, ansi-a to ansi-e, and arch-d. Portrait is the default. Any positive widthMm and heightMm also work; the presets are only a convenience.
Projection ​
With projection: "auto", the sheet uses the UTM zone (or polar UPS zone) that contains the center. It keeps that one projection for the whole sheet, even when the sheet reaches into a neighboring zone, and reports the crossing as a warning. Pass an explicit UTM zone to match a paper map series.
Grid ​
grid: {} draws an MGRS grid with the smallest standard interval whose lines are at least 10 mm apart. Set intervalMeters to 100, 1,000, 10,000, or 100,000 to choose one. You can also set color, lineWidthCssPixels, and labelSizeCssPixels.
Marginalia ​
marginalia: {} shows the declared scale, a scale bar, grid and north information (including the MGRS grid zone designation), frame corner marks, a pair of 10 mm calibration marks, and a footprint label, plus any metadata text. Turn items off individually, for example { showScaleBar: false, showFootprint: false }, and set color to change the marginalia color.
Layers ​
layers paint bottom to top in array order. Each layer has a stable id, a list of { graphic } entries, and optional shared portrayal defaults:
const layers = [
{
id: "boundaries",
graphics: boundaries.map((graphic) => ({ graphic })),
portrayal: { strokeWidthCssPixels: 1.5, symbolColor: "#000000" },
},
{
id: "units",
graphics: units.map((graphic) => ({ graphic })),
},
];A graphic can be:
- a
ControlMeasurefrom@orbat-mapper/control-measures, - a point symbol (
kind: "point-symbol"), or - a custom vector symbol (
kind: "custom-symbol") that carries its own SVG.
TacticalDraw point symbols work as they are, including offsets and connector styling. Images are not supported; use a raster basemap for imagery.
Layer portrayal sets defaults for its graphics: symbolColor, strokeWidthCssPixels, strokeDashCssPixels, lineCap, lineJoin, labelHeightCssPixels, and labelSizeClampCssPixels. Styles on each graphic still take priority.
The sheet never modifies your graphics.
Sizes on paper ​
A CSS pixel is 1/96 inch (about 0.26 mm) on the sheet. Pixel-sized strokes, symbols, and labels print at that fixed physical size. Meter-sized symbols and labels print at their true ground size for the sheet's scale, held within the labelSizeClampCssPixels band (8–24 px by default). Unlike the map, the sheet never hides a label that is too small: a fixed-scale print pins it to the band's minimum.
Point symbols ​
To print point symbols, pass a renderer as pointSymbols. The renderPointSymbol function from @orbat-mapper/point-symbols (installed with its milsymbol peer) already has the right shape:
import { renderPointSymbol } from "@orbat-mapper/point-symbols";
createTacticalMapSheet({
...request,
pointSymbols: { render: renderPointSymbol },
});A custom renderer must return a ControlledVectorSymbol: a stable resource ID, the SVG, its size, and its anchors. The SVG must stay within a simple vector subset. Raster, linked, scripted, or malformed SVG throws rather than being dropped or rasterized.
Results and warnings ​
createTacticalMapSheet returns:
| Field | Description |
|---|---|
svg | The complete SVG document. |
footprint | The covered area as a densified GeoJSON polygon. |
warnings | Ordered, structured warnings (see below). |
metadata | The resolved page, frame, scale, CRS and EPSG code, grid interval, and scale distortion. |
resources | Identities of the embedded fonts and fill patterns. |
registrationMarks | For registered overlays, the resolved marks and their positions on the sheet. |
The function throws when it cannot honor the request: invalid coordinates, a page geometry that does not fit, a missing point-symbol renderer, or unsupported SVG. It warns when the result is usable but not exact. It never silently omits requested content.
| Warning code | Meaning |
|---|---|
cross-zone-footprint | The sheet reaches into another UTM zone. Lists the zones. |
cross-projection-footprint | The sheet crosses the UTM/UPS boundary. |
scale-deviation | Scale at the frame edge differs from the center by 0.1% or more. |
graphic-clipped | A graphic extends past the map frame and is clipped. |
graphic-outside-frame | A graphic lies entirely outside the map frame. |
graphic-hidden | A graphic renders nothing at this scale, for example a label above its hide threshold. |
point-symbol-fallback | The renderer returned fallback artwork, typically for an unrecognized symbol code. |
low-raster-dpi | A basemap tile is below 150 DPI. |
raster-dpi-anisotropy | A basemap tile's horizontal and vertical DPI differ by more than 1%. |
Graphic warnings carry layerId and graphicId, so you can point the user at the affected graphic.
PDF output ​
The PDF writer is a separate entry point, so SVG-only apps never load it:
import { renderMapSheetPdf } from "@orbat-mapper/tactical-map-sheet/pdf";
const { pdf, warnings } = renderMapSheetPdf(request); // same request as for SVG
download(new Blob([pdf], { type: "application/pdf" }), "sheet.pdf");It takes the same request and returns the same fields as the SVG result, with pdf (a Uint8Array) in place of svg. Text is embedded as searchable Open Sans, and identical requests produce identical bytes.
What your app is responsible for ​
The package produces the document. Your app chooses what to include, shows the footprint and any warnings or errors to the user, and saves the bytes.