A JavaFX implementation of NATO APP-6, Edition E (2023) military symbology - the standard set of symbols used to represent units, equipment, installations and activities on a military map.
Symbols are composed and rendered as ordinary javafx.scene.Nodes, by way of foxglove, so they can be styled, transformed and embedded in a scene graph like any other JavaFX content. The same model also serialises back out to SVG.
APP-6E, not MIL-STD-2525D. The two are related and largely converged, but they are formally distinct documents and differ in detail. This project targets the NATO standard. Its data lineage comes from Esri's
joint-military-symbology-xml(JMSML), which modelled MIL-STD-2525D specifically; that project is no longer maintained, and JMSFX now owns the schema and instance data going forward.
jmsfx.ctg.co.nz - browse the symbol sets and compose icons in the browser, with SVG export. It runs the jmsfx-server module from this repository.
Every icon is drawn inside a standard viewBox="0 0 612 792", and is composed of independently selectable parts, each with a fixed, well-defined position:
- the frame, which carries identity and dimension,
- the main icon, identifying the entity itself,
- two sector modifiers, qualifying it further,
- and a number of amplifiers, varying by symbol set.
Rendering is therefore a matter of choosing the SVG for each part and layering them in a defined order - there is no per-icon layout. IdentificationSymbol models the chosen parts and their state; IdentificationSymbolIcon is the JavaFX Node that draws it.
They line up because that one coordinate space is shared: a main icon and its sector modifiers are built within the bounding octagon, x 183.5 to 426.5 and y 272.5 to 516.5. That is a rule rather than a measurement, which is what lets a symbol's extent be worked out without starting a JavaFX toolkit.
The exception is the Control Measures, and a few Cyberspace path and terrain graphics. APP-6E 8.1.3 exempts them from the composition rules: they are map graphics rather than icons assembled within the octagon, they may use any part of the canvas, and their extent has to be measured. They are marked FREE_CANVAS in the model, and are the only main icons that carry measured bounds.
Each part is one SVG fragment, and what those files have to look like - content roots, the free canvas shape, normalisation, and the two checks bound to the build - is the fragment contract.
APP-6E is deliberately designed so that a consumer can extend the base symbology - adding or removing elements for its own domain. Hand-maintaining the resulting combinations is impractical, so the domain model is generated instead: jmsfx-generator reads a YAML model and emits the Java for jmsfx-standard. Revisions to the standard, and per-consumer extensions, are absorbed by regenerating rather than by editing thousands of classes.
Grouped by deliverable, so that things which share a release sit together:
| Module | What it is |
|---|---|
jmsfx-parent |
Shared build configuration - Java level, encoding, formatter, release profile. Carries no modules, so it can be versioned on its own; the root pom.xml is only an aggregator. |
jmsfx-core |
The symbology API - symbol sets, entities, modifiers, amplifiers, and the rendering model. Everything else pins it, so it moves slowly. |
jmsfx-tools |
Build-time tooling, released as one thing. |
jmsfx-generator |
Generates a library from a YAML model, via FreeMarker templates. |
jmsfx-editor |
Desktop application for editing the YAML model itself, rather than composing icons. |
jmsfx-viewer |
The applications, released as one thing. Neither compiles against a library; which one they ship with is a packaging choice. |
jmsfx-creator |
Desktop application for composing icons and saving them as composite SVG. |
jmsfx-server |
Spring Boot web application exposing the same capability over HTTP - this is what runs the site above. |
library/ |
The generated libraries. A plain directory, not a module: each library tracks its own domain, so these are the lifecycles that should not move together. |
jmsfx-standard |
The generated APP-6E domain model. Not hand-written; see above. |
jmsfx-historical |
An extension library - icons APP-6E dropped, an enlarged Dismounted Individual set, extra amplifiers. |
jmsfx-battleorder |
An extension colouring a unit's frame by branch of service, following Battle Order's palette. Generated from an overlay on the standard model rather than a model of its own. |
The editor sits with the generator rather than with the applications because it edits the model file, which is the generator's input. Nothing in either pom says so - the coupling runs through the YAML.
Published to Maven Central from 2.0.0. Take jmsfx-core for the API and exactly one generated library -
exactly one, because a library registers itself as an IconLibrary service and discovery has to fail
rather than choose when two are on a classpath.
<dependency>
<groupId>io.github.ctgnz</groupId>
<artifactId>jmsfx-core</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>io.github.ctgnz</groupId>
<artifactId>jmsfx-standard</artifactId> <!-- or jmsfx-historical, or jmsfx-battleorder -->
<version>2.0.0</version>
</dependency>A library carries every drawing it can render as generated code, so there is nothing to put on the classpath beyond the jar and nothing read from it at render time.
Writing your own extension library? jmsfx-core's test jar publishes the injected-markup contract that
each library here verifies itself against:
<dependency>
<groupId>io.github.ctgnz</groupId>
<artifactId>jmsfx-core</artifactId>
<version>2.0.0</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>Requires JDK 25 and Maven.
git clone --recurse-submodules https://github.com/ctgnz/jmsfx.git
cd jmsfx
mvn -Pstandard install-Pstandard names the symbology library the two applications ship with. There is deliberately no
default - a build that names none is refused rather than producing an application with nothing to
render with - so every mvn invocation here carries -Pstandard or -Phistorical.
The --recurse-submodules matters: jmsfx-server takes its branding stylesheet from the ctg-brand submodule, and Maven will quietly skip the missing directory rather than fail if it is absent.
To run the web application locally:
mvn -Pstandard -pl :jmsfx-server -am verify
java -jar jmsfx-viewer/jmsfx-server/target/jmsfx-server-*-standard.jarIt serves on port 8080 by default.
Cutting a release is docs/releasing.md - the sequence, and the several ways it has gone wrong.
The web application runs headless, with no display or JavaFX toolkit required - icons are composed and served as SVG. It deliberately does not rasterise: converting SVG to other formats is a job existing tools already do well.
Apache License 2.0 - see LICENSE.md.