Build an AI Agent That Drafts a Terrain Analysis in MilX

Tutorial 15 of 17 · Expert · 60 min

Give an AI agent an area and a mission, and get back a .milxlyz file with obstacles, avenues of approach, key terrain and observation already drawn — a first draft you then check and finish in map.army.

The agent does exactly what the manual terrain analysis does, in the same OAKOC order. Do that tutorial first: you can only review the agent’s draft if you know what a good one looks like.

You need

  • An AI agent that can run tools and write files — for example Claude Code, or an agent you build on an AI API.
  • Python 3 for the file writer below, and access to terrain data for your area (see step 3).
  • The example terrain-analysis-example.milxlyz as a pattern for the agent.
  • An AI service your organisation approves for the data involved.
Hint: Never send classified or personal information to a public AI service. Positions, missions and unit lists leave your organisation the moment you send them. Use an approved, on-premise service where your rules require it.

How the agent works

A MilX document holds layers, each with symbols and tactical graphics, plus metadata.

A language model cannot see the ground. Asked for a terrain analysis from memory, it invents plausible rivers and ridges. An accurate draft therefore needs three parts:

  1. Data tools that fetch real terrain facts for the area: elevation (to find steep slopes), rivers, forests, built-up areas, roads and bridges.
  2. The model that applies the OAKOC rules to those facts and decides what to draw.
  3. A writer that turns the decisions into a valid MilX® file — written by code, not typed by the model, so the format is always right.

map.army is not involved until the end: you load the finished file and review it there.

Step 1: Teach the agent the format

A MilX layer is XML, zipped as Layer.milxly inside a .milxlyz. Each graphic is a MIL-STD-2525C symbol code with its text modifiers, plus a list of points. Give the agent the example file and these rules in its instructions:

  • Every symbol code has exactly 15 characters. The 2nd character is the affiliation (F friend, H hostile), the 4th the status (P present, A anticipated).
  • Points are WGS84: X is the longitude, Y the latitude.
  • Multi-point graphics have a point order: an axis of advance takes the tip first, then the start, then a point behind the tip that sets the width of the arrowhead.
  • Attribute values are validated by the MSS®. A wrong value — 60 where three digits are required — makes the symbol disappear. Let the agent use only the text modifier T unless you verify others.

The full background is on AI with map.army and MilX File Format.

Step 2: Give it a fixed symbol vocabulary

Do not let the model choose symbols freely. Give it this table and tell it to use nothing else:

OAKOC elementSymbol (MIL-STD-2525C)CodePoints
Severely restricted terrain (NO-GO)Obstacle Restricted AreaGFMPOGR--------area, 3 or more
River, ravine, embankmentObstacle LineGFMPOGL--------line, 2 or more
Enemy avenue of approachAxis of Advance, Ground (hostile)GHGPOLAG-------tip, start, width
Key terrainPoint Of InterestGFGPGPRI-------1
Observation postObservation Post / OutpostGFGPDPO--------1
Where an avenue narrowsNamed Area Of InterestGFGPSAN--------area, 3 or more

Every code comes from the list of MIL-STD-2525C symbols. The T text says why: NO-GO slopes over 45 %, K1 bridge La Punt.

Step 3: Feed it real terrain data

Give the agent tools that return facts, not maps. Common open sources:

  • Elevation — a digital elevation model such as Copernicus DEM or your national survey’s model. The tool computes slope classes for the area: for example GO below 30 %, SLOW-GO 30–45 %, NO-GO above 45 % for tracked vehicles. Set the thresholds for your vehicles.
  • Water, forest, built-up areas, roads, bridges — OpenStreetMap through the Overpass API, or your national topographic data.

Then instruct the agent in plain words:

Area of interest: west 9.84, south 46.54, east 10.03, north 46.66 (WGS84).
Mission: defend the valley against an enemy advancing from the north-east.
1. Call the elevation tool and the map-data tool for the area.
2. Mark NO-GO slope areas and linear obstacles (rivers) from the data.
3. Derive the enemy avenues of approach between the obstacles,
   wide enough for a mechanised company.
4. Mark key terrain (bridges, junctions, dominating heights) and one
   observation post per avenue; an NAI where each avenue narrows.
