Mapbox-gl-js: Data driven properties on line-gradient colors

Created on 14 Nov 2019  Â·  12Comments  Â·  Source: mapbox/mapbox-gl-js

Motivation

As far as I tested for know, it seems not possible to use data driven property to set the color associated with the percentages in the line-gradient / heatmap paint property.
For instance see this API example below :

map.addLayer({
    type: 'line',
    source: 'line',
    id: 'line',
    paint: {
         'line-width': 14,
         'line-gradient': [
             'interpolate',
             ['linear'],
             ['line-progress'],
             0, ["get", "src-color"],
             1, "["get", "dst-color"]
        ]
    },
});

Cheers,
Matthieu.

cross-platform feature

All 12 comments

For the heatmap, this is technically impossible since the heatmap layer gets colorized after the accumulation stage (when all features are rendered into one grayscale texture), using a single gradient as lookup.

For the line gradient though, this might be possible — needs investigation.

Ok. I thought I was the same "engine" underneath since they share common API but, yeah, I'm mostly interested in line-gradient anyway.

Is there a some kind of "standard procedure" in order to implement a data driven property ? I'm trying to investigate on my own but I'm not familiar with the code so it's not very efficient 😄

I've been using something like this:

const stops = [
  0, 'green',
  0.2, 'cyan',
  0.6, 'orange',
  0.9, 'green',
  1, 'cyan',
];

map.addLayer({
    type: 'line',
    source: 'line',
    id: 'line',
    paint: {
         'line-width': 14,
         'line-gradient': [
             'interpolate',
             ['linear'],
             ['line-progress'],
             ...stops
        ]
    },
});

image

Seems to work well, but ideally I want there to be a hard line between the colour transitions. But I can't seem to figure out how to do that.

Hello, I have a need to put several animated lines with different gradients on the map. Trying to read gradient values from a line's properties (in the same manner as matthieugouel did) ended up with this error:

Error: layers.line-animation.paint.line-gradient: data expressions not supported

Will it be fixed sometime? It looks like the only way to do my task is to put lines in different layers which will hit performance, and I would rather not to do that.

any update?

I'm also very much interested in having such functionality.
Is there a way to give a hand to help implementing it?

Adding another request for this feature!

This feature would be great, thanks :)

Also it would be great if the gradient values (not colors) were to be taken from a property or an array that matches the line points array.
There's a nice feature in the Oruxmaps app that shows the slope using gradient colors - i.e. when the slope is "hard" the color is red and when the slope is "easy" the color is green.
This would be a very nice addition to this framework to be able to do it.
I have looked at the code to understand where the data from line progress is coming from but couldn't fully understand, mainly I guess because it's tiled base...?
If someone can help me better understand the code I might be able to help here...
IF you want me to open a new issue since it's not exactly the same let me know...

Hi. I found a solution for this, maybe.
Example. We need set gradient to route by speed data. 0 - green, 50 - yellow, 100 -red

  1. Create source with all points
map.addSource('route', {
          'type': 'geojson',
          'lineMetrics': true,
          'data': {
            'type': 'FeatureCollection',
            'features': [{
              "type": "Feature",
              "geometry": {
                "type": "LineString",
                "coordinates": values.map(item => item.location)
              }
            }]
          }
        });
  1. Create layer
map.addLayer({
          id: "route",
          type: "line",
          source: "route",
          paint: {
            "line-width": 5,
            'line-gradient': [
              'interpolate',
              ['linear'],
              ['line-progress'],
              ...getLineBackground()
            ]
          }
        })
  1. Create getLineBackground function.
const getLineBackground = () => {

    const range = chroma.scale('green', 'yellow', 'red').domain(0, 50, 100); // use chroma.js

    const colorsData = [];
    const totalDistance = values.reduce((total, currentPoint, index) => {
      if (index !== this.props.values?.length - 1) {
        return total + getDistanceFromLatLng(currentPoint.location[0], currentPoint.location[1], this.props.values[index + 1].location[0], this.props.values[index + 1].location[1]);
      }
      return total;
    }, 0); // distance between first and past point

    // next calculate percentage one line to all distance 
    const lengthBetweenPoints = values?.map((item, index) => {
      if (index === 0) {
        return {
          ...item,
          weight: 0
        };
      }
      if (index === values?.length - 1) {
        return {
          ...item,
          weight: 1
        };
      }

      return {
        ...item,
        weight: getDistanceFromLatLng(item.location[0], item.location[1], values[index + 1].location[0], values[index + 1].location[1]) / totalDistance
      };

    }); 


    let weight = 0;
    // next fill colorsData for **line-progress** property 
    lengthBetweenPoints.forEach((item, index) => {
      if (item.weight || index === 0) {
        if (index !== lengthBetweenPoints.length - 1) {
          weight += item.weight;
          colorsData.push(weight);
        } else {
          colorsData.push(item.weight);
        }
        colorsData.push(range(item.speed).hex());
      }
    });

    return colorsData;
  };

In final, we have
image

PS: Code for getDistanceFromLatLng

const getDistanceFromLatLng = (lon1,lat1,lon2,lat2) => {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1);
  var a =
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
    Math.sin(dLon/2) * Math.sin(dLon/2)
  ;
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = R * c; // Distance in km
  return d;
};

function deg2rad(deg) {
  return deg * (Math.PI/180);
}

@EarlOld this only works for a single feature. Ideally, we would be able to set data-driven line-gradients for tons of lines in the same layer (such as a road network visualizing traffic). One way we could do that is by keeping line-gradient static (so there's one texture for color lookup), but introducing a new line-gradient-progress property that would map line-progress to the final gradient key value, e.g.:

'line-gradient-progress': [
  'interpolate', 'linear', ['line-progress'], 
  0, ['get', 'start_speed'], 
  1, ['get', 'end_speed']
]
Was this page helpful?
0 / 5 - 0 ratings

Related issues

stevage picture stevage  Â·  3Comments

BernhardRode picture BernhardRode  Â·  3Comments

PBrockmann picture PBrockmann  Â·  3Comments

aendrew picture aendrew  Â·  3Comments

jfirebaugh picture jfirebaugh  Â·  3Comments