Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variable text placement for point labels #5038

Closed
nickidlugash opened this issue Jul 24, 2017 · 8 comments
Closed

Variable text placement for point labels #5038

nickidlugash opened this issue Jul 24, 2017 · 8 comments
Labels
cross-platform 📺 Requires coordination with Mapbox GL Native (style specification, rendering tests, etc.) feature 🍏 needs discussion 💬

Comments

@nickidlugash
Copy link

The purpose of this is ticket is to discuss and document our experimentation with variable text placement for point labels, and to decide if we want to implement this in Mapbox GL as an alternative option to fixed label placements.

Generally, variable label placement is the ability for the renderer to assign different placements for a label in order to improve the likelihood that the label will be visible.

Implementing viewport label placement will unblock this ability. (Caveat: we can't implement this for api-gl, as cross-tile placements would not be predictable.)

Example placement workflow:

For each label:

  1. Try Placement Option 1.
  2. If using Option 1 will cause the label to not be placed (because it collides with an already placed label), try Placement Option 2.
  3. And so on through all specified placement options.
  4. If last placement option does not allow label to be placed, do not place label and move on to next label.

Benefits

Allowing variable label placements should result in label visibility that more closely matches a map designer's intent, as defined by the zoom level visibility and placement order of features in the data source + stylesheet. Specifically:

1. Improved label hierarchy

With a fixed label placement, it's common for a label that was attempted to be placed later (and therefore generally less important) to be visible while a label that was attempted to be placed earlier (and therefore generally more important) to be hidden. This is due to a collision between the label with earlier placement and another label with even earlier placement.

For example:

better-hierarchy

In this example, Label 1 is placed first. Label 2 is attempted to be placed second, but with the fixed placement, it collides with Label 1 and does not get placed. With a variable placement, using the second placement option (the right side) gives Label 2 space to be placed. Label 3 is attempted to be placed third, and with the fixed placement there is space for it to be placed since Label 2 was not placed. With the variable placement, Label 3 collides with Label 2 and does not get placed. Even though the number of labels visible is the same with fixed and variable placement in this example, variable placement improves the visibility of more important labels.

2. Improved label density

In addition, variable label placement can allow more labels overall to be visible, to more closely match the density of the data.

For example:

better-density

In this example, a fixed placement allows 2 out of 3 labels to be visible, while a variable placement allows all 3 labels to be visible.

Spec implementation

Text "placement" here refers specifically to a combination of 3 style spec layout properties:

  • text-anchor
  • text-justify
  • text-offset

Typical combinations of this are:
placement-options

Assuming that we want to allow users to control the variable placement options (which I think is fairly necessary for this to be useful), here's a few possible spec changes to consider:

Allow array values (stacks) for the 3 relevant properties

For these 3 properties, we could allow users to specify an array of values that are treated as a stack.

These 3 properties would need to be evaluated together, i.e. each array would be required to have the same length, and the corresponding position in each array would need to be applied together.

e.g. If these

  • text-anchor: [left, right, top, bottom]
  • text-justify: [left, right, center, center]
  • text-offset: [[0.5, 0], [-0.5, 0], [0, 0.5], [0, -0.5]]

Then the first placement option would be:

  • text-anchor: left
  • text-justify: left
  • text-offset: [0.5, 0]

Add new properties for text offsets

Our current implementation of the text-offset property uses x/y coordinates to define the offset, which means that offset distance and direction are defined together. We may want to explore the possibility of adding new properties that separate these two variables, e.g. text-offset-distance and text-offset-direction.

Benefits compared to using current text-offset property:

  • Easier to define variable placements
  • Easier to use a data field to define placement direction

Implementation considerations:

  • These require each other?
  • Use of these properties disables text-offset?
  • options for values:
    • text-offset-distance:
      • em (same as existing text-offset unit)
      • px
    • text-offset-direction:
      • not a property; offset direction automatically follow text-anchor
      • enum: same as the 9 text-anchor values + auto (follows text-anchor))
      • degrees (angle of rotation)

e.g.

  • text-justify: [left, right, center, center]
  • text-anchor: [left, right, top, bottom]
  • text-offset-distance: 20
  • text-offset-direction auto

Challenges

  • Performance impact of doing more placement calculations per label
  • Lack of parity between GL maps and raster maps created with api-gl (I'm currently looking into how much of an issue this might be for users)
  • Label instability
    • With the viewport label placement refactor, we'll be calculating placements much more frequently than our current once-a-zoom-level strategy – about every 50ms. Doing variable label placements so frequently may cause more instability, since placements may change every 50ms (and as always, a label placement/visibility change can cause cascading effects for labels placed after it). However, the new viewport label placement implementation also creates cross-fading for labels in all situations, which makes label changes much less jarring. Instability may therefore not actually be a big issue.
    • If it is an issue, @ansis and @ChrisLoer have discussed the possibility of doing label placement prioritization based on labels that are visible for the floor zoom level. Using text-optional: true for dense, low priority point labels (e.g. small point-of-interest like stores and restaurant in Mapbox Streets vector tiles) may help with stability, particularly during rotation.
  • Same offset distance for different placements will not result in the same visual offset distance. For example, corner placements (top-left, etc) would look further offset. We would need to account for this.
  • Potentially unclear relationships between text and icons (more of a map design challenge than a rendering challenge).

Next steps

1. Prototype

@mollymerp is working on an initial mapbox-gl-js prototype in this branch, with placement logic hardcoded in the renderer.

2. Compare prototypes to fixed placement styling

Using two sets of features in Mapbox Streets vector tiles as case studies, I plan to compare the initial prototypes to our current fixed placement styling (using the new viewport placement implementation):

  • City labels with data-driven placement values.

    • We have a data field that defines the optimal placement as one of 8 possible directions. For the largest cities, these values were manually chosen based on visual inspection.
  • POI labels with a single placement value

    • Our POI label data, like most of our point label data (and probably most of our customers') do not contain any information about optimal placement, so we just place all POI text in one direction (below the icon).

3. Document more use cases

I've been mostly focused on evaluating variable label placement for use with our Mapbox Streets vector tiles. If you have a real or hypothetical use case for this, please comment below!

4. Explore options for spec implementation

I included a few initial ideas here, and will continue to round up feedback, alternatives suggestions, and important considerations.

Other variable placement options for future consideration

  • Vertical orientation for point labels as a variable placement option (particularly for Japanese maps)
  • Variable placements for line labels

/cc @mollymerp @ChrisLoer @ansis @mapbox/gl-core @ajashton @ericwolfe @peterliu
(Please cc anyone else who might be interested in this topic.)

@nickidlugash nickidlugash added cross-platform 📺 Requires coordination with Mapbox GL Native (style specification, rendering tests, etc.) needs discussion 💬 labels Jul 24, 2017
@stevage
Copy link
Contributor

stevage commented Jul 25, 2017

A feature like this existed in CartoCSS (implemented by TileMill), which I used quite a lot. One tweak I would like to request is the ability to tweak the workflow:

  1. Try Placement Option 1.
  2. If using Option 1 will cause the label to not be placed (because it collides with an already placed label), try Placement Option 2.
  3. And so on through all specified placement options.
  4. If last placement option does not allow label to be placed,
    4a. If "text-alway-place" is false, do not place label and move on to next label.
    4b. If "text-always-place" is true, place the label at Placement Option 1, even though it collides.

My preference was generally that I want every label to be shown (especially important for static maps), so I want the mapping engine to try to avoid collisions, but to fallback to placing with a collision if necessary. CartoCSS only gave the ability to either avoid collisions (but maybe get no label) or allow collisions (and make no attempt to avoid them), so it'd be great to have a middle option.

@nickidlugash
Copy link
Author

Thanks for the feedback, @stevage! I'm not sure how straightforward or difficult that ability would be to implement – @ansis or @ChrisLoer would have a better sense. It might be straightforward to attempt adding the feature one more time after all placement options fail, with the first placement option and as if text-allow-overlap:true was assigned. (On the spec side it could perhaps be handled just by assigning text-allow-overlap: true along with the variable placement assignments, rather than a new property).

@stevage
Copy link
Contributor

stevage commented Jul 29, 2017

On the spec side it could perhaps be handled just by assigning text-allow-overlap: true along with the variable placement assignments, rather than a new property

Yeah, good thought. There are definitely use cases for having that flag on or off. General purpose maps for mass audiences probably value looking nice and avoiding any label collisions. Specialist maps for niche audiences probably value getting all the information shown one way or the other.

@mb12
Copy link

mb12 commented Aug 17, 2017

@nickidlugash Can the unit of text-offset be specified in terms of the icon next to which text is being placed instead of ems? In all the examples you have included above assume that the icon is smaller than 1emx1em. The style will break, the moment one decides to use larger icons.

This is an issue even today if the style uses larger icons. It would be nice if one could create robust "place text below icon layers" in the style that do not depend on icon size in ems.

@nickidlugash
Copy link
Author

Can the unit of text-offset be specified in terms of the icon next to which text is being placed instead of ems?

@mb12 An ideal unit for text offset is tricky because in practice, the ideal offset often needs to be relative to both the icon size and the text size (or line width and text size, for offset line labels). Currently you can use icons larger than 1emx1em since you can specify a value of larger than 1, but would require a property function value if you want to use a variety of icon sizes within a single style layer. There are definite pain points to specifying offset values right now, as you've highlighted, so if we do consider implementing a new offset property, we'll need to take this into consideration. I suggested pixels as a alternative unit with the thought that an absolute unit in conjunction with the upcoming expressions syntax might make it easier to take into account both icon/line and text size.

@andrewharvey
Copy link
Collaborator

This would tie in nicely with #6432 to ensure labels are drawn on screen if they would otherwise be cut offscreen.

@ChrisLoer
Copy link
Contributor

We have a PR up implementing this! Please check it out: #7596

@stevage the PR doesn't include the text-always-place flag you suggested, although there's nothing that would prevent us from doing that in the future. I think it's mainly a style-spec design question, as far as implementation goes it would be relatively straightforward.

@ChrisLoer
Copy link
Contributor

Implemented in #7596. 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cross-platform 📺 Requires coordination with Mapbox GL Native (style specification, rendering tests, etc.) feature 🍏 needs discussion 💬
Projects
None yet
Development

No branches or pull requests

8 participants