5. Return the graphics as a list for the writer. Use only the symbol
   table you were given. Give a reason in T for every graphic.

Step 4: Write the file with code

Layer-Manager toolbar in map.army under 'Local MilX Layers' with the second icon, Load MilX, circled

The agent passes its list to a small writer, which checks every graphic before it writes anything. This writer produced the example file:

import io, re, zipfile
from xml.sax.saxutils import escape

SIDC = re.compile(r"^[SG][FHNU][A-Z][PA][A-Z-]{11}$")

def check(sidc, points, bbox):
    if not SIDC.match(sidc):
        raise ValueError(f"not a 15-character 2525C code: {sidc}")
    need = 2 if sidc[4:7] == "OGL" else 3 if sidc[4:6] in ("OL", "OG", "SA") else 1
    if len(points) < need:
        raise ValueError(f"{sidc}: needs at least {need} points")
    west, south, east, north = bbox
    for lon, lat in points:
        if not (west <= lon <= east and south <= lat <= north):
            raise ValueError(f"{sidc}: {lon},{lat} is outside the area")

def graphic(sidc, points, attributes):
    symbol = f'<Symbol ID="{sidc}">' + "".join(
        f'<Attribute ID="{k}">{escape(v)}</Attribute>' for k, v in attributes.items()
    ) + "</Symbol>"
    pts = "".join(f"<Point><X>{x:.6f}</X><Y>{y:.6f}</Y></Point>" for x, y in points)
    return (f"<MilXGraphic><MssStringXML>{escape(symbol)}</MssStringXML>"
            f"<PointList>{pts}</PointList></MilXGraphic>")

def write_layer(path, name, graphics, bbox):
    for g in graphics:
        check(g[0], g[1], bbox)
    xml = ('<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
           '<MilXDocument_Layer xmlns="http://gs-soft.com/MilX/V3.1" '
           'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
           "<MssLibraryVersionTag>2025.02.20</MssLibraryVersionTag>"
           f"<MilXLayer><Name>{escape(name)}</Name><LayerType>Normal</LayerType>"
           "<GraphicList>" + "".join(graphic(*g) for g in graphics) + "</GraphicList>"
           "<CoordSystemType>WGS84</CoordSystemType><ViewScale>0.1</ViewScale>"
           "<SymbolSize>1</SymbolSize></MilXLayer></MilXDocument_Layer>")
    buffer = io.BytesIO()
    with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as z:
        z.writestr("Layer.milxly", xml)
    with open(path, "wb") as f:
        f.write(buffer.getvalue())

write_layer("terrain-analysis.milxlyz", "Terrain analysis (AI draft)", [
    ("GFMPOGL--------", [(9.885, 46.552), (9.925, 46.578), (10.0, 46.625)], {"T": "Inn"}),
    ("GHGPOLAG-------", [(9.89, 46.562), (10.01, 46.64), (9.90, 46.574)], {"T": "AA 1"}),
    ("GFGPGPRI-------", [(9.928, 46.582)], {"T": "K1 bridge La Punt"}),
], bbox=(9.84, 46.54, 10.03, 46.66))

Load the result with Load MilX in the Layer-Manager, or publish it on a web server and open it with a ?layer= link.

Hint: A .milxlyz is a ZIP file. Write it in binary mode, and keep it away from tools that convert line endings — a single converted byte makes it unreadable.

Step 5: Check the draft before you trust it

The file loads even when the analysis is wrong. Review it on the map, ideally in the 3D view:

  • Does every NO-GO area really sit on steep ground or water, and is no gap missing between them?
  • Does each avenue of approach run between the obstacles, in the right direction, tip at the objective?
  • Is every key terrain point on the bridge, junction or height its text names?
  • Correct what is wrong in map.army, add what the data cannot know — the weather, the enemy’s equipment — and save the layer as yours.

The agent saves you the drawing, not the judgement. The analysis is ready when a person has checked it.

Next tutorial

Continue with Exchange overlays with C2 systems through NVG to hand the finished analysis to other systems. Reference: AI with map.army, Import Overlays, Load a MilX layer via URL parameter.

Last updated: