Skip to Content
This documentation is provided with the HEAT environment and describes this HEAT instance.

TacticalMap (Next)

Renders tracked entities (crew vehicle, targets) as NATO-style symbols on a static, per-range background image rather than tiled map imagery. Built for ranges that have no internet access at runtime, unlike MapDisplay, which loads tiles from a mapURL.

Dashboard generation

Value
Runner nodedashboard-v2
Frontendui/dashboard (TacticalMapDS -> TacticalMap)
Data envelope$heat-dataservice (contract)

Layout component identifier

TacticalMap: the value of configuration.component in v2 layout rows.

Layout configuration

FieldDescription
component"TacticalMap"
nameInternal name
titleContentPanel title
channelsChannel id(s) bound to this widget, mapdisplay shape
defaultRealmRealm for data lookup
showOpenModalButtonExpand to modal

tacticalMapItem

The background image, its bounds and how turret yaw is read are set per layout in configuration.tacticalMapItem:

KeyRequiredDescription
backgroundImgUrlYesRange background image, for example /env-assets/maps/<site>/v1/range.jpg uploaded to Control Centre scratch public/
rangeBoundsYes{ minLat, minLon, maxLat, maxLon } in WGS84 degrees: the image’s geographic extent, used to project every point
turretYawReferenceNoHow movement[].yaw is expressed. "absolute" (default): a compass bearing, clockwise from north, like heading. "relative": the turret angle relative to the hull, clockwise; the cone is drawn at heading + yaw.

There is no tile URL, zoom or cone styling to set.

Data contract

Envelope

Publish canonical $heat-dataservice with realms, groups, and channels per the dashboard-v2 upstream contract.

Map channel

PropertyValue
shape"mapdisplay"
dataSame movements[] envelope as MapDisplay

TacticalMap reads the same mapdisplay channel shape as MapDisplay, but interprets each movement point differently and requires more fields:

FieldRequiredDescription
movements[].entityConfigYes{ allegiance, domain, unitType }. A movement with no entityConfig is silently dropped, not shown with a generic icon.
movements[].movement[].latitude/longitudeYesReal WGS84 degrees. Projected against the fixed range image bounds, so a session that took place outside that range will not land correctly on the image.
movements[].movement[].healthNo0-100. A target’s health reaching 0 removes its marker from the map (see TacticalMap.tsx’s visibleTargets filter).
movements[].movement[].headingNoCompass degrees, clockwise from north: hull orientation. Drives marker rotation for tracked vehicles.
movements[].movement[].yawNoTurret orientation. A compass bearing by default, or relative to the hull when tacticalMapItem.turretYawReference is "relative". Drives the vision-cone rotation; falls back to heading when absent.
movements[].movement[].engagementTypeNoFor example "fire", "lase" or "kill". Marks that point with a permanent red cross on the map (no caption). Any non-empty value renders the same marker.
movements[].movement[].engagementTargetIdNoWhich target an engagement point targeted, if known.

entityConfig’s allegiance/domain/unitType are the same enums as NatoSymbol . There is no channel-level affiliation source in the underlying capture (protobuf unit_type_data carries no friend/hostile field), so entityConfig has to be assigned upstream, in the arbex/processor script that builds the channel, not inferred by the frontend.

Example channel payload

{ "id": "tactical-map-tracks", "name": "tactical-map-tracks", "groupId": "map", "shape": "mapdisplay", "data": { "movements": [ { "name": "Puma", "entityConfig": { "allegiance": "friend", "domain": "ground", "unitType": "tank" }, "movement": [ { "latitude": "53.4701", "longitude": "8.3850", "heading": "45", "yaw": "60", "health": "100", "capturedDate": "2026-08-17T11:56:40.029Z" } ] }, { "name": "land_zbt_scheibe_58_pzturm_falling (482)", "entityConfig": { "allegiance": "hostile", "domain": "ground", "unitType": "tank" }, "movement": [ { "latitude": "53.4712", "longitude": "8.3862", "heading": "0", "health": "100", "capturedDate": "2026-08-17T11:56:40.029Z" }, { "latitude": "53.4712", "longitude": "8.3862", "heading": "0", "health": "0", "engagementType": "fire", "capturedDate": "2026-08-17T11:58:12.500Z" } ] } ] } }

Behaviour notes

  • Playback-driven, not a live-updating track. TacticalMapDS samples every target’s track at the same fraction of a shared session duration (via useClock and interpolateTrack), throttled to a 100ms cadence. It does not render a raw point-by-point trail; short-lived and long-lived targets are kept in sync against one shared timeline rather than each target’s own span. The reached-path polyline is drawn only for the tracked vehicle (a friend movement with more than one point); other movements get no trail, so a static target does not show a grey dot under its marker.
  • Vision cone. Rendered for any non-hostile entityConfig.allegiance. It points along yaw (or heading + yaw when turretYawReference is "relative"), falling back to heading. On the tracked vehicle the cone starts at the turret, and that point turns with the hull.
  • Popup. Hovering or clicking a marker shows its name, health and Heading (hull, compass degrees). When the point has turret data it also shows Turret, the turret angle relative to the hull, clockwise (0 = straight ahead, 90 = right). The turret angle is worked out from heading and yaw in either reference mode.
  • Tracked-vehicle marker. A friendly target with more than one track point renders as a rotating vehicle sprite (VehicleMarker) instead of a NATO symbol. Everything else (hostile targets, single-point tracks) renders as a NatoSymbol.
  • No external tiles, no internet dependency. The background is a bundled static image (public/assets/tactical-map/rme-range-map.png), matching the platform’s air-gapped runtime requirement.
Opens your browser's print dialog. Choose "Save as PDF" as the destination.