🚧 These docs are a work in progress — content is incomplete and may change without notice.
Skip to content

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 ​

sh
pnpm add @orbat-mapper/tactical-map-sheet
sh
npm install @orbat-mapper/tactical-map-sheet

The package depends on @orbat-mapper/control-measures and @orbat-mapper/tactical-draw for rendering, but not on a map engine.

Create a sheet ​

ts
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 ​

FieldRequiredDescription
pageyeswidthMm, heightMm, and optional marginMm, orientation, and CSS background (default transparent).
centeryes[longitude, latitude] of the sheet center.
scaleDenominatoryesNominal 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 }.
gridMGRS grid options, or omit / false for none.
layersOrdered layers of graphics. See Layers.
pointSymbolsRenderer for unit symbols. See Point symbols.
marginaliaMarginal information options, or omit / false for none.
metadatatitle, subtitle, classification, and preparedBy text for the marginalia.
rasterBasemapImagery beneath the grid. See Raster basemaps.
registrationMGRS registration marks for a transparent overlay. See Registered overlays.
printerCalibrationA 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:

ts
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:

ts
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 ControlMeasure from @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:

ts
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:

FieldDescription
svgThe complete SVG document.
footprintThe covered area as a densified GeoJSON polygon.
warningsOrdered, structured warnings (see below).
metadataThe resolved page, frame, scale, CRS and EPSG code, grid interval, and scale distortion.
resourcesIdentities of the embedded fonts and fill patterns.
registrationMarksFor 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 codeMeaning
cross-zone-footprintThe sheet reaches into another UTM zone. Lists the zones.
cross-projection-footprintThe sheet crosses the UTM/UPS boundary.
scale-deviationScale at the frame edge differs from the center by 0.1% or more.
graphic-clippedA graphic extends past the map frame and is clipped.
graphic-outside-frameA graphic lies entirely outside the map frame.
graphic-hiddenA graphic renders nothing at this scale, for example a label above its hide threshold.
point-symbol-fallbackThe renderer returned fallback artwork, typically for an unrecognized symbol code.
low-raster-dpiA basemap tile is below 150 DPI.
raster-dpi-anisotropyA 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:

ts
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.