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

Declarative styling #2

Open
49 of 85 tasks
pjcozzi opened this issue Jun 15, 2015 · 94 comments
Open
49 of 85 tasks

Declarative styling #2

pjcozzi opened this issue Jun 15, 2015 · 94 comments
Assignees

Comments

@pjcozzi
Copy link
Contributor

pjcozzi commented Jun 15, 2015

Short-Term

Spec

@ggetz

  • Better name than "3D Tiles Style"
  • Hackathon to experiment with current version

@pjcozzi

  • TODOs in spec
  • Finish spec and schema

Cesium Implementation

@ggetz

  • Latest schema for color map and color ramp
    • Remove BooleanExpression, NumberExpression, and friends
    • Unit tests: Cesium3DTileStyle (and implicitly for Cesium3DTileStyleEngine)
  • Load style from uri, not just JSON string.
  • Pass styleEngine last to constructors (and make it optional?)
  • Example evaluating a style/expression on pick
  • Remove chroma - 18d80354be225a116d0a711bb79b190871a82534 and e7aebcddacdcdcc00bb5120215d7f248a5acb29a (and ColorRampExpression.computeEvenlySpacedIntervals)

@pjcozzi

  • Reference doc
  • Update CHANGES.md
  • Run coverage and see if we are missing any tests for 3D Tiles styles

Later

Spec

  • Add JulianDate datatype to support styling with date/time metadata
  • Introduce translucency property to assign to without having to set RGB?
    • And/or built-in variables to get the feature's current color and show?
  • Operator overloads for regular expressions (we already overload for Color so why not here?), see Declarative styling #2 (comment)
  • Allow variable names inside member expressions, eg. ${foo[${memberName}]}
  • Non-visual properties, e.g., for infobox display, e.g.
{
    "show" : true,
    "color" : [255, 255, 255],
    "meta" : {
        "description" : "Hello, ${FeatureName}"
    }
}
var str = style.meta.description.evaluate(feature);
  • Escape variables in strings.
  • In general, is the ability to assign to feature properties in the style (as part of the JSON or maybe expression) useful?
    • Improved "standard library"
  • Cesium built-in math functions, e.g., distance
  • Check out GLSL
    • Built-ins like GLSL, e.g., czm_time, czm_inShadow, etc.
    • Include GLSL in a style?
    • Cascading multiple styles on one tileset?
    • Add explicit interval to optimize conditional when used for intervals
    • Referencing other features in a style
