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

More layer matching control with priority and exclusive #2036

Closed
nvkelso opened this issue Feb 14, 2019 · 3 comments · Fixed by #2046
Closed

More layer matching control with priority and exclusive #2036

nvkelso opened this issue Feb 14, 2019 · 3 comments · Fixed by #2046
Assignees

Comments

@nvkelso
Copy link
Member

nvkelso commented Feb 14, 2019

Tangram ES sister issue to PR #705.

What @bcamper said over there:

This PR introduces two new keywords -- priority and exclusive -- for scene layers, to enable greater control over feature filter matching and styling composition.

Background
Tangram layer matching has always been inclusive, meaning a feature can match multiple layers (at multiple levels of the layer style tree), which are then merged to determine the final rendering parameters. Composing complex sub-layer trees can be used to capture both feature and zoom-dependent styling along orthogonal dimensions, for example varying road line widths and colors by separate criteria.

However, two in many ways simpler behaviors have not been easily expressed with the current layer syntax:

  • The priority with which layers are matched has always been implicit. While layer depth takes priority (with an inheritance model), when layers at the same level match (siblings), the tie-breaker has been alphabetical, with the last lexically sorted layer name winning. This is cryptic at best and a significant hindrance to easy authoring where layer priority is important (see Bubble Wrap shield matching).
  • Since multiple sibling layers will match by default, there has been no way to express simple exclusive relationships, for example an ordered list of filter conditions, where the first matching filter should be used, and the others discarded. While it has often been possible to work around this by carefully building filters to be functionally exclusive (for example, defining a filter A, then defining a filter B that negates all of A's conditions, in addition to including its own), this is also an unwieldy approach, and does not serve all cases (such as when the filters have natural overlaps that can't easily be separated in a way that avoids a feature from matching more than once).

New Syntax
The priority and exclusive keywords address these cases, and can be used both separately and together.

  • priority: allows for the explicit definition of the matching order for sibling layers (alphabetical by layer name is still used as the default / fallback).
  • exclusive: marking a layer as exclusive ensures that when that layer matches, no other sibling layers (and their cascading sub-layers, etc.) will; when multiple exclusive layers match, the higher priority layer wins.

For example, exclusive could express a single if-else filter condition:

layers:
  if:
    filter: ...
    exclusive: true
  else:
    ...

When used together, priority and exclusive can be used for chained if-elseif-else filter cases, to ensure that only one of an ordered set of conditions matches:

layers:
  layerA:                 # if matches layerA...
    filter: ...
    priority: 1
    exclusive: true
    draw: ...
  layerB:                 # else if matches layerB...
    filter: ...
    priority: 2
    exclusive: true
    draw: ...
  layerC:                 # else default to layerC...
    priority: 3
    exclusive: true
    draw: ...

Separate from "flow control" cases, priority can simply be used on its own to disambiguate the precedence of multiple layers without reliance on naming conventions.

Implementation
The implementation is a fairly narrow impact on the layer matching logic:

  • When determining layer matching and merging order:

    • Layers with an explicit priority value are matched first, with values sorted from lowest to highest (e.g. priority: 1 is matched before priority: 2).
    • Layers without an explicit priority are matched after, and continue to be sorted lexically.
  • Matching operation:

    • If a layer marked exclusive matches, all further matching at that level in the layer tree ceases and the matching layer is used (matching continues for that exclusive layer's child/descendant layers).
    • In the case that multiple layers are marked exclusive, the first one (according to the order described above) is used.
@matteblair
Copy link
Member

Now that I'm implementing this for Tangram ES, I've found a potentially unintuitive loophole of sorts. Let's say we have some layers like this:

layers:
  layer_a:
    layer_aa:
      priority: 2
      draw:
        group_0:
          color: red
  layer_b:
    layer_bb:
      priority: 1
      draw:
        group_0:
          color: blue

A feature will match both layer_aa and layer_bb. layer_bb appears to have precedence because of its priority value, but priority actually has no effect here because it only establishes ordering of layers within a parent and both layer_aa and layer_bb are the only layers in their respective parents.

We've recognized this problem before and decided to disambiguate cases like this using the lexical order of the layer's fully-qualified name. In this case, that would mean that layer_aa takes precedence (because layer_a:layer_aa precedes layer_b:layer_bb).

@bcamper Does this track with your understanding of the feature? And does Tangram JS do the same disambiguation for layers at the same depth that aren't siblings?

@bcamper
Copy link
Member

bcamper commented Mar 15, 2020

@matteblair thanks, it mostly does :) However, in this case, what I would expect and observe in Tangram JS is that the draw parameters from layer_bb are what are actually applied. The reason for this (good or bad, mostly a historical artifact) is that the layers are first sorted and then applied lexically. So since layer_b:layer_bb gets sorted last, it also gets applied/merged into the draw group last. (That's why in some Mapzen styles you see z- name-spaced layers, to force them to "win" 🙈.)

This is also referred to in the original PR quoted above:

The priority with which layers are matched has always been implicit. While layer depth takes priority (with an inheritance model), when layers at the same level match (siblings), the tie-breaker has been alphabetical, with the last lexically sorted layer name winning.

I agree about the potential confusion in cases such as this. For this initial round, the syntax was enforced amongst a single group of sub-layers. I'm totally open to revising the logic (since AFAIK this feature is still pretty nascent in terms of actual use) if you have other suggestions?

I guess it would be possible to apply the priority across all layers a given feature matches. This could be more intuitive, but it also seems like it may be less "stable" in the sense that you can have many different combinations of sub-layer matches, depending on the different filters, whereas at least with the priority scoped to a single sub-layer you can have confidence that the priority is being applied only to a set of features conform to that layer's filter. Does that make sense? (It doesn't mean one is necessarily better or worse, just trying to wrap my head around it.)

@matteblair
Copy link
Member

Oook, getting back to this now. I was kind of muddling a few statements together in the previous post.

  1. When resolving layer order by name, I had mis-remembered the direction of alphabetical sorting that we'd chosen to use. Tangram ES in fact takes the parameters from layer_bb in that case. This is consistent with the new sorting behavior too, which is what I was trying to figure out.

  2. Priority sorting can be misleading for layers that are at the same depth but are not "siblings". I agree that there's value to being able to reason locally about a branch of the layer tree, so I'm not sure that applying priority globally is a good idea either.

Now I've got a good handle on this feature. The PR to add this should be up very soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants