This article explains the support that LuciadLightspeed offers for labeling the representations of your domain objects in a Lightspeed view.

What is a label?

A label is any description or classifier you attach to a domain object representation: a single word, a multi-line text string, an icon, or even an interactive panel.

LuciadLightspeed makes no distinction between regular painting and label painting. Just as regular painters, label painters implement the ILspPainter interface. Label painters can also be added to a layer, and they can be customized with an ILspStyler.

There are a few notable differences between label objects and regular painted objects though:

  • Labels are usually painted on top of all regular object representations. See also Layer ordering.

  • Labels are usually painted in screen coordinates, not as objects that are part of the displayed world.

  • The position and visibility of individual labels can be coordinated across different layers.

Activating labeling

The recommended way to activate label painting for a model is to attach ALspLabelStyler objects to a layer’s label painter using a TLspShapeLayerBuilder. The labels will appear if the styler returns at least one style for the domain objects. Program: Enabling label painting by setting stylers on a TLspShapeLayerBuilder demonstrates how you can do this.

Program: Enabling label painting by setting stylers on a TLspShapeLayerBuilder
TLspShapeLayerBuilder layerBuilder = TLspShapeLayerBuilder.newBuilder();
layerBuilder.labelStyler(TLspPaintState.REGULAR, styler);
// other layer builder calls ...
ILspLayer layer = layerBuilder.build();

You can also just set one or more fixed styles directly on the layer builder, without using a styler. Program: Enabling label painting by setting styles on a TLspShapeLayerBuilder demonstrates how you can do this.

Program: Enabling label painting by setting styles on a TLspShapeLayerBuilder
layerBuilder.labelStyles(TLspPaintState.REGULAR, TLspTextStyle.newBuilder().build());

Alternatively, if you are not using a layer builder to create a TLspLayer, you can set a TLspLabelPainter on the layer for paint representation LABEL, and attach the stylers to that painter. Program: Enabling label painting by attaching a TLspLabelPainter to a layer shows how you can do this.

Program: Enabling label painting by attaching a TLspLabelPainter to a layer
TLspLayer layer = new TLspLayer();
TLspLabelPainter labelPainter = new TLspLabelPainter();
labelPainter.setStyler(TLspPaintState.REGULAR, styler);
layer.setPainter(TLspPaintRepresentation.LABEL, labelPainter);

Customizing the label content and look

You can handle all of the customization of the labels through ALspLabelStyler objects. Styling allows you to specify the content, the look, the positioning and the number of displayed labels. ALspLabelStyler objects offer advanced options like defining multiple labels or specifying positioning or priorities. In some cases a regular ILspStyler is sufficient. It doesn’t offer advanced labeling options though.

The criteria for choosing an ALspLabelStyler for labels are very much the same as those for choosing stylers for regular shape painting. You can pick a simple TLspLabelStyler with just one style, or you can decide to use advanced stylers that use the view scale to manage level-of-detail, or that handle animations. As with regular painting, you can set a styler for each TLspPaintState: REGULAR, SELECTED and EDITED. If you do not specify a styler for SELECTED, the painter will fall back to a modified REGULAR styler. If you do not specify a styler for EDITED, the painter will fall back to the SELECTED styler if available and otherwise to a modified REGULAR styler.

To see your labels, define your label styler so that it applies at least one style for the domain objects, for example a TLspTextStyle or a TLspIconStyle. Otherwise, nothing is displayed. For more information about applying text or icon styles, see Using text as labels and Using images as labels. Program: Very basic label stylers shows the most basic label stylers possible.

Program: Very basic label stylers
ILspStyler regularStyler  = TLspTextStyle.newBuilder().textColor(Color.white).build();
ILspStyler selectedStyler = TLspTextStyle.newBuilder().textColor(Color.red).build();

Program: A styler that applies different styles to different domain objects shows a custom label styler that applies different styles to different domain objects.

Program: A styler that applies different styles to different domain objects
  public class MyStyler extends ALspLabelStyler {
    @Override
    public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
      for (Object domainObject : aObjects) {
        if (...) {
          aStyleCollector.object(domainObject)
                         .styles(someStyles)
                         .algorithm(someAlgorithm)
                         .submit();
        } else {
          aStyleCollector.object(domainObject)
                         .styles(otherStyles)
                         .algorithm(otherAlgorithm)
                         .submit();
        }
      }
    }
  }

