You are displaying the symbols defined in Military Symbology standards MIL-STD 2525 or APP-6 in your LuciadLightspeed application. Now, to fulfill specific requirements of your project, you need to add custom control measure symbols to the standard symbols.

Why do it?

LuciadLightspeed provides military symbols that comply with the MIL-STD 2525B/C and APP-6A/B/C standards, but you may need to extend those standards to add your own symbology set.

How does it work?

There are two ways you could add a custom Control Measure symbol to your symbology. You could use the styling of an existing symbol, but applied to a different geometry or you could create a completely custom symbol with its own styling and geometry. Both of these can be achieved by creating a custom ALspStyler which can submit default military TLspAPP6ASymbolStyle or TLspMS2525bSymbolStyle to custom geometries or submit a completely custom ALspStyle.

Since your custom symbols would likely have a SIDC code which is not defined in a supported military standard, you would need to define your own custom military domain object. This is required because the standard TLcdEditableAPP6AObject or TLcdEditableMS2525bObject only allow creation of symbols with a SIDC defined in their respective symbology standards.

How to do it?

To illustrate how to add custom decorations to military symbols, we are going to show how to configure a military layer with a military symbol styled according to the MIL-STD-2525 B specification, but with a custom geometry and another military object with completely custom styling. We will represent these military objects with our own custom domain object called MyMilitaryObject.

Create a custom domain object

We will create a simple MyMilitaryObject class with three properties. A symbology code, to represent the SIDC, a title, which we will use as label for the object and a ILcdShape to represent the object’s geometry.

It is required to make you domain objects implement ILcdBounded if you want them to be visualized in a Lightspeed view.

Program: MyMilitaryObject class
  /**
   * Simple custom domain object that contains a symbology code,
   * a title and a {@link ILcdShape}
   */
  private static class MyMilitaryObject implements ILcdBounded {

    private final String fSymbologyCode;

    private final String fTitle;

    private final ILcdShape fShape;

    public MyMilitaryObject(String aSymbologyCode, String aName, ILcdShape aShape) {
      fSymbologyCode = aSymbologyCode;
      fTitle = aName;
      fShape = aShape;
    }

    public String getSymbologyCode() {
      return fSymbologyCode;
    }

    public ILcdShape getShape() {
      return fShape;
    }

    public String getTitle() {
      return fTitle;
    }

    @Override
    public ILcdBounds getBounds() {
      return fShape.getBounds();
    }

  }

Create a custom ALspStyler

To implement the desired behavior it is necessary to create custom styler which extends from ALspStyler. This styler will be called MySymbolStyler.

For brevity we will make MySymbolStyler perform both the symbol and label styling. In you own application it is recommended that you perform the label styling in a separate custom ALspLabelStyler.

In our MySymbolStyler.style(Collection<?> aObject, ALspLabelStyleCollector, TLspContext) method, we will loop over the aObjects collection and style all instances of MyMilitaryObject. We check whether the military object’s symbology code is a standard MIL-STD-2525 B SIDC code or not, and perform either the standard MIL-STD-2525 B styling or our custom styling in the methods styleStandardSymbol(MyMilitaryObject, ALspStyleCollector) and styleCustomSymbol(MyMilitaryObject aSymbol, ALspStyleCollector aStyleCollector) respectively.

Program: MySymbolStyler’s style() method
    @Override
    public void style(Collection<?> aObjects,
                      ALspStyleCollector aStyleCollector,
                      TLspContext aContext) {
      // iterate over the objects,
      // determine if the symbol's symbology code is a standard SIDC or not
      // style standard symbols according to the MIL-STD-2525 B specification
      // and custom symbols according to our custom styling
      for (Object object : aObjects) {
        if (object instanceof MyMilitaryObject) {
          MyMilitaryObject symbol = (MyMilitaryObject) object;
          if (isStandardSymbol(symbol)) {
            styleStandardSymbol(symbol, aStyleCollector);
          } else {
            styleCustomSymbol(symbol, aStyleCollector);
          }
        }
      }
    }

To check whether the symbology code is a valid MIL-STD-2525 B SIDC, we simply try to create a TLcdEditableMS2525bObject with the military object’s symbology code. If the constructor of TLcdEditableMS2525bObject throws an IllegalArgumentException it means the given symbology code is not valid for the given ELcdMS2525Standard.

Program: MySymbolStyler’s isStandardSymbol(MyMilitaryObject) method
    private boolean isStandardSymbol(MyMilitaryObject aSymbol) {
      try {
        // try to create a new TLcdEditableMS2525bObject with the symbols symbology code
        new TLcdEditableMS2525bObject(aSymbol.getSymbologyCode(),
                                      ELcdMS2525Standard.MIL_STD_2525d);
        return true;
      } catch (IllegalArgumentException ex) {
        // The constructor of TLcdEditableMS2525bObject will throw an IllegalArgumentException
        // if the symbol code is not part of the MIL-STD-25258 B specification
        return false;
      }
    }

The styleStandardSymbol(MyMilitaryObject, ALspStyleCollector) create and submits a TLspMS2525bSymbolStyle, which will add the default MIL-STD-2525 B styling. If the ALspStyleCollector is a ALspLabelStyleCollector, this will perform the default MIL-STD-2525 B styling.

When we pass a non-standard geometry to the ALspLabelStyleCollector.geometry method, this will cause the MIL-STD-2525 B styling to be applied to that (non-standard) geometry instead of the standard one. If you want the styling to be applied to a standard geometry, you need to pass an instance of TLcdEditableMS2525bObject to both the ALspLabelStyleCollector.object and ALspLabelStyleCollector.geometry methods.

Program: MySymbolStyler’s styleStandardSymbol(MyMilitaryObject, ALspStyleCollector) method
    private void styleStandardSymbol(MyMilitaryObject aSymbol,
                                     ALspStyleCollector aStyleCollector) {
      // Create a TLcdEditableMS2525bObject based on the military symbol's symbology Code
      TLcdEditableMS2525bObject standardSymbol
          = new TLcdEditableMS2525bObject(aSymbol.getSymbologyCode(),
                                          ELcdMS2525Standard.MIL_STD_2525d);

      // set the unique designation modifier
      standardSymbol.putTextModifier(ILcdMS2525bCoded.sUniqueDesignation, aSymbol.getTitle());

      // create a default ILcdMS2525bStyle and adjust some style settings
      TLcdDefaultMS2525bStyle style = TLcdDefaultMS2525bStyle.getNewInstance();
      style.setLabelFont(Font.decode("Dialog-12"));
      style.setLabelColor(Color.WHITE);
      style.setLabelHaloEnabled(true);
      style.setLabelHaloColor(Color.BLACK);
      style.setAffiliationColorEnabled(true);

      // then create default symbology ALspStyle for the given military symbol and ILcdMS2525bStyle
      TLspMS2525bSymbolStyle symbologyStyle
          = TLspMS2525bSymbolStyle.newBuilder()
                                  .ms2525bCoded(standardSymbol)
                                  .ms2525bStyle(style)
                                  .build();

      // finally submit the style
      aStyleCollector.object(aSymbol)
                     .geometry(aSymbol.getShape())
                     .style(symbologyStyle)
                     .submit();
    }

The styleSCustomSymbol(MyMilitaryObject, ALspStyleCollector) method only does a check to see whether or not the ALspStyleCollector is an instance of ALspLabelStyleCollector. If it is, the styling is delegated to styleCustomSymbolLabel(MyMilitaryObject, ALspLabelStyleCollector), which performs the label styling, it is not the styling is delegated to styleCustomSymbolBody(MyMilitaryObject, ALspStyleCollector), which performs the symbol styling.

Program: MySymbolStyler’s styleCustomSymbolBody(MyMilitaryObject, ALspStyleCollector) method
    private void styleCustomSymbol(MyMilitaryObject aSymbol,
                                   ALspStyleCollector aStyleCollector) {
      if (aStyleCollector instanceof ALspLabelStyleCollector) {
        // If the style collector is a ALspLabelStyleCollector,
        // we need to perform the custom label styling
        styleCustomSymbolLabel(aSymbol, (ALspLabelStyleCollector) aStyleCollector);
      } else {
        // otherwise we need to perform the symbol styling
        styleCustomSymbolBody(aSymbol, aStyleCollector);
      }
    }

The styleCustomSymbolLabel(MyMilitaryObject, ALspLabelStyleCollector) and styleCustomSymbolBody(MyMilitaryObject, ALspStyleCollector) methods just perform regular Lighstpeed styling and no longer use any of the military symbology apis. styleCustomSymbolLabel(MyMilitaryObject, ALspLabelStyleCollector) creates a basic FixedTextProviderStyle with the military symbol’s title and adds some text style to it. styleCustomSymbolLabel(MyMilitaryObject, ALspLabelStyleCollector) creates a complex stroke style which it applies to the military symbol’s geometry.

More information about Lightpeed labeling can be found in the vector labeling tutorial. More information about complex strokes can be found in the complex stroking guide.

Note that we style all custom symbols the same way in our MySymbolStyler. In your actual application you might want to have different styles for different symbols. You custom styler should then check you military symbol’s symbology code to determine which styling to apply. You might want to delegate this behaviour to a delegate ALspStyler, but the general structure of your MySymbolStyler should remain the same.

Program: MySymbolStyler’s styleStandardSymbol(MyMilitaryObject, ALspStyleCollector) method
    private void styleCustomSymbolLabel(MyMilitaryObject aSymbol,
                                        ALspLabelStyleCollector aStyleCollector) {
      // create a label context style that displays the military symbol's title
      ALspStyle labelContentStyle = FixedTextProviderStyle.newBuilder()
                                                          .text(aSymbol.getTitle())
                                                          .build();

      // create a text style to adjust some font settings
      ALspStyle textStyle = TLspTextStyle.newBuilder()
                                         .font(Font.decode("Dialog-12"))
                                         .textColor(Color.WHITE)
                                         .haloColor(Color.BLACK)
                                         .build();

      // submit the styles
      aStyleCollector.object(aSymbol)
                     .geometry(aSymbol.getShape())
                     .styles(labelContentStyle, textStyle)
                     // use TLspOnPathLabelingAlgorithm to display the label on the symbol body
                     .algorithm(new TLspOnPathLabelingAlgorithm())
                     .submit();

    }
Program: MySymbolStyler’s styleStandardSymbol(MyMilitaryObject, ALspStyleCollector) method
    private void styleCustomSymbolBody(MyMilitaryObject aSymbol,
                                       ALspStyleCollector aStyleCollector) {
      // Create a complex stroke style
      TLspComplexStrokedLineStyle stokeStyle = getDashDotArrowStyle(Color.GREEN);

      // submit the style
      aStyleCollector.object(aSymbol)
                     .geometry(aSymbol.getShape())
                     .style(stokeStyle)
                     .submit();
    }

    // creates a complex stroke style with a dash-dot-arrow pattern: -.>-.>-.>
    private TLspComplexStrokedLineStyle getDashDotArrowStyle(Color aColor) {
      ALspComplexStroke arrow = ALspComplexStroke.arrow()
                                                 .size(12)
                                                 .lineColor(aColor)
                                                 .lineWidth(2)
                                                 .build();

      ALspComplexStroke dashdot = ALspComplexStroke.append(
          ALspComplexStroke.line()
                           .length(10)
                           .lineColor(aColor)
                           .lineWidth(2)
                           .build(),
          ALspComplexStroke.gap(3),
          ALspComplexStroke.filledArc()
                           .length(5)
                           .minorRadius(2.5)
                           .fillColor(aColor)
                           .build(),
          ALspComplexStroke.gap(3)
      );

      ALspComplexStroke plain = ALspComplexStroke.line()
                                                 .lengthRelative(1)
                                                 .lineColor(aColor)
                                                 .lineWidth(2)
                                                 .build();

      return TLspComplexStrokedLineStyle.newBuilder()
                                        .regular(dashdot)
                                        .fallback(plain)
                                        .decoration(0.25, arrow)
                                        .decoration(0.5, arrow)
                                        .decoration(0.75, arrow)
                                        .decoration(1.0, arrow)
                                        .build();
    }

