Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/spatialillusions/milsymbol/llms.txt

Use this file to discover all available pages before exploring further.

Cesium renders a 3D globe where geographic positions are expressed as longitude, latitude, and optional altitude. Point features are displayed using the billboard property of an entity or through a BillboardCollection. Both accept an image that can be a URL or Base64 data URL — exactly what milsymbol’s toDataURL() provides.
The milsymbol repository does not include a Cesium example file. The patterns on this page are general integration guidance based on milsymbol’s output API and Cesium’s billboard API. Refer to the Cesium documentation for the most current Cesium API details.

Correcting the anchor with pixelOffset

Cesium billboards are centered on their position by default — the image center sits on the cartographic coordinate. milsymbol’s getAnchor() returns the pixel offset from the top-left of the image to the geographic anchor point. To make that anchor point fall on the Cesium position you need to shift the billboard so that the anchor aligns with the origin. Cesium’s pixelOffset uses screen-space coordinates where positive Y is up. The formula to convert milsymbol’s top-left-relative anchor into a Cesium pixel offset is:
var symbol = new ms.Symbol("SFG*UCI---*****", { size: 30 });

var anchor = symbol.getAnchor(); // { x, y } from top-left
var size   = symbol.getSize();   // { width, height }

// Shift the image so the anchor pixel lands on the Cesium position:
//   offsetX: negative moves image left (anchor was right of center)
//   offsetY: negative moves image down in Cesium (anchor was below center)
var offsetX = -(anchor.x - size.width  / 2);
var offsetY = -(anchor.y - size.height / 2);

viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(16.0, 59.0),
  billboard: {
    image: symbol.toDataURL(),
    pixelOffset: new Cesium.Cartesian2(offsetX, offsetY)
  }
});

Basic entity billboard

The simplest way to add a milsymbol symbol to Cesium is through viewer.entities.add with a billboard property:
var symbol = new ms.Symbol("SFG*UCI---*****", {
  size: 20,
  square: true   // keeps consistent bounding boxes across symbols
});

viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(16.0, 59.0),
  billboard: {
    image: symbol.toDataURL()
  }
});

Adding multiple units from GeoJSON

This illustrative pattern iterates over GeoJSON features and creates a Cesium entity for each one, applying anchor correction:
// Utility: create a Cesium entity for a GeoJSON feature
function addUnitEntity(feature) {
  var unit = new ms.Symbol(feature.properties.SIDC, {
    size: 20,
    square: true
  });

  var anchor  = unit.getAnchor();
  var size    = unit.getSize();
  var offsetX = -(anchor.x - size.width  / 2);
  var offsetY = -(anchor.y - size.height / 2);

  viewer.entities.add({
    name: feature.properties.name,
    position: Cesium.Cartesian3.fromDegrees(
      feature.geometry.coordinates[0],
      feature.geometry.coordinates[1]
    ),
    billboard: {
      image: unit.toDataURL(),
      pixelOffset: new Cesium.Cartesian2(offsetX, offsetY)
    }
  });
}

// Add all features from a GeoJSON situation object
situationData.features.forEach(addUnitEntity);

Using BillboardCollection for large datasets

When placing hundreds or thousands of symbols, Cesium.BillboardCollection is more efficient than individual entities:
var billboards = viewer.scene.primitives.add(
  new Cesium.BillboardCollection()
);

situationData.features.forEach(function (feature) {
  var symbol  = new ms.Symbol(feature.properties.SIDC, { size: 20, square: true });
  var anchor  = symbol.getAnchor();
  var size    = symbol.getSize();

  billboards.add({
    position: Cesium.Cartesian3.fromDegrees(
      feature.geometry.coordinates[0],
      feature.geometry.coordinates[1]
    ),
    image: symbol.toDataURL(),
    pixelOffset: new Cesium.Cartesian2(
      -(anchor.x - size.width  / 2),
      -(anchor.y - size.height / 2)
    )
  });
});

High-DPI PNG output

For sharper symbols on retina displays, render the milsymbol canvas at a higher resolution and convert it to a data URL directly from the canvas object:
var ratio  = window.devicePixelRatio || 1;
var symbol = new ms.Symbol("SFG*UCI---*****", { size: 20 * ratio });

// asCanvas() returns an HTMLCanvasElement; call its own toDataURL()
var hiDpiURL = symbol.asCanvas().toDataURL();

viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(16.0, 59.0),
  billboard: {
    image: hiDpiURL,
    scale: 1 / ratio   // scale back down in screen space
  }
});
For symbols that will be shown at a fixed screen size regardless of camera distance, set sizeInMeters: false (the Cesium default) and use scale to control the display size. Use scaleByDistance with a NearFarScalar to make symbols shrink as the camera pulls back from the globe.
The square: true option in milsymbol forces all symbols to the same bounding box dimensions regardless of echelon or text fields. This simplifies anchor calculations for collections where you want uniform sizing but loses the echelon-proportional sizing that the standard recommends.

Build docs developers (and LLMs) love