Using text as labels

The most common purpose of labels is to display textual information next to the domain object. You can display a textual label by using a TLspTextStyle in your label styler, as already shown in Program: Very basic label stylers.

TLspTextStyle allows you to specify not only the presence of a label, but also the look of the label text. TLspTextStyle offers basic settings such as font, color and halo. It also allows you to set the alignment and line spacing for multi-line labels. Table 1, “Text label styling options” demonstrates these settings.

Table 1. Text label styling options
Style Result

The default text style: TLspTextStyle.newBuilder().build()

lightspeed textstyle default

Text style with a different font: TLspTextStyle.newBuilder().font("Serif-BOLD-18").build()

lightspeed textstyle font

Text style in red without halo: TLspTextStyle.newBuilder().textColor(Color.red).haloThickness(0).build()

lightspeed textstyle nohalo

Text style for multi-line labels: TLspTextStyle.newBuilder().alignment(TLspTextStyle.Alignment.CENTER).build()

lightspeed textstyle multiline

If you would like to add properties of your domain objects as label content, use TLspDataObjectLabelTextProviderStyle. This implementation of ALspLabelTextProviderStyle is highly suitable for the usage of object properties as label text. See the section below for more information.

Customizing text label content

By default, the text content of a label is retrieved using the toString() method of the domain object. You can override the default content by adding an ALspLabelTextProviderStyle to your label styler. You can implement a text provider style yourself, or use a TLspDataObjectLabelTextProviderStyle to add properties of ILcdDataObject domain objects to your label. For more information about ILcdDataObject objects, see Unified access to domain objects.

Formulate the label content as expressions. If you specify more than one expression, each expression appears on a separate line in the label, as shown in Program: Using two ILcdDataObject properties as one multi-line text label.

Program: Using two ILcdDataObject properties as one multi-line text label
TLspDataObjectLabelTextProviderStyle.newBuilder().
   expressions("STATE_NAME", "POP1996").build()

Changing text label content

Changes to the text content of labels are not necessarily picked up immediately.

This means you have to notify the painters when a domain object’s text has changed. You can use two kinds of events to notify painters:

  • Model change events for the relevant domain objects, for example through the ILcdModel.elementChanged method. These events should for example be used when the content of a data object is changed.

  • Style change events for the relevant domain objects, for example through the ALspStyler.fireStyleChangeEvent method. These events should for example be used when using a new TLspDataObjectLabelTextProviderStyle with different expressions.

Using images as labels

Beside text content, you can also display arbitrary image content as a label. To display an image as a label, use a TLspIconStyle in your stylers. The TLspIconStyle contains an ILcdIcon. There are various ways to use this feature:

  • A simple TLcdImageIcon based on a file. You can define the styler so that a property of the domain object determines which file is selected.

  • A custom ILcdIcon implementation that uses AWT Graphics for icon drawing.

Changing icon-based label content

Similar to text content, labels painted by icons are not updated automatically when the icon changes. Updates are triggered by model change events or style change events only.

To detect a possible change in the icon, the painter uses the equals method. Therefore, two different icons should not compare as equal. For performance reasons though, make sure that identical icons always compare as equal. Preferably use the same instance.

Using Swing components as labels

Beside text and images, you can also display Swing components as a label. To do this, use ALspSwingLabelStyler. This styler has a method that returns a Swing component for a specific label. Using this component, the styler generates an image that is used by the label painter. Because of this it is not possible to interact with the used component. To see how it is possible to interact with labels that are composed of Swing components, see Creating interactive labels that users can edit.

lightspeed label awt components
Figure 1. Labels painted using Swing components. Screenshot taken from the lightspeed.labels.interactive sample.

Options for additional styling

In addition to the text and image styling options described above, you have a few other styles at your disposal to change the look of your labels. These additional styles can be used either with text labels or image labels:

  • TLspPinLineStyle: indicate that a pin line should be drawn from the label to its domain object anchor point.

  • TLspLabelBoxStyle: specifies stylings such as the background color, the presence of a label frame or a halo.

  • TLspLabelOpacityStyle: specifies transparency of the labels, or a color to blend the label with. This style can be used efficiently with animations, highlighting or other changes that happen frequently, as this style is applied on the label using the graphics hardware without any texture redrawing.

Table 2, “Additional labeling styling options” shows how to use these styles and the results.

Table 2. Additional labeling styling options
Style Result

Pins: TLspPinLineStyle.newBuilder().color(Color.red).width(2).build()

lightspeed label pin

Frame and fill: TLspLabelBoxStyle.newBuilder().frameThickness(2).frameColor(Color.red).filled(true).fillColor(Color.lightGray).build()

lightspeed label frame

Opacity and blend color: TLspLabelOpacityStyle.newBuilder().color(Color.red).opacity(0.5f).build()

lightspeed label modulation

Defining multiple labels for a domain object

To display more than one label for your domain objects you need a custom ILspStyler that uses the ALspLabelStyleCollector.label(aSublabelID) call. As shown in Program: Defining multiple labels for domain objects in a ALspLabelStyler, you can specify different sets of styles for different sublabels for one domain object using ALspLabelStyleCollector.

The argument aSublabelID is an object that identifies the sublabel for a domain object. You would typically use a fixed set of different sublabels, such as a Java enum or a set of incrementing numbers.

You can safely use your own objects by following these guidelines:

  • Make sure the sublabel ID is not equal for different labels on the same domain object. Otherwise only one of the two labels is displayed.

  • The sublabel ID must be equal for the same label of a domain object on subsequent styling calls. Although the label will be displayed correctly, an unequal sublabel ID for the same label creates unnecessary overhead: the painter will assume one label was removed and a new one was added.

  • The sublabel ID can be shared between different domain objects, so you can use constants.

  • This label() call is optional if you need just one label: a default sublabel ID of "single" will be used.

Program: Defining multiple labels for domain objects in a ALspLabelStyler
    public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
      aStyleCollector.objects(aObjects).label(1).styles(someStyles).submit();
      aStyleCollector.objects(aObjects).label(2).styles(otherStyles).submit();
      aStyleCollector.objects(aObjects).label(3).styles(moreStyles).submit();
    }

Positioning labels

This section describes the way LuciadLightspeed positions labels with respect to the shape representing the domain object.

By default, LuciadLightspeed uses the type of shape to determine where to place labels, as shown in Figure 2, “Default label positions for points, lines and polygons”:

lightspeed label defaultpositioning
Figure 2. Default label positions for points, lines and polygons

You can control the positioning of your labels in various ways using the styling API.

The styling API allows you to:

Alternatively, you can use a custom label placement algorithm, although this is more involved. For more information, refer to Using an algorithm for label decluttering.

Configuring relative label positions

You can fully customize the positioning of your label by specifying an algorithm in your ILspStyler using ALspLabelStyleCollector. Using an algorithm for label decluttering describes the algorithms delivered with LuciadLightspeed and how to use them.

For convenience, labels on point-based domain objects are automatically positioned relative to the point using the TLspLabelingAlgorithm. By default, one of the eight compass direction locations shown in Figure 3, “All possible locations when using TLspLabelLocationProvider” is used, or the label is placed on top of the point itself.

lightspeed label compasslocation
Figure 3. All possible locations when using TLspLabelLocationProvider

You can override these positions by specifying a different set of locations in your styler:

Program: Defining two possible locations, above and below the object, offset by 50 pixels
private final ALspLabelLocationProvider fLocationProvider = new TLspLabelLocationProvider(50, Location.NORTH, Location.SOUTH);
private final ILspLabelingAlgorithm fAlgorithm = new TLspLabelingAlgorithm(fLocationProvider);

public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
  aStyleCollector.objects(aObjects).algorithm(fAlgorithm).submit();
}

Defining a different anchor point for labels

By default, a label is positioned relative to the domain object’s focus point. Defining a different anchor point for the label may help you achieve the following goals:

  • Add labels to different shapes in an ILcdShapeList.

  • Add labels to different parts of a shape.

  • Label a domain object which itself is no shape, by specifying a shape that should be labeled. For example, by using a property of the domain object that defines a location.

To specify a different anchor point for a label, you can use ALspStyleCollector.geometry() calls.

Just as for regular styling, use either a specific ILcdShape for each domain object, or pass an ALspStyleTargetProvider that calculates the anchor point when requested.

It is important to note that the anchor points defined this way need to be specified in the reference of the ILcdModel that contains the domain objects.

Figure 4, “Labels anchored to specific points” shows such an example: each river polyline not only has a label for its name, but also a label indicating the river’s spring and mouth. Program: Defining different anchor points for labels shows the ALspStyler and ALspStyleTargetProvider used to accomplish this.

lightspeed label geometryanchor
Figure 4. Labels anchored to specific points
Program: Defining different anchor points for labels
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
  ALspLabelTextProviderStyle textProvider = new ALspLabelTextProviderStyle() {
    public String[] getText(Object aDomainObject, Object aSublabelID, TLspContext aContext) {
      return new String[] {aDomainObject.toString() + " " + aSublabelID.toString()};
    }
  };

  ALspStyleTargetProvider firstPointProvider = new ALspStyleTargetProvider() {
    public void getStyleTargetsSFCT(Object aObject, TLspContext aContext, List<Object> aResultSFCT) {
      ILcdPointList points = (ILcdPointList) aObject;
      aResultSFCT.add(points.getPoint(0));
    }
  };

  ALspStyleTargetProvider lastPointProvider = new ALspStyleTargetProvider() {
    public void getStyleTargetsSFCT(Object aObject, TLspContext aContext, List<Object> aResultSFCT) {
      ILcdPointList points = (ILcdPointList) aObject;
      aResultSFCT.add(points.getPoint(points.getPointCount() - 1));
    }
  };

  aStyleCollector.objects(aObjects).label("river").styles(textStyle).submit();
  aStyleCollector.objects(aObjects).label("spring").geometry(firstPointProvider).styles(textStyle, pinStyle, textProvider).submit();
  aStyleCollector.objects(aObjects).label("mouth").geometry(lastPointProvider).styles(textStyle, pinStyle, textProvider).submit();
}

Defining labels with a dependency on other labels

If you are using multiple labels per domain object, you can position one label relative to another label, instead of to the labeled domain object. To achieve this, use the ALspLabelStyleCollector.anchorLabel(aSublabelID) call, and specify a label previously defined for the domain object as sublabel ID.

For example, Figure 5, “Labels positioned relative to other labels” show a city with a couple of labels: the top icon represents the "info" label; with the city and state next to it as text labels. The bottom icon represents a "statistics" label with the city’s population and housing units numbers next to it. If the icon labels move, the text labels stay with them.

lightspeed label dependencies
Figure 5. Labels positioned relative to other labels
Program: Defining labels positioned relative to other labels
public void style( Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext ) {
  aStyleCollector.objects(aObjects).label("info").styles(infoStyles).submit();
  aStyleCollector.objects(aObjects).label("name").anchorLabel("info").styles(nameStyles).submit();
  aStyleCollector.objects(aObjects).label("state").anchorLabel("info").styles(stateStyles).submit();

  aStyleCollector.objects(aObjects).label("statistics").styles(fStatsStyles).submit();
  aStyleCollector.objects(aObjects).label("population").anchorLabel("statistics").styles(populationStyles).submit();
  aStyleCollector.objects(aObjects).label("housing_units").anchorLabel("statistics").styles(housingUnitsStyles).submit();
}

Configuring labels along a polyline’s path

By default, for polyline objects, LuciadLightspeed uses straight labels that are aligned with the polyline at a certain point. Sometimes however, you want the labels to closely follow the polyline, so that the labels themselves are bent according to the polyline. You can do this by using the TLspCurvedPathLabelingAlgorithm.

Program: Defining labels closely following a polyline shows how to use this algorithm in your styler. Figure 6, “Labels positioning along a path (left) versus following a path (right)” shows the default behavior, regular labels placed aligned with a path, next to curved labels following a path closely using TLspCurvedPathLabelingAlgorithm.

Program: Defining labels closely following a polyline
private final ILspLabelingAlgorithm fCurvedPathAlgorithm = new TLspCurvedPathLabelingAlgorithm();

public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
  aStyleCollector.objects(aObjects).styles(...).algorithm(fCurvedPathAlgorithm).submit();
}
lightspeed label curved
Figure 6. Labels positioning along a path (left) versus following a path (right)

Configuring label decluttering

Each Lightspeed view has decluttering algorithms to make sure labels never overlap with each other. By default, all layers in the view and their labels are automatically decluttered together, so that no label in the view overlaps with another label.

You can customize this behavior for each layer or even for each individual label. To do so, you must define and use a placement group.

A declutter group specifies:

The label placement process treats each placement group separately, so labels from different placement groups can overlap.

You can add your own placement group using TLspLabelPlacer.addPlacementGroup. Each view already contains four pre-defined placement groups that you can use:

  • TLspLabelPlacer.DEFAULT_DECLUTTER_GROUP: this group is used by default for all layers and all their labels. No labels in this group can overlap.

  • TLspLabelPlacer.DEFAULT_NO_DECLUTTER_GROUP: this group can be used for labels that should always be shown, even if they overlap.

  • TLspLabelPlacer.DEFAULT_GRID_GROUP: this group is used for grid labels by default. Because this is a separate group, grid labels are decluttered amongst themselves, but they can still overlap with other labels.

  • TLspLabelPlacer.DEFAULT_REALTIME_GROUP: this convenience group can be used when labeling dynamic data, for example tracks. This group allows them to be decluttered independently from static labels.

The easiest way to use a declutter group is through the ALspLabelStyleCollector, e.g. by using ALspLabelStyler. You can specify the group to use for all labels, or for differently for individual labels if necessary. Program: Defining the declutter group for labels shows an example.

Program: Defining the declutter group for labels
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
  aStyleCollector.objects(aObjects)
                 .label(1)
                 .group(TLspLabelPlacer.DEFAULT_DECLUTTER_GROUP)
                 .styles(...)
                 .submit();
  aStyleCollector.objects(aObjects)
                 .label(2)
                 .group(TLspLabelPlacer.DEFAULT_NO_DECLUTTER_GROUP)
                 .styles(...)
                 .submit();
}

Setting label priorities across layers

During decluttering, the label placement process tries to find a location for each label where no other label has been placed yet. Because certain labels may be more important than others, you can specify a priority for each label type. All labels in the declutter group are sorted by this priority and placed in this order. High priority labels are placed first, and lower priority labels may be skipped if there is no spare view room left. Since a declutter group can contain labels from different layers, the priorities are compared across layers.

The priorities are compared across different layers, so you have to align them. Also note that a lower number means a higher priority.

Priorities are defined through the ALspLabelStyleCollector:

Program: Defining label priorities
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
  aStyleCollector.objects(aObjects).label(1).priority(100).styles(...).submit();
  aStyleCollector.objects(aObjects).label(2).priority(200).styles(...).submit();
}
Program: Defining label priorities using a ILspLabelPriorityProvider
ILspLabelPriorityProvider citiesPriorityProvider = new ILspLabelPriorityProvider() {
  public int getPriority(TLspLabelID aLabelID, TLspPaintState aPaintState, TLspContext aContext) {
    if (isCapital( aDomainObject )) {
      return 10;
    } else {
      return 20;
    }
  }
}

public void style( Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext ) {
  aStyleCollector.objects( aObjects ).priority(citiesPriorityProvider).styles( ... ).submit();
}

Interacting with labels

Users can interact with labels just like they can with representations of domain objects: they can select labels and perform certain operations on them. Moving labels explains how to move labels. If you want to offer more complex interaction, you can use interactive labels, which allow you to represent your label with any Swing component and interact with it. This is explained in Creating interactive labels that users can edit.

Moving labels

Just as domain object modifications can be triggered by input events, an input event can also trigger the editing of one or more labels painted in the view. You can make use of the LuciadLightspeed label editor classes to modify the label positions.

lightspeed label editing
Figure 7. Moving a label

You can easily enable label editing on a TLspShapeLayerBuilder, as shown in Program: Enabling editable labels using a TLspShapeLayerBuilder. The layer will use the default label editor implementation, TLspLabelEditor.

Program: Enabling editable labels using a TLspShapeLayerBuilder
layerBuilder.labelEditable(true);

If you create your own layer without using a layer builder, the following steps are required:

  1. Choose an editor: either use the default TLspLabelEditor, or create a custom implementation of ILspEditor.

  2. Create an editable layer: to configure a layer as editable, set the editable flag to true: layer.setEditable(true) and register the chosen label editor with the correct paint representation: layer.setEditor(paintRepresentation, chosenEditor).

  3. Set the edit controller: set the TLspEditController on the view with view.setController(new TLspEditController()), so that the editor can receive input events from the UI handles it provides. Note that the default controller can edit as well.

The default label editor implementation provided by LuciadLightspeed is TLspLabelEditor. This label editor converts label locations to TLspStampLabelLocation objects. This makes it possible to move a label anywhere on the screen.

Creating interactive labels that users can edit

An interactive label is a label that users can interact with to modify properties of domain objects. You can configure interactive labels using an ALspInteractiveLabelProvider. To display and edit interactive labels, you can use a TLspInteractiveLabelsController. For more information about the usage of controllers, see Interacting with the view<<lsp_advanced_gui>.

For performance reasons, interactive labels are only shown when the user moves the mouse cursor over a regular label. If the ALspInteractiveLabelProvider indicates that it can provide an interactive label, the interactive label is presented to the user instead of the regular label.

lightspeed interactive label
Figure 8. Interactive label during editing. Screenshot taken from the lightspeed.labels.interactive sample.

Implementing an ALspInteractiveLabelProvider

When you are implementing an ALspInteractiveLabelProvider, the most important method is the startInteraction method. This method returns the Swing component with which the user can interact. This component can be any Swing component: it can be a single text field or a JPanel containing check boxes, combo boxes, and more.

There can be only one interactive label from the same provider at any time. Therefore, the implementation of this method can always return the same instance of the Swing component. The methods of ALspInteractiveLabelProvider are always called in a certain order. There are never two calls to startInteraction before the previous interaction is stopped or canceled. This allows you to attach listeners to the domain object and the Swing components in the startInteraction method, for example, and to remove these listeners in the implementation of the stopInteraction and cancelInteraction methods.

Activating interactive labels

TLspInteractiveLabelsController shows interactive labels based on mouse-moved events. When the mouse hovers over a label, it provides an interactive label for that label. For touch input, where hover actions do not exist, the interactive label is put in place at the first touch.

It is also possible to bypass the automatic behavior. When the setProvideInteractiveLabelOnMouseOver method of TLspInteractiveLabelsController is set to false, no interactive labels are shown automatically. You can then ask the ALspInteractiveLabelProvider to provide an interactive label with the startInteraction method. To stop or cancel the label interaction, you can use the stopInteraction and cancelInteraction methods respectively as this no longer happens automatically either. Bypassing the automatic behavior allows you, for example, to implement a scenario in which a user cycles through the labels by pressing a certain key on the keyboard, making each label interactive one after the other.

Using mouse and touch events

In order to move the interactive labels, the ALspInteractiveLabelProvider can dispatch mouse or touch events that happen on the interactive label to the view. The active ILspController can then receive the events and use those, for example, to move the label with the label editor. Which events are dispatched to the view and which events are dispatched to the interactive label is decided by the dispatchAWTEvent method of the ALspInteractiveLabelProvider. By default this method dispatches the event to the view when it happened on a JLabel or JPanel. It dispatches all other events to the originating component itself. As a result, when the user clicks and drags on a JLabel, the controller of the view can handle these events and can, for example, move the label. Clicking on, for example, a JSlider would as expected drag the knob. You can override this method to customize which events are dispatched to the interactive label and which are dispatched to the view.

Using obstacle providers

The label decluttering algorithms used by the ILspLabelPlacer can ensure that no two labels overlap on the screen.

