CartoDB / carto-vl

CARTO VL: a Javascript library to create vector-based visualizations
BSD 3-Clause "New" or "Revised" License
129 stars 26 forks source link

Complex marker logic #1053

Open rjimenezda opened 6 years ago

rjimenezda commented 6 years ago

I have a question about a relatively complex marker logic situation.

You have several (small amount) types of features on a table. You want to represent each one of these with a particular icon.

This is solved with a symbol ramp:

symbol: ramp(buckets($category, ['primary', 'secondary']), [image(primaryUrl), image(secondaryUrl)], image(fallback))

So, we are mapping the icon on one dimension, the $category.

Imagine now we want to highlight a particular feature, by changing its marker. Maybe a different version of the marker associated with its category.

How would you approach this?

I thought of a couple of things

Approach 1 - If expression

Not knowing anything about internals. Would an if operator be feasible?

if(condition, expression_for_true, expression_for_false)

The condition could be $cartodb_id == ${selectedId} The first expression could be the ramp for highlighted markers based on category The second expression could be the ramp for non-highlighted markers based on category.

This approach reminds me of doing spreadsheets :grimacing:

Approach 2 - Composed variables

What if we could create interpolated like this:

$category_${selectedId == $cartodb_id}

Then the ramp would map: primary_1, primary_0, secondary_1, secondary_0 to primaryUriSelected, primaryUri, secondaryUriSelected, secondaryUri

VictorVelarde commented 6 years ago

@rjimenezda if expression can be emulated with @blend (I learned that today indeed!).

You have to think of it as blend(FALSE_VALUE, TRUE_VALUE, CONDITION). For example, using color: gray and a width of 10 for all features, but changing those properties for the relevant/s one could be something like:

color: blend(gray, red, @f_selected)
width: blend(10, 40, @f_selected)
@f_selected: $cartodb_id == 1
davidmanzanares commented 6 years ago

Firstly, Blend is a pretty performance heavy expression, it is ok to use it, but don't expect good performance if you put 15 nested blends.

WebGL shader compilation will probably fail if you use lots of them too.

That said, if you want to highlight a few markers (or groups of markers that share one condition, like a category property) blend is the way to go.

Just to expand Victor's response, you can mix the image ramp you put with blend.

If you just want to color the marker you can use the color property and that will override the color, use blend there as Victor proposed.

If you, on the other hand, want to use another image, you can use blend in this way:

symbol: blend(myUsualImageRamp, myHighlightImage, myCondition)

rjimenezda commented 6 years ago

So blend works with images as well? I thought it would only work with colors.

I tested it on a JSFiddle and it seems to work. I'll give it a try on my actual project see what happens :+1:

rjimenezda commented 6 years ago

Ok so with blend and blend.mix (as per @rochoa's suggestion on #959) I was able to get a pretty well-performing example.

However, I still encountered a problem.

After changing the image a few times, there's something going wrong with webgl and the image.

INVALID_ENUM: activeTexture: texture unit out of range

You can check on this fiddle:

https://jsfiddle.net/03b582ca/13/

On my real example it takes longer to happen, because I'm doing this on featureenter events, but happens eventually.

Because it looks like it's running out of space to keep these images, @Jesus89 mentioned that maybe storing the images on a variable makes it so it reuses them instead of loading them every time. But it turns out this:

@selectedimg: image('${imagePath}')
@fallback: image('${fallbackPath}')
symbol: ramp(buckets(...), [@selectedImg], @fallback)

Is not valid. Ramp says that fallback is undefined.

In case you're wondering why the crazy base64 images, it's just because on my example I'm generating them on the fly with dynamic colors. But in the end, there are going to be 4 images. I know that I can change the color prop, but that changes the whole marker, and there's parts that need to be different colors.

I also tried with normal urls, but same thing happens:

https://jsfiddle.net/jp1xanmd/2/

rjimenezda commented 6 years ago

I just realized that the texture unit out of range error also happens if you keep the image as it is but blend other properties (I'm changing width as a temporary workaround).

https://jsfiddle.net/vac07t93/

davidmanzanares commented 6 years ago

This needs more time to properly debug it, but the "texture unit out of range" could be due to an accumulation of "blend"s. There is a limitation on the maximum number of images (when not used under a list, which a special case that is optimized by using texture atlas). If you are putting many images (10+) this is probably what is happening.

BTW, I think it would be much helpful for us, and probably for you too, if you used https://cartodb.github.io/carto-vl/examples/editor/index.html instead of jsfiddle. You can use built-in images directly.

rjimenezda commented 6 years ago

Technically, it's only 2 images. Three or four if you consider the "hovered" state as a different one, but it seems to also happen when you only change the width as well

Maybe they're being loaded each time something changes :man_shrugging:

I was using fiddle because the docs examples link to fiddle to try them out. Maybe we should make it so they point to our editor.

davidmanzanares commented 6 years ago

It doesn't matter if the same image is referenced twice, that are 2 images for WebGL (unless they are in a list).

Maybe they're being loaded each time something changes man_shrugging

They probably do some heavy operations, but they shouldn't accumulate texture units... Unless you are using blending, if you use blending you can accumulate images, but each time a blending finishes it should free the resources and avoid long-term texture unit accumulation.

Regarding fiddle... I think that we should improve the current approach for styling things like this. Our "editor" is a really useful page... cc: @rochoa

rochoa commented 5 years ago

@rjimenezda, I think what's missing here is no longer how to solve the initial problem but the fact that we have some errors regarding freeing resources at WebGL side. If you are OK, I'm gonna close this issue a create a new one to address the issue with the out of range textures problem:

WebGL: INVALID_ENUM: activeTexture: texture unit out of range
[.WebGL-0x7fe4fc860e00]GL ERROR :GL_INVALID_VALUE : glUniform1i: texture unit out of range
rjimenezda commented 5 years ago

Fine by me :+1:

pedrohb88 commented 5 years ago

@rjimenezda, I think what's missing here is no longer how to solve the initial problem but the fact that we have some errors regarding freeing resources at WebGL side. If you are OK, I'm gonna close this issue a create a new one to address the issue with the out of range textures problem:

WebGL: INVALID_ENUM: activeTexture: texture unit out of range
[.WebGL-0x7fe4fc860e00]GL ERROR :GL_INVALID_VALUE : glUniform1i: texture unit out of range

Is there any approach to solve this problem? I'm facing the same thing and i definitely don't know what to do.

elenatorro commented 5 years ago

Hello @pedrohb88, let's see if we can prioritize these issues related with the blendTo method and the texture units used. Thanks!

jsanz commented 5 years ago

@elenatorro is this issue to be reopened?

elenatorro commented 5 years ago

It's not closed, it's now on RT. Right now is not one of our priorities, but if it's something urgent let us know.