Automatic Label Placement
Contents
Introduction
A major problem in map making is to place labels well. Much of the research in automatic label placement has the difficult goal of placing as many labels as possible without overlap. This goal is appropriate when making printed (or rasterized) maps, but it is less important in a GIS system where you can usually zoom in to more detailed maps, or search for a place name.
In Carmenta Engine, the automatic label placement is designed to be efficient, simple to use and stable during panning, but it will not always place the maximum amount of labels. The behavior is controlled by LabelOrganizingSettings that are attached to visualizers, and which offer a number of possibilities:
Labels can have different priority, which can depend on feature attributes.
Labels can be moved to avoid overlap (only in 2D).
Labels with lower priority that overlap labels with a higher priority can be removed.
Duplicate labels can be removed.
Label positions that are partially outside the view area can be avoided.
For labels in 2D maps that must follow a possibly curved line - usually street names - it is possible to avoid positions where the line is too curved.
In addition to configuring how individual labels should be placed through LabelOrganizingSettings you must also add LabelOrganizingLayer which is responsible for organizing and placing all labels generated by its child layers:
![]() |
Both the layer and the settings are necessary to get automatic label placement. So, if you forget to use a LabelOrganizingLayer, then the LabelOrganizingSettings will have no effect. And if a visualizer under a LabelOrganizingLayer lacks a LabelOrganizingSettings, then it will be displayed in the standard way, unaffected by the automatic label placement.
A LabelOrganizingLayer will only organize labels generated by its child layers. This means that the LabelOrganizingLayer should appear leftmost, next to the view:
![]() |
Which visualizers can be label-organized?
A LabelOrganizingSettings can be attached to any visualizer that inherits from PointVisualizer, and that is not only TextVisualizer, but also SymbolVisualizer and PointVisualizerSet. Some examples:
![]() |
How these visualizers are label-organized depends on whether they have been generated from a point feature or a line feature. (If they are used on a polygon feature, the behavior is like a point feature in the center if PointVisualizer.AtCenter is True, otherwise like a line feature along the perimeter.)
Label placement in 2D
This section contains 2D specific label placement functionality.
Point features
Symbols for point features
Usually, you don't want a label-organizing layer to consider alternative placements when a point feature is displayed as a symbol, but there are two reasons why you should give a LabelOrganizingSettings to the SymbolVisualizer anyway:
The LabelOrganizingLayer will then be aware of the symbol visualization, so that other labels (usually texts) can be moved to avoid it;
It can make sense to remove symbols with lower priority if they would otherwise overlap other labels.
To ensure that the label-organizing layer does not consider alternative placements for a symbol, only the original place specified by the visualizer, you must set LabelOrganizingSettings.Count = 0. To allow removal of symbols that would otherwise overlap other labels, you must set LabelOrganizingSettings.RemoveConflicts = True (which is the default).
Texts for point features
For texts, alternative placements make sense. The number of alternatives is specified by LabelOrganizingSettings.Count, but the original place given by the visualizer is tried first and will be used if possible.
Let us say that the text visualizer has alignX = Middle, alignY = Baseline, offsetX = 30 and offsetY = 0. The original place could then look like this (the black dot shows where the point feature is):
![]() |
The alternative places are generated automatically, and override the alignments and offsets of the text visualizer. If LabelOrganizingSettings.Count = 8, then the first four alternatives are like this:
![]() |
which are tried in the order above, below, to the left and to the right. The rest are like this:
![]() |
which are tried in clockwise order starting from the top left.
You can allow more alternatives than 8; the general scheme is explained in more detail under LabelOrganizingSettings.
Line features
A label can be attached to a line feature. The PointVisualizer suggests a default visualization, but the label-organizing layer can modify it, depending on if and how the label should be repeated along the line.
If PointVisualizer.Repeat is set to No, then the label should appear only once, in a position along the line normally specified by PointVisualizer.At (where 0.0 means start and 1.0 means end). However, the label-organizing layer will ignore the at value and try out the specified number of alternative places, preferring those near the middle of the line.
For other Repeat values, the label-organizing layer calculates where the label would have been placed if not organized but then allows each placement to be moved slightly along the line, up to the number of pixels given by LabelOrganizingSettings.Dx.
For more details, see LabelOrganizingSettings.
For text that must follow a curved line (because TextVisualizer.FollowLine = True), you can make the label-organizing layer prefer places where the line is straighter. See LabelOrganizingSettings.MaximumTextCurvature.
Polygon features
Labels that are drawn at the center of polygons, configured by setting PointVisualizer.AtCenter to True , will also be handled by the automatic label placement and the candidate positions will be distributed inside the polygon.
![]() |
It is also possible to prevent labels in large polygons from being placed inside smaller polygons by setting LabelOrganizingSettings.AvoidOtherPolygons to True (this situation can occur when polygons contain other polygons).
For more details, see LabelOrganizingSettings.
Avoiding other visualization
In normal operation a LabelOrganizingLayer will only try to place labels to avoid overlap with other labels and not with other visualization like lines or polygons.
If you want to prevent labels from overlapping specific visualization you can:
Set LineVisualizer.AvoidableByLabels to True on the visualizers that generate the visualization you want labels to avoid. (It is also possible to avoid visualization generated by a PolygonVisualizer or CompositeLineVisualizer in the same way.)
Set LabelOrganizingSettings.LabelAvoidance to indicate exactly which visualization a label should avoid.
For more details, see LabelOrganizingSettings.
Label placement in 3D globe views
This section contains details that are specific to placing labels in 3D.
Conflicts due to perspective
Label placement in 3D is limited to removal of overlapping billboard labels (2D labels positioned in 3D space and always oriented towards the camera). More advanced options that are available in 2D maps, for example generating multiple candidate positions or making texts follow lines, are not supported.
Another key difference in 3D compared to 2D is that labels behave very differently due to the 3D perspective. When the camera looks straight down the map might look similar to a 2D map but when the camera is oriented to look along the surface of the globe towards the horizon labels are pushed towards the horizon on the screen due to the perspective.
![]() |
In fact labels in 3D, in general, conflict more with each other than in 2D due to how the perspective, depending on the camera orientation, clumps labels together. If one of these labels also happens to have a high priority it can remove/hide many labels that are closer, and therefore usually of more immediate interest to the user, to the camera.
To avoid or at least mitigate this problem the LabelOrganizingLayer will automatically remove all labels located farther away than the horizon. This can be disabled on a per label basis through LabelOrganizingSettings.RemoveBeyondHorizon.
Preventing labels from "sticking into the ground"
When you look at a 3D map from above it can be easy to mistake it for a 2D map but even then all labels are positioned in 3D space relative the surface of the globe. This means that labels can intersect with the ground.
![]() |
On the left in the example above the camera is centered over and looks down at Detroit, just outside the upper left corner of the image, and you can clearly see that "Washington, D.C", "Harrisonburg", "Charlottesville" and other labels intersect with the ground. On the right the camera is oriented to look north over Florida and here "Titusville", "West Palm Beach" and "Straits of Florida" can be seen intersecting with the ground. Both are examples of the same problem, namely that in 3D the curvature of the Earth can cause the Earth itself to be closer to the camera than parts of a specific label which in turn hides parts of the label due to the z-depth test during rendering.
There is no solution that will avoid this problem in all situations but there are a couple of things that can be done to minimize it:
Turn off layers that contain labels when you zoom out far enough that the curvature of the Earth becomes noticeable.
Use the distance3D update attribute in a Visualizer.Condition to turn labels off when they are far from the camera.
Make the altitude a label is placed at depend on the position of the camera so that the label is placed higher when the camera is farther from the Earth. This can be accomplished by setting the PointVisualizer.OffsetZ property of the visualizer that generates the label to an expression that calculates an offset from the current set of update attributes (like distance3D).
The 3d_globe.px sample configuration uses several of these ideas to minimize intersections between all labels and the Earth.
Another option also worth mentioning is setting the PointVisualizer.RenderingPriority on all visualizers that generate labels to High. This will ensure the labels are visible but they will also be visible through everything, including the ground, mountains, buildings etc.
Additional options available in both 2D and 3D
Extra conflict margin
When conflicting labels are removed, two labels can still appear very close together.
In the 2D map below, the labels "Charlottenburg" and "Wedding" do not conflict but there is almost no separation between them which makes them hard to read. The problem is even more pronounced in the 3D map where "Exeter" and "Bournemouth" and "Caen", "Le Havre" and "Rouen" are very close together horizontally.
![]() |
To increase the conflict margin, you can set TextVisualizer.BackgroundPadding to be a couple of pixels. This will work even if the TextVisualizer.BackgroundColor is transparent (as it usually is).
Priority
The LabelOrganizingSettings.Priority tells how important a visualizer is. Labels with higher priority are placed first, and those with lower priority can be removed to avoid conflicts (if LabelOrganizingSettings.RemoveConflicts = True).
For example, point features that represent cities often have an attribute that gives the population, which works well as the priority. Just make the priority an indirect attribute variable:
![]() |
For lines, you can use the length as priority, and for polygons, you can use the area. Attributes length and area can be computed by the SizeOperator.
Example: Priority from two attributes
In the Carmenta sample geodata for Lake Tahoe, there is a file tahoe_cities.shp containing cities, but the population attribute, POP, is unfortunately unknown (set to zero) for most of the small cities. However, another attribute, CODE, is either PPL (city) or PPLX (suburb). So it makes sense to give suburbs lower priority than cities, and otherwise depend on population. You can do this by computing a priority value from two attributes using an expression attribute variable.
![]() |
Removing duplicates
A country with many islands can be represented by many polygons with the same name attribute. Without a label-organizing layer, visualization will be bad:
![]() |
With a label-organizing layer, and settings that remove conflicts but not duplicates, we can still get too many labels:
![]() |
If we remove duplicates as well (which is the default), we usually get a sensible amount of labels:
![]() |
Note that the duplicated "Greece" to the east (at Nisos Megisti) is allowed, since it is far away from the first "Greece" label. This is controlled by the LabelOrganizingLayer.MinimumDuplicateDistance property, which by default is 512 pixels. If we lower it to 220 pixels, we can get a few more labels:
![]() |
In the examples above, the label priority was the polygon area, which was computed by a SizeOperator.
When are two visualizations regarded as duplicates?
With the default settings, two text labels are regarded as duplicates if they contain exactly the same text (and are not single characters from a symbol font), even if the font or color differs.
Carmenta Engine can only remove duplicate text labels automatically. However, you can specify a feature attribute with the LabelOrganizingSettings.DuplicateAttribute property the value of which will be used to detect duplicates. This makes it possible to remove duplicate visualizations generated by a symbol font, SymbolVisualizer or PointVisualizerSet.
Allowing duplicates for features of different types
With the default settings, a blue "Mississippi" text along the river would be regarded as a duplicate of a black "Mississippi" text for the state. It is possible to allow this kind of duplication, where the labels are for features of different type, and still remove other kinds of duplication.
![]() |
In the map configuration above, both the StateTextVisualizer and the WaterTextVisualizer display the NAME attribute of their feature. However, in the label organizing settings for the water names, the duplicateAttribute is a different attribute, specified by Atom0. (The Atom0 could be anything, for example DUP_ATTRIB, as long as it differs from NAME.) The value of this attribute is set to the NAME attribute prefixed by an exclamation mark, in the AttributeOperator0. This means that the duplicate attribute for the Mississippi river will get the value "!Mississippi", which differs from the value of duplicate attribute for the state, which is just "Mississippi". But all the displayed labels come from the NAME attributes, which lack the exclamation marks.
So in this way, a state label can have the same text as a water label, and this is not regarded as a duplication. (We assume that no NAME attribute will start with an exclamation mark.)