You added multiple labels to an object in a Lightspeed view, but only one of these labels appears. What is going on ?

What happens?

Say that you want to add a label at the start and end of a line. In such a case, it is easy to end up with the following code:

Program: Attempt to add a label at the start and end of a line
public class MultipleLabelsStyler extends ALspLabelStyler {
  @Override
  public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
    for (Object object : aObjects) {
      // Calculate start and end points...
      ...

      // Add labels for the start and end point
      aStyleCollector.object(object).geometry(startPoint).styles(labelStyles1).submit();
      aStyleCollector.object(object).geometry(endPoint).styles(labelStyles2).submit();
    }
  }
}

Why does this happen?

Each label needs a unique identifier. This is reflected in the TLspLabelID class. A label is identified by its:

  1. Layer

  2. Paint representation. In 99% of the cases, the paint representation is LABEL.

  3. Domain object

  4. Sub-label ID

When these 4 fields are the same for two labels, the labels are considered identical. This is exactly what happens in the styler of the example above. Because ALspLabelStyleCollector.label() is not called explicitly, LuciadLightspeed assigns a default sub-label ID to the label. This happens for both labels, so they end up with the same default sub-label ID. The layer, paint representation, and domain object identifiers are the same as well, so there is no way for LuciadLightspeed to know that they represent distinct labels.

How can you fix it?

In short, you can fix this problem by assigning a unique identifier to each label of the same object. For the example above, such an approach results in the following code:

Program:Assign unique identifier to each object label
public class MultipleLabelsStyler extends ALspLabelStyler {
  @Override
  public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
    for (Object object : aObjects) {
      // Calculate start and end points...
      ...

      // Add labels for the start and end point, using different sub-label IDs for each label
      aStyleCollector.object(object).label("start").geometry(startPoint).styles(labelStyles1).submit();
      aStyleCollector.object(object).label("end").geometry(endPoint).styles(labelStyles2).submit();
    }
  }
}

One object label gets the start sub-label ID while the other gets the end sub-label ID. As you can see in the code, you only need to assign distinct sub-label IDs if an object has multiple labels. This means that a sub-label ID can be used repeatedly for multiple objects.

What makes up a good sub-label id?

Good sub-label IDs fulfill a number of conditions:

  • They must uniquely identify each of of the labels for the same object,paint representation, and layer.
    Not doing so makes labels disappear.

  • They must be immutable and must properly implement the equals() and hashCode() methods.
    Not doing so causes memory leaks and flickering labels.

  • They must be equal for the same label in subsequent ALspLabelStyler.style() calls.
    Not doing so causes flickering labels.

  • They must be suitable for use as a cache key, meaning that they should not hold references to domain objects,layers,the view, and so on.
    Not doing so causes memory leaks.

Good candidates for sub-label IDs are String or Integer objects, but you can use more complex objects as well. In many cases, it is useful to add information to the sub-label ID about the object receiving the labels. For example, when you are adding labels to grid lines, the sub-label ID could look as follows:

Program:Adding labels to grid lines
public class GridLineSublabelID {
  private final double fCoordinate;
  private final boolean fHorizontal;

  public GridLineSublabelID(double aCoordinate, boolean aHorizontal) {
    fCoordinate = aCoordinate;
    fHorizontal = aHorizontal;
  }

  public double getCoordinate() {
    return fCoordinate;
  }

  public boolean isHorizontal() {
    return fHorizontal;
  }

  @Override
  public boolean equals(Object o) {
    ...
  }

  @Override
  public int hashCode() {
    ...
  }
}

The coordinate and orientation information in this sub-label ID can be used to determine the label text or the labeling algorithm for grid line labels, for example.