color : ramp(palette, distance(${thisFeature.location}, ${anotherFeature.location}))
show : (${thisFeature.height} > ${anotherBuilding.height})
  • Multiple expressions in conditions, Declarative styling #2 (comment)
  • Generate reference doc from JSON schema
  • Core styling schema + extensions for buildings, vector data, point clouds, etc.?
  • Influence from CZML, i.e., time-dynamic styles
    • Integration with Cesium Property system, e.g., evaluate expressions as Property or vice-versa.
  • Other style languages, e.g., more like CSS selectors
  • Support vector and matrix properties since this is supported in the batch table binary.
  • Bounding volume hierarchy information ([Cesium3DTileStyleEngine] Missing support of access to values which are not in batch table, but present cesium#4654)
    • Tile bounding volumes
    • Level in tree
  • Support vector types like vec2, vec3, and vec4
  • Allow component-wise math operations similar to GLSL

Cesium Implementation

  • Interfaces for Cesium3DTileStyle and friends for users writing their own styles.
  • Fix jsep to allow for single backslash characters to be parsed correctly. Workaround is to replace before jsep for now.
  • Column numbers for syntax errors
  • Use regex and replace function for replaceVariables in Expression.js?
  • GPU implementation for tile formats that would really benefit, e.g., point clouds
  • Line width (can we make this as simple as a uniform/batch-value and not recreate geometry?)
  • Expose AST?
  • Expose Conditional properties?
  • Apply to GeoJSON?
  • Optimizations if needed: constant folding and dead code elimination
  • Schema validation - how much do we do at runtime? Tool for offline validation?
  • Style features in parallel

Built-in functions

  • radians(x) - convert value to radians
  • degrees(x) - convert value to degrees
  • cos(x)
  • sin(x)
  • tan(x)
  • asin(x)
  • acos(x)
  • atan(x)
  • atan2(y,x) - atan(y,x) in GLSL
  • pow(x,y)
  • sqrt(x)
  • abs(x)
  • sign(x)
  • floor(x)
  • ceil(x)
  • round(x)
  • min(x,y)
  • max(x,y)
  • clamp(x,y,z)
  • mix(x,y,a)
  • length(x)
  • distance(x,y)
  • dot(x,y)
  • cross(x,y)
  • normalize(x)
  • random - 0.0 to 1.0 (some complexity on the GLSL side)
  • exp(x)
  • log(x)
  • exp2(x)
  • log2(x)
  • fract
  • time
  • Consider data-driven implementation to avoid lots of duplication where the only difference is the function name and number of arguments.
@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 5, 2016

The immediate need for declarative 3D Tiles styling is to be able to write a simple expression that maps a feature's property (or properties) value to its appearance (initially, show/color), e.g.,

  • "Show all buildings greater than 10 meters tall"
  • "Use a color ramp to color buildings based on the number of floors"

There's early implementation work in Cesium's 3d-tiles-style branch.

The styling format is JSON, and the schema is not at all final. Here's a few examples:

// Show all buildings greater than 10 meters tall, e.g., `${Height} > 10`
{
    "show" : {
        "leftOperand" : "${Height}",
        "operator" : ">",
        "rightOperand" : 10
    }
}
// Create a color ramp  with the provided palette based on the number of floors in a building
{
    "color" : {
        "expression" : {  // Will make this less verbose soon, just ${Floors}
            "leftOperand" : "${Floors}",
            "operator" : "+",
            "rightOperand" : 0
        },
        "intervals" : [
            1,  // [1, 10)
            10, // [10, 19)
            20  // [20, infinity)
        ],
        "colors" : [
            [237, 248, 251],
            [158, 188, 218],
            [129, 15, 124]
        ]
    }
}
// Map a feature's ID to a color
{
    "color" : {
        "propertyName" : "id",
        "map" : {
            "1" : [255, 0, 0],
            "2" : [0, 255, 0]
        },
        "default" : [255, 255, 255]
    }
}

In Cesium, a style can be applied to a Cesium3DTileset object, e.g.,

tileset.style = new Cesium.Cesium3DTileStyle(tileset, styleJson);

Creating the Cesium3DTileStyle object basically "complies" the style, and assigning it to tileset.style tells the tileset to apply the style (efficiently based on visibility and if the style or feature properties have changed).

After the style is compiled, it can be changed, e.g.,

tileset.style.show.operator = '===';

Feature properties that may impact how the style is evaluated can also be changed, e.g.,

feature.setProperty('id', 1);

In addition to declarative styling, the lower-level API can be used to override the appearance of individual features, e.g.,

var feature = scene.pick(movement.endPosition);
if (Cesium.defined(feature)) {
    feature.color = Cesium.Color.YELLOW;
}

Outdated, but still useful:
img_0350

Schema Ideas

  • Shorthand schema for literal boolean expression for show
  • Use CSS color formats throughout
  • Add RegEx pattern to color ramp
  • Resolve an expression (including reference to a property name) to a color (using optional regex)
  • Replace expression JSON with real expressions, e.g., ${Height} * 2 > (${Width} + 1) / 3. The JSON becomes the AST, which is (initially if not always) not part of the spec, but rather a Cesium implementation detail.

Related-ish

@pierotofy
Copy link
Contributor

I'd much rather use a concise syntax (e.g. "{Height} > 100") than individual properties, which would get really messy for complex rules.

A simple parser can transform the concise syntax into the equivalent structure in a precompilation step.

Something that I could see being used is also the ability to specify styling programmatically, so maybe define an abstract "Cesium3DTileStyleFormatter" interface in Cesium that can be implemented by the developer to specify the styling at runtime, e.g.:

var sf = new MyStyleFormatter();
// ...
tileset.style = new Cesium.Cesium3DTileStyle(tileset, sf);

Where MyStyleFormatter implements:

show: function(tile){
    return tile.Height > 100;
}

It could be useful to do things like:

color: function(tile){
   return new StaticColor(Math.random() * 255, Math.random() * 255, Math.random() * 255);
}

To color buildings at randoms (and other possibilities).

In fact a JSON style could be a special case of programmatic styling:

var sf = new JsonStyleFormatter(styleJson);
// ...
tileset.style = new Cesium.Cesium3DTileStyle(tileset, sf);

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 5, 2016

Thanks for the input, @pierotofy. We definitely want to support custom functions for expressions, and, I agree, will most likely go with the concise syntax. Thanks for the code snippets!

@mramato
Copy link

mramato commented Feb 5, 2016

This is one area where I think Cesium's current Property system can be
leveraged to create some powerful capabilities. I'm not saying the
Property system exactly as it is today is a perfect fit, but ultimately I
personally feel it's where things will go. I've wanted to add additional
Property capabilities for a while and this may be a good reason to look
into that.

On Fri, Feb 5, 2016 at 10:16 AM, Patrick Cozzi notifications@github.com
wrote:

Thanks for the input, @pierotofy https://github.com/pierotofy. We
definitely want to support custom functions for expressions, and, I agree,
will most likely go with the concise syntax. Thanks for the code snippets!


Reply to this email directly or view it on GitHub
#2 (comment)
.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 9, 2016

Update #2 (comment) to account for the latest implementation work and offline discussion with @mramato.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 10, 2016

The styling spec will be in the 3d-tiles/spec branch (just a placeholder for now). I'll bootstrap the prose/schema writing as things start to solidify.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 13, 2016

Notes on parsers

We'll start with jsep.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 14, 2016

Notes on expressions

Now

  • Types
    • Number
    • Boolean
    • String
      • Might be a jsep bug with escaped quotes, e.g., "a \"b\" c"
    • Color (CSS color format)
    • Variable (feature property) - resolves to Number, Noolean, String, or Color (or null I suppose)
  • Short-circuit expressions
  • Member expressions
    • Spec
    • Implementation
  • Arrays
  • Boolean, Number, String constructors
    • Spec
    • Implementation
  • RegEx
    • Implementation
    • Spec
    • toString
  • Evaluate colors take non-literal arguments in constructor
  • Constructor function changes
  • Fast path for well-known expressions, e.g., ${Property} + literal
  • Remove debugging console.log statements
  • Examples / test cases (good start, but not complete):
"show" : 'a // Syntax error
"show" : true // Boolean literal
"show" : !false // Unary NOT
"show" : true && true // Logical AND
"show" : false && true // Logical AND, short-circuit
"show" : false || true // Logical OR
"show" : true || false // Logical OR, short-circuit
"show" : (false && false) || (true && true)
"show" : true ? true : false // Conditional, short-circuit-ish
"show" : false ? false : true // Conditional, short-circuit-ish
"show" : 2 > 1 // And similiar for <, <=, >=, ===, !===
"show" : 0 > -1 // Unary NEGATE
"show" : (1 + 1) > 1 // *, /, %
// For now, do not support:
//   * Unary: ~, +
//   * Binary : |, ^, &, ==, !=, <<, >>, >>>
//   * Expressions
//     * Array, e.g., [1, 2]
//     * Compound, e.g., 1; 2; 3
//     * Member, e.g., feature.name // might need this soon
//     * This, e.g., this
// See node types, operations, and literals in the annotated source: http://jsep.from.so/annotated_source/jsep.html
"show" : "${SomeFeatureProperty}" // Feature property is a boolean
"show" : "${ZipCode} === 19341" // Variable equals number
"show" : "${Temperature} > 100"
"show" : "(${Temperature} > 100) && ((${Weight} / ${Area}) > 2.0)"
"show" : "${County} === 'Chester'" // Property name equals string
"show" : "${County} === regExp('/^Chest/')" // String compare with RegEx. Open to other syntax
"show" : "${County} !== null" // I guess we should support null, what about undefined?
"show" : 1 // Convert number to boolean I suppose
// CSS colors
"color" : "#EAA56C"
"color" : "cyan"
"color" : "rgb(100, 255, 190)"
"color" : "hsl(250, 60%, 70%)"
"color" : "(${Temperature} === 'hot') ? 'red', '#ffffff'"
"color" : "rgb(255, 0, 0, (${Visible} ? 255 : 0))"
"color" : "rgb(${red}, ${green}, ${blue}, 255)" // Number properties
"color" : "rgb(${red} * 255, ${green} * 255, ${blue} * 255, 255)" // Convert 0..1 to 0..255

@pierotofy
Copy link
Contributor

For RegEx syntax we could also use (borrowed from Perl/Ruby):

"show" : "${County} =~ /^Chest/" // Matches
"show" : "${County} !~ /^Chest/" // Does not match

null and undefined should probably be both supported (and enforce strong typing):

"show" : "${County} !== null && ${Country} !== undefined"
"show" : "${County} !== null" // Does not match undefined values
"show" : "${County} !== undefined" // Does not match null values

I would vote in favor of not allowing casts from numbers to bools, just to enforce better code practices, but it's not a big deal if they are allowed.

"show" : "1" // Error: "Expected bool, got number"
"show" : "${param} === 1" // OK

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 15, 2016

Thanks @pierotofy, @ggetz is starting the design/implementation now. We'll look at these cases; I'm not sure about introducing =~ yet though as we want to map to JavaScript as best as we can.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 18, 2016

@ggetz check out the new Color section in the styling spec and let me know what you think about the TODOs (just open PRs for them) and please make sure our implementation and spec are in-sync and that we reasonably covered the edge cases.

@ggetz
Copy link
Contributor

ggetz commented Feb 18, 2016

I added my comments in #70

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 19, 2016

@ggetz there is a new section on Number. Let me know your thoughts and please bring our implementation in sync. Also note that isNaN() is true and isFinite() is false due to implicit conversion to Number.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 19, 2016

@ggetz there is a new section on conversions. Please make sure to test all possible Color conversions carefully, e.g., Color() !== undefined, Color() !== null, !Color() === false, etc. We want the semantics to be the same as if we were using the Cesium Color object in JavaScript.

Note that we have to add a toString() function to Color for implicit conversion to string (or explicit via Color().toString()). We'll have to add a few more functions to make it walk and talk like a real JavaScript object. More info to follow.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 19, 2016

@ggetz for strings, make sure we parse both ' and ".

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 19, 2016

@ggetz there is a new section on operators. Please make sure our implementation is in-sync (in particular I added the unary + and I don't know if we implemented ternary yet) and carefully tested.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 19, 2016

@ggetz are we in-sync with this:

Color supports the following binary operators by performing component-wise operations: ===, !==, +, -, *, /, and %. For example Color() === Color() is true since the red, green, blue, and alpha components are equal.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 19, 2016

Note that we have to add a toString() function to Color for implicit conversion to string (or explicit via Color().toString()). We'll have to add a few more functions to make it walk and talk like a real JavaScript object. More info to follow.

I looked more at this. Just toString() is fine for our current purposes. See Section 19 here if interested.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Feb 19, 2016

@ggetz do you want to design the regex support?

@ggetz
Copy link
Contributor

ggetz commented Feb 19, 2016

Color supports the following binary operators by performing component-wise operations: ===, !==, +, -, *, /, and %. For example Color() === Color() is true since the red, green, blue, and alpha components are equal.

Yes this is implemented

@ggetz
Copy link
Contributor

ggetz commented Feb 19, 2016

do you want to design the regex support?

Sure

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Apr 6, 2018

@ggetz is there anything here critical for 1.0?

@ggetz ggetz removed the 1.0 label May 11, 2018
@ronanmcnulty
Copy link

Is there anyway to color based on whether the lat & lng are within a polygon?

@ggetz
Copy link
Contributor

ggetz commented Nov 14, 2018

@ronanmcnulty It sounds like classification might fit your use case better.

@ThornM9
Copy link

ThornM9 commented Jun 10, 2019

Thanks for the input, @pierotofy. We definitely want to support custom functions for expressions, and, I agree, will most likely go with the concise syntax. Thanks for the code snippets!

Were custom functions for expressions ever implemented @pjcozzi ? I'm attempting to color a point cloud by elevation but there's no height attribute for point clouds.

@pjcozzi
Copy link
Contributor Author

pjcozzi commented Jun 18, 2019

@ThornM9 no but there may be another way to achieve your use case, the best place to ask is the Cesium forum: https://groups.google.com/forum/?hl=en#!forum/cesium-dev

@conradlempert
Copy link

conradlempert commented Jun 20, 2020

I noticed that atan2(y,x) is more inaccurate than atan(y/x). (While atan(y/x) only works for x > 0 of course). In my particular use case I used the atan2 function to compute the longitude of points in the tileset.

The resulting longitude from atan2(y, x) was off by about 0.00001 (ca. 10 meters), while it looked perfect with atan(y/x).

Unfortunately I don't know how to tell you the exact resulting numbers since they are only in the GPU I guess, I can only give this estimate from what it looks like.

My cesium version is 1.63, however this was reproducable in a sandcastle.

Sandcastle:
https://sandcastle.cesium.com/#c=lVX7TyM3EP5XRtFJbErq7CabB081B7SNRAGRcKeTIiFnd0JcHDu1vYSA+N873kcIHHdqfyCsxzPfN283m1daKAcnUmcpTNdwZfSMwRU6NDCQElUDTrTMFlPB4UaJBzRWuDVc66l2IrFwzqcMRglXSqg7DzCQ+DdXqdEwNjpJtBRAR/iLOxRwInTCjRTIJuqBG3gQuCKiI1C4ghO0IluwL7ksmNSS/HyileNCoZnUGvA8UQDkmiEJefogUjT7lWFikEi+aiPTcaES1BsT9VI/mKiCzgmJFt1bvuJf+3RcXAY5R2bkBneo1TVanZkE2czoxcCS2jANom7cijYMRSjMJqiQLY1YCEfZsoynaVDyFo4kWlkHKVonFHdCK3LnnXnCF2g4IxOfjMKjLYP9N+5z4+iLq3bg1QDiOIx6ccy6cRzFnXYUNwp5q9UJOxGLo7DX34uisNMqL+LOXiduhazdi3v9OG63vbieX2ojkNL/HemfyFOq95VwyfxaS1lSd4i022u3+2EnbO/1Or2S4deQtXrdbtiLOu1+e6/f75YXXdbqR+RLv9eJwzDsdyrqsmhl4hhVNl1TwRfCInNzVMEsU4l3a5PbojeK5C7znj6q6s2mOlPe39FyjobSSzGhyQmgbEFGIipYWa/nwjsgIOr2PPgcsvEqp9M+bPRymXhEORJPuA9R2Ni+oSHQr2164k/s+vLb4Pzz+c3ZG81UWD6VeIpLNx9TwU+FdVwlBHlBI0huXl2OhuPhl7Pb4cXvw4vh+Ftl/eI/8qwB+L9mE4Zwr/QK3Jw7+hEW7gUNop6B1OpOuCzFpqTS+g9ycbHMikIDaSrtgEZ9DUuDCeW8UULO9QpJDmudAc08WMQCXzigX4mcsi+1vreVJcw0zd0cX0nfcK2Em5MlV8G6+VhnJc1AWt3wmHauM0l7Ccntmd8uVE/rGcnS064wdyGvahW5H3RD7SlG/2TcYEp9UGb+TEqxtFqk7Osfo37MbrXCSwrnekv7oIJQ2iy4pHKmV2U37WxEwQMm7eDTc1GNy4vbwefR5fnN+OyFPcIvsAO7bzwg4S7sNLx8u9iw8yHC+iOE9f9BePoI4ckj1Os7B9t52hRlQCXwe6iW12JSI+X3CdilW7Zu/vjusT6pHXyI3NpAt36G3fgP2BW+dWuJBFuOYIozeiPs1khSG13jUvIE3/uS99zbyH0DgdNlPyPN4WxGIpq8Cm6jv/8ObjO/1Szt+1Ct+FkWnyiawuylNKdGX3nDT88b9Bc4hhwi2Nr0+s7w5Vwk+UO0Wf1Bvo/qbGNbz7dBnrBqB1YJ+8HLN/LXQa5ULZF8m9QatcNcelzF+ZtYLLVx/okMGGs6XFCWyY/mNEvuiSixtkAAOGxumx6m4gFEevTByw6J5NbSzSyT+Q6d1I4Pm6T/nanU+cvjx1bytVebR8fnhZAxdtik48eWTms55eYd8r8

@lilleyse lilleyse removed the next label Nov 15, 2021
@lilleyse
Copy link
Contributor

See #179 for optimizing large number of conditional expressions.

@lilleyse
Copy link
Contributor

See #239 for batching vector images into a texture atlas that's part of the payload

@lilleyse
Copy link
Contributor

See #252 for mutables (like GL uniforms)

@lilleyse
Copy link
Contributor

See #266 for styling alpha separately

@lilleyse
Copy link
Contributor

See #268 for accessing feature ids in the styling language

@lilleyse
Copy link
Contributor

See #292 for accessing instanced attributes in the styling language

@lilleyse
Copy link
Contributor

More considerations for 3D Tiles Next:

  • Can styles access metadata at different granularities including tileset, tile, and group metadata. We support this in CesiumJS.
  • Do properties need to be fully qualified based on their class id? e.g. ${classId.propertyId}? Or only when there are collisions?
  • Can property textures be styled? Would styling need to happen on the GPU in this case?
  • Should geometry attributes like position, normal, etc be available as built-ins and not just for point clouds?
  • Do styles have access to statistics?
  • Do styles have access to class min/max properties values? Both are needed to do color ramps. Statistics are good for localized color ramps, class min/max is good for global color ramps (think a color ramp from the Mariana trench to Everest)
  • Do styles have access to enums? ${buildingType} === BuildingType.HOSPITAL

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

No branches or pull requests

9 participants