However, it is often useful to mark areas in the screen where no labels should be displayed. For example:

  • The body representations of domain objects. You could avoid displaying labels over icons in the view.

  • GUI components overlaid on the view. You could avoid displaying labels behind those components since they would be hard to read.

To accomplish this, you can attach ILspLabelObstacleProvider objects to a declutter group. An obstacle provider returns a set of areas on the screen where no labels should be placed.

To attach an obstacle provider to a declutter group, use TLspLabelPlacer.addLabelObstacleProvider(). Note that you are responsible for removing the obstacle provider from the placer at the appropriate time.

Since an obstacle provider is attached to a declutter group, all labels and algorithms used in that group take the areas returned by the providers into account.

Painting 3D labels

All the labeling functionality described above applies both to 2D and 3D views. Positioning and decluttering work in the same way, although terrain can influence the visibility of the labels.

You need to take into account a subtlety with respect to painting order, however. In 2D, labels are always painted on top of other content. In 3D, labels are painted in the OPAQUE paint phase. This means other content painted in the TRANSPARENT phase can be over the labels if it is closer to the camera. Figure 9, “Labels can be obscured by content in 3D.” shows this.

lightspeed label 3d
Figure 9. Labels can be obscured by content in 3D.

If you do not want this behavior, you can activate full overlay using TLspLabelPainter.setOverlayLabels. Figure 10, “Labels overlaid in 3D.” shows the result.

lightspeed label 3doverlay
Figure 10. Labels overlaid in 3D.

Advanced label placement

The previous sections explain the various ways to configure labeling in your application and should be enough for most usages. This section provides more background information on label placement.

It is possible for the view to position and declutter labels. For this purpose ILspLabelPlacer is used. Whereas label painters determine the content and appearance of the labels, ILspLabelPlacer computes if and where the label is positioned. On top of that, it stores the label location information so that the painter, for example, can access it.

Note: the default label placer works asynchronously. This means the label visibility is not updated on every paint, and can scale better with many labels.

The label placement and decluttering functions, of which the goal is to avoid overlap between labels, can take multiple labels of a group of domain objects into account. The group can be so large as to contain all the objects visible in the view, including objects of different layers. This allows for the more advanced placement of labels, to improve readability and avoid overlap.

Using label placers to locate labels explains the label placer implementations and how they use an algorithm to compute the label position. It has a subsection that details the labeling algorithm implementations and one that shows how custom labeling algorithms can be created or custom priorities can be set. Accessing label locations programmatically explains how label locations can be accessed programmatically.

Using label placers to locate labels

Because a label placer operates on the labels of all layers, it is located in the ILspView, and can be retrieved using the getLabelPlacer method. In the Lightspeed view implementations, a default ILspLabelPlacer is already set. It can be configured in the layers. If this does not suffice, you can configure the label placer manually, and set it on the layer using the setLabelPlacer method of ILspView.

Lightspeed views offers one label placer implementation:

Using an algorithm for label decluttering

The LuciadLightspeed label placer implementations use labeling algorithms to place and declutter labels. It is possible to customize the placement of labels by choosing a different labeling algorithm.

LuciadLightspeed offers the following labeling algorithms:

See the package com.luciad.view.lightspeed.label.algorithm for more information.

Enabling a certain algorithm can be done through the ALspLabelStyler, as shown in Program: Specifying labeling algorithms using ALspLabelStyleCollector.

Program: Specifying labeling algorithms using ALspLabelStyleCollector
    public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) {
      aStyleCollector.objects(aObjects).label(1).algorithm(algorithm1).submit();
      aStyleCollector.objects(aObjects).label(2).algorithm(algorithm2).submit();
    }

Defining a custom algorithm for the placement of labels

One way to customize labeling algorithms is to wrap them, to further remove unwanted labels for example. If you want even more control of label placement, you can consider implementing your own labeling algorithm.

Accessing label locations programmatically

Apart from placing labels, ILspLabelPlacer also keeps track of the storage location of labels. For this, it uses ALspLabelLocations, accessed with the getLabelLocations() method of ILspLabelPlacer. This object makes it possible to check if labels are visible and where they are located, or to listen to visibility or location changes. It also makes it possible to manipulate label locations or visibility manually.