CesiumGS / 3d-tiles

Specification for streaming massive heterogeneous 3D geospatial datasets :earth_americas:
2.09k stars 463 forks source link

Declarative styling #2

Open pjcozzi opened 9 years ago

pjcozzi commented 9 years ago

Short-Term

Spec

@ggetz

@pjcozzi

@ggetz

@pjcozzi


Later

Spec

{
    "show" : true,
    "color" : [255, 255, 255],
    "meta" : {
        "description" : "Hello, ${FeatureName}"
    }
}
var str = style.meta.description.evaluate(feature);
color : ramp(palette, distance(${thisFeature.location}, ${anotherFeature.location}))
show : (${thisFeature.height} > ${anotherBuilding.height})

Cesium Implementation

Built-in functions

pjcozzi commented 8 years ago

I will add this to the roadmap.

Also, some useful info is here: http://stackoverflow.com/questions/4719320/new-number-vs-number

pjcozzi commented 8 years ago

@ggetz here's some notes for when you are ready to integrate the new expressions into the style JSON schema.

Here's a map:

"color" : {
    "key" : "RegEx('^1(\\d)$').exec(${id})",
    "map" : {
        "1" : "Color('#FF0000')",
        "2" : "Color('#00FF00')"
    },
    "defaultValue" : "Color('#FFFFFF')"
}

Note that the value in each pair in the map (e.g., "Color('#FF0000')") is an expression evaluating to a Color so it could include variables, etc.

Likewise, I'm pretty sure we will want the key in each pair to also be an expression (kinda like you had before), and we can optimize the case where all keys in the map are literals by building the hash lookup at compile time.

For "color ramps" or "intervals", I think it is best to start with a generic set of conditionals that we execute like a series of if...else statements, e.g.,

"color" : {
    "key" : "${Height} / ${Area}",
    "conditional" : {
        "(${KEY} >= 1.0)  && (${KEY} < 10.0)"  : "Color('#FF00FF')",
        "(${KEY} >= 10.0) && (${KEY} < 30.0)"  : "Color('#FF0000')",
        "(${KEY} >= 30.0) && (${KEY} < 50.0)"  : "Color('#FFFF00')",
        "(${KEY} >= 50.0) && (${KEY} < 70.0)"  : "Color('#00FF00')",
        "(${KEY} >= 70.0) && (${KEY} < 100.0)" : "Color('#00FFFF')",
        "(${KEY} >= 100.0)"                    : "Color('#0000FF')",
    }
}

key could be optional (or even not part of the schema, but it adds a nice readability and easy optimization for complete expressions). Each conditional property has an expression that evaluates to Boolean and an expression that evaluates to Color.

This approach allows a lot of flexibility and avoids a bunch of schema for closed/open intervals; however, it isn't well suited to a binary search for evaluation (it is basically a linear search). Optimizing it would require a bunch of analysis; later, if we need, we could just introduce an interval schema.

Note that these color maps and ramps could be implemented completely in an expression with the ternary operator, but it would be ugly, painful to generate, and harder to optimize at runtime.

ggetz commented 8 years ago

For "color ramps" or "intervals", I think it is best to start with a generic set of conditionals that we execute like a series of if...else statements

Aren't a map and ramp pretty much the same thing? For example, you can express the "map" in "ramp" syntax pretty easily if we add "defaultValue":

"color" : {
    "key" : "RegEx('^1(\\d)$').exec(${id})",
    "conditional" : {
        "${key} === 1" : "Color('#FF0000')",
        "${key} === 2" : "Color('#00FF00')"
    },
    "defaultValue" : "Color('#FFFFFF')"
}
pjcozzi commented 8 years ago

I actually had that at one point, but made them separate so it would be easy to optimize maps with all literals, e.g., build the hash (JavaScript object) at compile time. However, I think it is still pretty easy to do the same compile-time analysis with conditional (just look for === LITERAL for all properties). Give it a shot!

pjcozzi commented 8 years ago

@ggetz I tightened up the spec in 20d820105127cc3282ca523764c5ae0275547898. Can you please review?

A few questions:

Colors support 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.

  • It's fine to say the following, but we also need to document the arguments like we do for Color, isNaN, etc.

The regExp function behaves like the JavaScript RegExp constructor and takes the same arguments.

  • Can you add a statement to the variables section like 'Variables can be used anywhere an expression can be used except...' or can they be used everywhere (in which case, we should make that explicit)?
pjcozzi commented 8 years ago

@ggetz to organize things, I moved our task lists from https://github.com/AnalyticalGraphicsInc/3d-tiles/issues/2#issuecomment-180355858 and https://github.com/AnalyticalGraphicsInc/3d-tiles/issues/2#issuecomment-183918785 to the top comment: https://github.com/AnalyticalGraphicsInc/3d-tiles/issues/2#issue-88423282

ggetz commented 8 years ago

Does our RegExp type have a toString() function like Color? It should.

Not an explicit function, I will add one.

We need to explain how operators work with RegExp (even if we just disable them for now). For example, colors say this:

I would say operators do not work with RegExp at all for now.

I'll add the other items to the spec as well.

ggetz commented 8 years ago

Also the changes in 20d8201 look good.

pjcozzi commented 8 years ago

@ggetz I stepped through the code in Expression.js and made some minor tweaks in https://github.com/AnalyticalGraphicsInc/cesium/commit/10c8c9f914a718cf1660bd7dd1133f6529752966

Here's a few questions/TODOs:

pjcozzi commented 8 years ago

@ggetz test coverage for Expression.js is pretty good at 98%. Can you please add tests for the DeveloperErrors that are not covered to bring coverage up to 100%?

ggetz commented 8 years ago

Can we do the instanceof checks in _evaluatePlus at compile time, e.g., so we will end up with more _evaluate functions but they will be optimized for their particular type instead of doing type checking at runtime.

I'm guessing this would apply to all of the arithmetic _evaluate functions?

ggetz commented 8 years ago

Also we could do some of the instanceof checks at compile time, but we would need to leave one evaluate function with the instanceof checks in at runtime because of expressions where the types are not clear until evaluation, for example ${myColor} + 3 * color('red')

pjcozzi commented 8 years ago

I'm guessing this would apply to all of the arithmetic _evaluate functions?

Yes.

Also we could do some of the instanceof checks at compile time, but we would need to leave one...

OK.

ggetz commented 8 years ago

Should we wait for the optimized evaluate functions? I believe they are currently on the roadmap under "Later".

pjcozzi commented 8 years ago

Should we wait for the optimized evaluate functions?

Will it be significantly time consuming or significantly deteriorate the code quality? If not, I would do it now. If so, then we can evaluate later.

ggetz commented 8 years ago

If it's targeted to the operators involving Colors, it shouldn't be too time consuming.

pjcozzi commented 8 years ago

@ggetz did you add key to https://github.com/AnalyticalGraphicsInc/cesium/pull/3655 as suggested in https://github.com/AnalyticalGraphicsInc/3d-tiles/issues/2#issuecomment-187728554?

For example:

"color" : {
    "key" : "${Height} / ${Area}",
    "conditional" : {
        "(${KEY} >= 1.0)  && (${KEY} < 10.0)"  : "color('#FF00FF')",
        "(${KEY} >= 10.0) && (${KEY} < 30.0)"  : "color('#FF0000')",
        "(${KEY} >= 30.0) && (${KEY} < 50.0)"  : "color('#FFFF00')",
        "(${KEY} >= 50.0) && (${KEY} < 70.0)"  : "color('#00FF00')",
        "(${KEY} >= 70.0) && (${KEY} < 100.0)" : "color('#00FFFF')",
        "(${KEY} >= 100.0)"                    : "color('#0000FF')"
    }
}

If not, can you please add it and name it expression since key came from the map terminology?

pjcozzi commented 8 years ago

Also, what do you think of renaming conditional in the schema to conditions, e.g.,

"color" : {
    "expression" : "RegEx('^1(\\d)$').exec(${id})",
    "conditional" : {
        "{$EXPRESSION} === 1" : "color('#FF0000')",
        "{$EXPRESSION} === 2" : "color('#00FF00')",
        "true" : "color('#FFFFFF')"
    }
}

becomes

"color" : {
    "expression" : "RegEx('^1(\\d)$').exec(${id})",
    "conditions" : {
        "{$EXPRESSION} === 1" : "color('#FF0000')",
        "{$EXPRESSION} === 2" : "color('#00FF00')",
        "true" : "color('#FFFFFF')"
    }
}
pjcozzi commented 8 years ago

@ggetz did you add key to AnalyticalGraphicsInc/cesium#3655 as suggested in #2 (comment)?

Also, later, we could consider expanding key/expression into an object that could hold multiple expressions for complex conditions, e.g.,

    "color" : {
        "expressions" : {
            "id" : "RegEx('^1(\\d)$').exec(${id})",
            "area" : "${length} * ${height}"
        },
        "conditions" : {
            "({$ID} !== 1) && (${AREA} > 0)" : "color('#FF0000')"
        }
    }

