Introduction

This article follows up on the article on adding and styling features.

Adding labels to features

You can attach icon and text labels to features with the FeatureCanvas::drawLabelFeatureCanvas::drawLabelFeatureCanvas::drawLabel method. Drawing labels is similar to drawing icons or text, with a few distinctions. The most prominent distinctions are:

  • Labels are decluttered

  • Labels have more flexible placement options

  • Labels are always painted on top

The following sections explain this in more detail.

Label decluttering

LuciadCPillar automatically declutters labels to make sure that labels don’t overlap. Labels have more than one possible location. Depending on the view extents and the space occupied by other labels, LuciadCPillar selects the best of those positions. If there are no good positions, the label is dropped.

Labels are decluttered according to these principles:

  • Labels can never overlap with other labels.

  • If a label has a position that lies completely inside the map, it has precedence over a position that lies partially outside the map. If no better position is available for the other label, the label is still painted partially outside the map though.

  • If no valid label position is available, the label isn’t painted. This can happen when all possible positions overlap with another label, for example.

  • If the anchor of the label is invisible, the label isn’t painted.

You can influence the label decluttering behavior by assigning prioritiesprioritiespriorities to a label. Labels with a higher priority are placed on the map before labels with a lower priority. In practice, this means that high-priority labels have a better chance of finding a valid location than low-priority labels. Note that label priorities work across layers.

Label positioning

You can attach labels to a feature in many ways:

label point
Figure 1. Example of a point label
Program: Point label placement
std::vector<RelativePosition> possiblePositions = {RelativePosition::east(offset),
                                                   RelativePosition::west(offset),
                                                   RelativePosition::north(offset),
                                                   RelativePosition::south(offset),
                                                   RelativePosition::northEast(furtherOffset, furtherOffset),
                                                   RelativePosition::northWest(furtherOffset, furtherOffset),
                                                   RelativePosition::southEast(furtherOffset, furtherOffset),
                                                   RelativePosition::southWest(furtherOffset, furtherOffset)};

canvas.drawLabel().anchor(*geometry).pointPositions(possiblePositions).text(labelTextBlock).pinStyle(pinStyle).priority(cityStyle->priority).submit();
var possiblePositions = new List<RelativePosition>
{
    RelativePosition.East(offset),
    RelativePosition.West(offset),
    RelativePosition.North(offset),
    RelativePosition.South(offset),
    RelativePosition.NorthEast(furtherOffset, furtherOffset),
    RelativePosition.NorthWest(furtherOffset, furtherOffset),
    RelativePosition.SouthEast(furtherOffset, furtherOffset),
    RelativePosition.SouthWest(furtherOffset, furtherOffset)
};

canvas.DrawLabel()
    .Anchor(geometry)
    .PointPositions(possiblePositions)
    .Text(labelTextBlock)
    .PinStyle(pinStyle)
    .Priority(cityStyle.Priority)
    .Submit();
val possiblePositions = listOf(
    RelativePosition.east(offset),
    RelativePosition.west(offset),
    RelativePosition.north(offset),
    RelativePosition.south(offset),
    RelativePosition.northEast(furtherOffset, furtherOffset),
    RelativePosition.northWest(furtherOffset, furtherOffset),
    RelativePosition.southEast(furtherOffset, furtherOffset),
    RelativePosition.southWest(furtherOffset, furtherOffset)
)

canvas.drawLabel().anchor(geometry).pointPositions(possiblePositions).text(labelTextBlock)
    .pinStyle(pinStyle).priority(cityStyle.priority).submit()
label inPath
Figure 2. Example of an in-path label
Program: In-path label placement
canvas.drawLabel()
    .anchor(*geometry) //
    .inPath()
    .text(name)
    .textStyle(std::move(textStyle))
    .priority(Priority::fallback())
    .queryable(false)
    .submit();
canvas.DrawLabel()
    .Anchor(geometry)
    .InPath()
    .Text(name).TextStyle(textStyle)
    .Priority(Priority.Fallback)
    .Queryable(false)
    .Submit();
canvas.drawLabel()
    .anchor(geometry)
    .inPath()
    .text(name)
    .textStyle(textStyle)
    .priority(Priority.Fallback)
    .queryable(false)
    .submit()
label onPath
Figure 3. Example of an on-path label
Program: In-path label placement
canvas.drawLabel()
    .text(*name) //
    .textStyle(textStyle)
    .anchor(*geometry)
    .onPath(PathLabelPosition::Above, 0.0)
    .priority(Priority::low())
    .submit();
canvas.DrawLabel()
    .Text(name).TextStyle(textStyle)
    .OnPath(PathLabelPosition.Above, 0.0)
    .Anchor(geometry)
    .Priority(Priority.Low)
    .Submit();
canvas.drawLabel()
    .text(name)
    .textStyle(textStyle)
    .anchor(geometry)
    .onPath(PathLabelPosition.Above, 0.0)
    .priority(Priority.Low)
    .submit()

Label content

The label content can be text-based, or it can be an icon.

  • Single-line text: You can specify a single string as label content using the text(std::string) method.

label text single line
Figure 4. Example of a single-line text label
  • Multi-line text: You can do this by specifying multiple strings as label content using the text(std::vector<std::string>) method.

label text multi line
Figure 5. Example of a multi-line text label
label icon
Figure 6. Example of an icon label

Visualizing label pins

You can attach a pinattach a pinattach a pin to a label. A pin is a line that connects the label with its anchor.

labelPin
Figure 7. Example of a pin line

Label painting

LuciadCPillar paints labels slightly differently than it paints regular icons or text:

Labels and selection

Labels are selectable by default. If you click them, you select the feature. If you don’t want a feature to be selectable from its label, you must invoke queryablequeryablequeryable with parameter false on a LabelDrawCommand.

Labels in map query results

Labels are part of the results returned by queryFeaturesqueryFeaturesqueryFeatures. The query result doesn’t include a touch point for labels, though. Because labels are overlaid on the map and not painted in the map reference like a Feature’s geometry, feature queries don’t compute the exact position on the map for labels. The MapQueryFeaturesResultMapQueryFeaturesResultMapQueryFeaturesResult entries for labels have the overlay flag enabled.