mapbox-gl-js icon indicating copy to clipboard operation
mapbox-gl-js copied to clipboard

Enable property functions for *-translate properties

Open lucaswoj opened this issue 9 years ago • 10 comments

Supporting *-translate properties isn't as straightforward as supporting other paint properties because translation is incorporated into the transform matrix.

Potential Designs

  • incorporate translation property function into position in vertex buffer, requires translate be treated like a layout property
  • add new "translate" shader uniform
  • don't support this feature

Properties

  • circle-translate
  • circle-translate-anchor
  • line-translate
  • line-translate-anchor
  • fill-translate
  • fill-translate-anchor

lucaswoj avatar Jun 13 '16 23:06 lucaswoj

We should be able to implement this by adding a vec2 translate attribute to the paint buffer. Ideally we could implement this such that the attribute not needed for non-data-driven translate values.

lucaswoj avatar Nov 21 '16 18:11 lucaswoj

This applies to the symbol properties as well:

  • text-translate
  • text-translate-anchor
  • icon-translate
  • icon-translate-anchor

anandthakker avatar Feb 08 '17 00:02 anandthakker

incorporate translation property function into position in vertex buffer, requires translate be treated like a layout property

The only downsides that I can think of for doing it this way is the hackiness of including special-case checking for paint['...-translate'] properties in groupByLayout(). @lucaswoj do you think there's more to it than that?

add new "translate" shader uniform

Would this need to be an attribute, rather than a uniform, to allow for property functions?

anandthakker avatar Feb 27 '17 21:02 anandthakker

I'm looking into implementing this (specifically circle-translate) but I was wondering how to support per feature translation? From the codebase my understanding is that the layoutVertexArray in CircleBucket is created only once and when draw_circle.js is called to actually draw all circles I'm not able to change (or inject a vec2 translate) into each circle per it's feature. Ideally my geojson should be something like:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "zoom5": [300,300],
        "zoom7": [200,200],
      },
      "geometry": {
        "type": "Point",
        "coordinates": [-97,39]
      }
    }
  ]
}

Any pointers maybe of where to look if I'd like to implement this per feature?

Thanks!

shayke avatar Nov 23 '17 14:11 shayke

This isn't working? UPDATE: Looks like it is not yet merged. 'circle-translate': ['get', 'markerOffset'] produces Error: layers.stopCircleLayer.paint.circle-translate: property expressions not supported

Geojson being:

var newOffset = someCondition ? [0, -20] : [0, -100];
    var feature = {
        'type': 'Feature',
        'geometry': {
            'type': 'Point',
            'coordinates': [stop.longitude, stop.latitude],
        },
        'properties': {
            'markerOffset': newOffset,
        },
    };

I was trying to use this as a means of doing cheap spiderfying/exploding/clustering by pre-identifying circles to be offset. In my case I am using a circle to create a dynamic colored icon since markers do not support dynamic coloring. But I can see where someone working with circle graph data would have overlaps.

ericjames avatar Apr 02 '18 21:04 ericjames

@ericjames I am pursuing a similar solution and I am kind of confused why property expressions work for *-offset properties e.g. on symbol type layers, but not for circle-translate. Is there known workaround?

While this doesn't work

map.addLayer({ 'id': 'points', 'source': 'points', 'type': 'circle', 'paint': { 'circle-color': ['get', 'color'], 'circle-radius': 16, 'circle-translate': ['get', 'offset'], 'circle-stroke-width': 2, 'circle-stroke-color': ['get', 'outlineColor'] } },'place_town');

this works perfectly fine

map.addLayer({ 'id': 'points_icons', 'source': 'points', 'type': 'symbol', 'layout': { 'icon-image': ['get', 'iconName'], 'icon-size': 0.5, 'icon-offset': ['get', 'offset'], 'icon-allow-overlap': true, 'icon-ignore-placement': true } },'place_town');

Only thing I can think of right now is adding a separate layer for each feature in a loop like this:

points.features.forEach((feature) => { map.addLayer({ 'id': feature.properties.object_type, 'source': 'points', 'type': 'circle', 'paint': { 'circle-color': feature.properties.color, 'circle-radius': 16, 'circle-translate': feature.properties.offset, 'circle-stroke-width': 2, 'circle-stroke-color': feature.properties.outlineColor } },'place_town'); });

but that's messy to handle. Or I could switch to symbol type layers, but since I am working with many different colors that would make my sprites messy as well.

If this is just a matter of merging, maybe this could be implemented fast?

timothyde avatar Apr 16 '18 06:04 timothyde

@jfirebaugh @lucaswoj @ericrwolfe any news on this?

We would love to be able to use these properties with the data-driven styling API. Makes the API a lot more complete. A less than ideal workaround we often need to use is to create separate layers with the corresponding settings, which impacts performance and makes the code less readable.

patrickkempff avatar Jun 17 '19 14:06 patrickkempff

I'm simply modifying the actual coordinates of the values of markers I want spiderfied.

ericjames avatar Jun 18 '19 20:06 ericjames

@asheemmamoowala does this mean that there is an alternative solution that doesn't rely upon duplication and main-thread js processing of all the features we need to offset? For that matter, this is from 2016. Quite frankly, I find it kind of impossible that this is still not tackled? Especially since sources accept urls (read sources containing data that's not available locally) and that's kind of severely affected by this.

raegen-work avatar Oct 05 '20 18:10 raegen-work

I'm wondering if there have been other solutions since. I ran into this thread looking for ways to evenly distribute circles that share the same coordinate without manually adjusting the coordinates. This is what I've got so far but it's not ideal because it doesn't take into consideration zoom or the pixel size of circles:

function distributeFeatures(center, features) {
    const numFeatures = features.length;
    const radius = 0.005
    const angleIncrement = (2 * Math.PI) / numFeatures

    features.forEach((feature, index) => {
        const angle = index * angleIncrement;
        const newX = center[0] + radius * Math.cos(angle)
        const newY = center[1] + radius * Math.sin(angle)
        feature.geometry.coordinates = [newX, newY]
    })

    return features
}

dgaitsgo avatar Feb 22 '24 22:02 dgaitsgo