mapbox / carto

fast CSS-like map stylesheets
https://cartocss.readthedocs.io/
Apache License 2.0
654 stars 129 forks source link

Allow setting of properties without generating rules #462

Closed pnorman closed 7 years ago

pnorman commented 7 years ago

A common problem when you want to set some secondary properties (e.g. color) in common to multiple selectors, but the enclosing block may not be exclusive. An example based on recent work is

#foo {
  [feature = 'bar'] {
    line-color: red;
    line-width: 2;
  }
  [feature = 'baz'] {
    line-color: blue;
    line-width: 2;
  }
}

In real-world examples instead of just one line-color line there can be 5-10 properties set for all features, and dozens (or much more, for osm-carto) of selectors for different features.

A logical syntax to try is moving the line-width up a block to be common:

#foo {
  line-width: 2;
  [feature = 'bar'] {
    line-color: red;
  }
  [feature = 'baz'] {
    line-color: blue;
  }
}

But this doesn't work, because it generates rules for feature=bar, feature=baz, and feature != bar or baz, or the same as

#foo {
  line-color: black; // default value
  line-width: 2;
  [feature = 'bar'] {
    line-color: red;
  }
  [feature = 'baz'] {
    line-color: blue;
  }
}

Proposal

I propose allowing overriding the default value with none

#foo {
  line-color: none;
  line-width: 2;
  [feature = 'bar'] {
    line-color: red;
  }
  [feature = 'baz'] {
    line-color: blue;
  }
}

This would then not generate rules for where line-color: none applies.

Workarounds

I'm aware of two common workarounds

@foo-line-width: 2
#foo {
  [feature = 'bar'] {
    line-color: red;
    line-width: @foo-line-width;
  }
  [feature = 'baz'] {
    line-color: blue;
    line-width: @foo-line-width;
  }
}

This is what OSM-Carto uses. It makes it easier to change, but still has the same duplicate lines, and adds lots of variables.

#foo {
  [feature = 'bar'],
  [feature = 'baz'] {
    line-width: 2;
    [feature = 'bar'] {
      line-color: red;
    }
    [feature = 'baz'] {
      line-color: blue;
    }
  }
}

This avoids the extra rule, but then the rules are duplicated and need to be kept in sync, which is prone to failure. It also makes the MSS more complex.

Impacts

For a real-world example of the types of selectors, see https://github.com/gravitystorm/openstreetmap-carto/blob/master/amenity-points.mss. I estimate it would save 400 lines from .points in that file and 300 lines from text, reducing the file size by a third.

nebulon42 commented 7 years ago

This comes from Mapnik, but I think the issue behind this is that some properties have implicit default values. Such as line-color has black or line-width has 1. It would also help if not specifying default values would not lead to rules. There are other cases where an error is thrown instead e.g. for text-face-name or shield-face-name.

Implementation-wise your proposal might be easier.

pnorman commented 7 years ago

the issue behind this is that some properties have implicit default values

Yes - the point of the proposed none. Not generating rules when the "primary" property is unset (e.g. not generating a LineSymbolizer when line-width is unset) would also work, but then it'd be good to have a way to unset a property, and that would probably have to be a 1.0 change as it could break a number of styles.

HolgerJeromin commented 7 years ago

This would prevent some bugs with missing attributes, too.

nebulon42 commented 7 years ago

Related: #214

Zverik commented 7 years ago

I agree with @nebulon42: setting default values to none and introducing this value should make style creation less wtfy.

nebulon42 commented 7 years ago

Also related: #18.

nebulon42 commented 7 years ago

I started thinking about this. Just to recap as #214 was already implemented: You are already able to write e.g. line-join: none to reset the line-join property for the specific line symbolizer. That means that the attribute stroke-linejoin will not show up in the XML. For the specific example of @pnorman with line-color: none this would mean that now there is still a line symbolizer created, because there is also the line-width property.

The problem with not generating a symbolizer when some rule is set to none is the question which rule to use? What is the most important property of a line? What defines a line/line symbolizer? Is it line-color or is it line-width? Is this defined by Mapnik or does CartoCSS has to think about something?

Maybe it would save some confusion if we would not specify some attribute of the symbolizer but reference the symbolizer itself like line: none or marker: none. In contrast to line: auto like proposed in #18.

Thoughts?