Configure the military layer

With our fancy MySymbolStyler completed, we can start the configuration of our military layer. We will configure our layer using the TLcdMS2525bLayerBuilder. In this example we will create a simple model containing a single TLcdEditableMS2525bObject. We will build a layer using this model and instance of our MySymbolStyler as bodyStyler and labelStyler.

Program: Configuration of the military layer
  protected static ILspLayer createCustomLayer() {
    // Create a Military object with a standard MIL-STD 2525d SIDC code,
    // but with an Ellipse geometry (as opposed to a polygon geometry, as is specified in the standard)
    MyMilitaryObject standardSymbol
        = new MyMilitaryObject("10062500001512030000",
                               "Custom Geometry",
                               createEllipse());

    // Create a Military object with a custom SIDC code, which will have custom styling
    MyMilitaryObject customSymbol
        = new MyMilitaryObject("MY_CUSTOM_SYMBOL",
                               "Custom Symbol",
                               createPolyline());

    // create a vector model and add the created symbols to it
    TLcdVectorModel ms2525Model = new TLcdVectorModel(new TLcdGeodeticReference(),
                                                      new TLcdModelDescriptor());
    ms2525Model.addElement(standardSymbol, ILcdModel.NO_EVENT);
    ms2525Model.addElement(customSymbol, ILcdModel.NO_EVENT);

    // create a new military layer based of that model,
    // configure a custom body styler that will style standard SIDC's according to the specification
    // and custom SIDC's according to our own custom styling
    return TLspMS2525bLayerBuilder.newBuilder()
                                  .model(ms2525Model)
                                  .bodyStyler(TLspPaintState.REGULAR, new MySymbolStyler())
                                  .labelStyler(TLspPaintState.REGULAR, new MySymbolStyler())
                                  .build();
  }

  private static ILcdEllipse createEllipse() {
    TLcdLonLatEllipse ellipse = new TLcdLonLatEllipse();
    ellipse.move2D(5.5, 52);
    ellipse.setA(80000);
    ellipse.setB(50000);
    return ellipse;
  }

  private static ILcdPolyline createPolyline() {
    TLcdLonLatPolyline polyline = new TLcdLonLatPolyline();
    polyline.insert2DPoint(0, 4, 50.5);
    polyline.insert2DPoint(1, 5, 51);
    polyline.insert2DPoint(2, 6, 50.5);
    polyline.insert2DPoint(3, 7, 51);
    return polyline;
  }

Visualizing the military symbol

Finally we can visualize our decorated symbol by creating a Lightspeed view with our military layer.

Program: Creating a view
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      // create the view
      ILspAWTView view = TLspViewBuilder.newBuilder()
                                        .buildAWTView();

      // create the custom decoration layer and add it to the view
      ILspLayer customLayer = createCustomLayer();
      view.addLayer(customLayer);

      // display the view in a frame
      JFrame frame = new JFrame("Custom Control Measure Symbols");
      frame.add(view.getHostComponent(), BorderLayout.CENTER);
      frame.setSize(350, 350);
      frame.setVisible(true);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      // fit on the layer
      FitUtil.fitOnLayers(frame, view, false, customLayer);
    });
  }
custom control measure
Figure 1. Custom control measure symbols visualized