Open drwestco opened 2 years ago
@drwestco: your proposal seems to result in tighter placement of labels, which arguably could be of some help, curious, do you see any benefit in the following hypothetical multi-pass approach:
This might help not to explode the amount of anchors produced during layout. I haven't started prototyping this yet though.
Motivation
The set of anchors, or possible places a symbol may appear along a line, is generated by the worker, and is done independently per line. The set is affected by the size of the label, the value of symbol-spacing, the length of the line segment, etc. Once the anchors are calculated for a given zoom level, that set is fixed and cannot be added to, or have any anchors within shift position.
During the placement pass, when determining collisions between multiple symbols, anchors calculated in the previous step may be rejected. This reduces the number of labels shown along a given line.
In some cases, depending on line length, the size of the label text, and the symbol-spacing value for the layer, there may only be a single anchor available.
The example here is contrived to illustrate the problem. It happens fairly regularly with real city street data, where short roads and long labels regularly lead to sparsely-labeled areas.
symbol-spacing=400
If another symbol happens to collide with that line label, that's it - no other position along the line is considered, even though there's plenty of space.
Design Alternatives
Use existing symbol-spacing behavior
Individual cases can be worked around by adjusting the symbol-spacing value for a layer.
symbol-spacing=300
However, since the text and line geometry determine the available space, crafting the appropriate symbol-spacing rule for a layer as a whole can be difficult, and may lead to over-crowding the line when even smaller values are used.
symbol-spacing=20; also note the weird extra space towards the end of the line
Move symbol-spacing evaluation to placement step
Conceptually, it makes sense to only consider symbol-spacing relative to a symbol that has actually been placed. If a collision knocks out a symbol, the placement code shouldn't need to move XX units from that now-unused anchor position before placing another instance of that symbol. The next spot along the line that clears the collision region should be available for label placement.
Design
Three parts to be considered to support this: style, layout, and placement.
Style
Need to add one or more properties to opt-in to the new behavior. Need to consider how it interacts with the existing symbol-spacing, [icon|text]-overlap, and [icon|text]-ignore-placement styles.
Layout
symbol-spacing evaluation needs to be removed from consideration when generating the anchor points for line labels. Instead, the layout system generates a larger set of possibly-overlapping anchors, increasing the pool of available placements for a symbol. The rules for how many and how closely spaced these anchors should be need to be figured out.
Placement
The pool of anchors generated by Layout is whittled down by collisions, and by evaluating symbol-spacing at this step. Once a symbol is placed, the
anchorIsTooClose
function (also moved from Layout to Placement) rejects anchors for the same symbol appearing closer than symbol-spacing allows.Drawbacks / Concerns
Performance
The additional anchors generated during layout increases the number of collisions to be evaluated during placement. In the example here, two anchors are rejected before finding the third one that avoids colliding with the foreground symbol.
Also, this increases the data passed between worker and main thread, so overall performance needs to be evaluated.
User Experience
The layout code has an extremely long history, and the comments discuss an emphasis on label stability across zoom levels. Need to figure out some way of determining if the change in experience is for the better or for the worse.
Since symbols are placed feature-by-feature, labels placed on multi-lane roads (i.e., highway shields) wind up only on a single lane, instead of alternating. This is an issue with the current maplibre code as well.