This is overkill for now, but let's consider it if experience shows it will be useful.

ggetz commented 8 years ago

"Expression" and "conditions" sound good. For expression, I think ${expression} is more straight forward than {$EXPRESSION} because it looks like the other variables.

I do like the idea of multiple expressions as well.

pjcozzi commented 8 years ago

I think ${expression} is more straight forward than {$EXPRESSION} because it looks like the other variables.

OK.

Also, this will override a property named expression so, in that case, they need to use feature.expression.

I do like the idea of multiple expressions as well.

OK, let's add it when we need it.

pjcozzi commented 8 years ago

I do like the idea of multiple expressions as well.

Also, I already updated the roadmap for this.

ggetz commented 8 years ago

Also, I already updated the roadmap for this.

OK thanks.

pjcozzi commented 8 years ago

First cut of the spec is in #80.

ggetz commented 8 years ago

First cut of the spec is in #80.

OK

ggetz commented 8 years ago

Suggestions for new name:

twpayne commented 7 years ago

tl;dr The current specification for conditions requires JSON object property order to be preserved, which is rarely true.

JavaScript's for ... in statement iterates over object properties in an arbitrary order. The popular Java library org.json defines a JSON object as "an unordered collection of name/value pairs". Iteration order over Golang maps (a common internal representation for JSON objects) is randomized.

Concrete example: Styling/README.md contains the example

{
    "show" : "${Area} > 0",
    "color" : {
        "conditions" : {
            "${Height} < 60" : "color('#13293D')",
            "${Height} < 120" : "color('#1B98E0')",
            "true" : "color('#E8F1F2', 0.5)"
        }
    }
}

A feature with property Height == 0 matches all three conditions, and therefore its computed color will depend entirely on the evaluation order of the conditions.

Generating conditions objects automatically will also be problematic, as some languages and libraries do not allow the order of properties in the serialized JSON output to be controlled.

Suggestion: either require that all conditions are mutually exclusive (tricky to enforce) or replace the conditions object with an ordered Array of [condition, value] pairs (uglier, but well defined).

pjcozzi commented 7 years ago

Good eye, @twpayne! I'm surprised we haven't ran into this issue in practice already.

replace the conditions object with an ordered Array of [condition, value] pairs (uglier, but well defined).

+1 for this.

@lilleyse is it reasonable for you to make this change (code and spec) as part of your current point cloud declarative styling work?

lilleyse commented 7 years ago

@lilleyse is it reasonable for you to make this change (code and spec) as part of your current point cloud declarative styling work?

Yes it should be easy to change. For the spec I'll open a separate PR for this fix. For the code I'll make the change in my styling PR.

pjcozzi commented 7 years ago

Yes it should be easy to change. For the spec I'll open a separate PR for this fix. For the code I'll make the change in my styling PR.

In https://github.com/AnalyticalGraphicsInc/cesium/pull/4336/commits/5cb1088d0dbc8b01d2eb46b289e1ab77150c01b1, part of https://github.com/AnalyticalGraphicsInc/cesium/pull/4336.

pjcozzi commented 7 years ago

Labeling draft 1.0 just for the utility functions and finishing the vector types.

pjcozzi commented 6 years ago

@ggetz is there anything here critical for 1.0?

ronanmcnulty commented 5 years ago

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

ggetz commented 5 years ago

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

ThornM9 commented 5 years ago

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 commented 5 years ago

@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 commented 4 years ago

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 commented 2 years ago

See https://github.com/CesiumGS/3d-tiles/issues/179 for optimizing large number of conditional expressions.

lilleyse commented 2 years ago

See https://github.com/CesiumGS/3d-tiles/issues/239 for batching vector images into a texture atlas that's part of the payload

lilleyse commented 2 years ago

See https://github.com/CesiumGS/3d-tiles/issues/252 for mutables (like GL uniforms)

lilleyse commented 2 years ago

See https://github.com/CesiumGS/3d-tiles/issues/266 for styling alpha separately

lilleyse commented 2 years ago

See https://github.com/CesiumGS/3d-tiles/issues/268 for accessing feature ids in the styling language

lilleyse commented 2 years ago

See https://github.com/CesiumGS/3d-tiles/issues/292 for accessing instanced attributes in the styling language

lilleyse commented 2 years ago

More considerations for 3D Tiles Next: