A recurring use case for LuciadLightspeed applications is the display of moving tracks with added heading vectors. The heading vector needs to indicate the direction in which the track is moving. The tracks consist of dynamically updated ILcdPoint objects. The length of this vector is commonly expressed in pixels rather than in a geographically defined unit of measure. That way, the vector remains visible at all zoom levels.

Intuitively, a developer might attempt to tackle this problem as follows:

  1. Given the lon/lat coordinates and the azimuth of the moving track, compute a second lon/lat point which lies at a pixel distance D from the track. To achieve this, the developer presumably uses the methods in ILcdEllipsoid and the view’s map scale to convert between a pixel distance and a distance in meters.

  2. Connect the track and the computed end point of the vector with a TLcdLonLatLine.

  3. Style the line with a TLspLineStyle.

This approach is cumbersome to implement and results in sub-optimal performance. Because the length of the lon/lat line depends on the zoom level, it needs to be re-computed each time the map scale changes. You can neatly implement all that inside an ALspStyler, but this styler will have to fire a style change event every time the users zooms in or out.

How to do it?

A better approach to the problem is to encapsulate the heading vector into an ILcdIcon. The icon could contain a simple line or a line with an arrowhead, for example:

up arrow

You can implement this by using a custom ILcdIcon that draws a line or arrow directly on the supplied Graphics, or you can store the icon in a file and load it with TLcdImageIcon. Once we have the ILcdIcon, we will use the track itself (an ILcdPoint) and style it with a TLspIconStyle.

To apply the heading to the icon automatically, the track object must implement ILcdOriented to advertise its current heading.

Oriented track
public class Track extends TLcdLonLatHeightPoint implements ILcdOriented {

  private double fHeading;

  public Track(double aLon, double aLat, double aZ, double aHeading) {
    super(aLon, aLat, aZ);
    fHeading = aHeading;
  }

  @Override
  public double getOrientation() {
    return fHeading;
  }
}

If you use an ILcdIcon, the icon style will position the icon so that its center is at the location of the track. The center point of the icon will also be used as the rotation point. For heading vectors, you typically want the start point of the arrow at the location of the track, and you also want to use this point to apply the rotation. To achieve that, make sure that your icon is an ILcdAnchoredIcon. If you are using an existing icon, you can use TLcdAnchoredIcon to wrap it and provide an anchor point.

When you are creating the TLspIconStyle, enable useOrientation so that the style will rotate the icon in the direction of the object as reported by ILcdOriented#getOrientation().

Note that an ILcdIcon is defined in AWT pixel coordinates. The origin is in the top left corner, with the Y-axis pointing down.

Creating the icon style with an anchored icon
TLcdImageIcon arrowIcon = new TLcdImageIcon("arrow.png");
TLcdAnchoredIcon anchoredIcon = new TLcdAnchoredIcon(arrowIcon, new Point(arrowIcon.getIconWidth() / 2, arrowIcon.getIconHeight()));
TLspIconStyle iconStyle = TLspIconStyle.newBuilder()
                                       .useOrientation(true)
                                       .icon(anchoredIcon)
                                       .build();

If the ILcdIcon draws the heading vector in white, you can use the modulationColor property on TLspIconStyle to change it to any desired color.