I want a data driven line-width to keep its relative size in all sizes I tried using an exponential function for that but it did not work the way I expected. For the sake of the example let's say I have 3 lines with value: 1, 10, 100

In zoom 0 their width would be: 1px, 10px, 100px
In zoom 1: 2px, 20px, 200px
In zoom 2: 4px, 40px, 400px
In zoom 4: 16px, 160px, 1600px
In zoom 10: 1024px, 10240px, 102400px

I marked the rate of change to 1.99 which gave the closest results, but it's still not it.

Can anyone help me find how can I style this in the Mapbox GL editor (and or via code)?


You've got the basic principle right, at each integer zoom level increase the line width would need to double to cover the same geographic area on the map (see zoom levels on the OSM wiki).

Expressing this mathematically if you know at a specific zoom level (A) that your line width should be (B) in pixels, then the width of that line at any a given zoom level (Z) will be:

B * 2^(Z - A)

In the Mapbox GL JS Style Spec we can use Zoom functions to set the line-width at different zooms, specifically the minzoom and maxzoom. By using the exponential type with base set to 2 the line width function should interpolate between these zooms in a way that the line covers the same geographic area as you zoom in and out.

"line-width": {
    "type": "exponential",
    "base": 2,
    "stops": [
        [0, baseWidth * Math.pow(2, (0 - baseZoom))],
        [24, baseWidth * Math.pow(2, (24 - baseZoom))]

You can see this in a complete example at https://jsbin.com/tesaqeq/edit?html,output

You can do this in Mapbox Studio by setting fixed stops and precalculated widths for those stops based on the above formula.

However because Mapbox GL JS renders this line based on data coming from vector tiles, depending on where the line geometry is in relation to the tile boarders, the width of the line and the buffer used within your vector tiles, GL JS in many cases will not render the line correctly, since it doesn't know there is a line a long way away with a huge width extending into your current view.

Because of that I'd only recommend the above method when you're dealing with very limited zoom ranges and limited line widths, anything else I'd recommend converting that line into a polygon (try turf.buffer) which will ensure it's rendered covering the same geographic area at all zooms.

| improve this answer | |
  • 1
    Awesome answer, @andrewharvey. I have the same issue, I have lines that I want to style at dynamic number of metres wide. I kludged it to work ok, but will refer to this to see if I can get it working better! I get what you're saying about polygons, but it's not ideal for data storage/editing for me. Maybe I could buffer and draw the buffers... Anyway, will look at this first! – Alex Leith Oct 24 '17 at 6:22
  • 1
    Good point. You can keep your source data as a line and build the buffering step into the data pipeline to convert that line into a real-world width polygon equivalent. This is what I built github.com/alantgeo/dataset-to-tileset for, so you can work with source data as a Mapbox Dataset and apply processing when turning it into a Tileset. – AndrewHarvey Oct 24 '17 at 10:58
  • This is great @AndrewHarvey, Thank you! Any chance you can point me at how could I use it with a data-driven-styling? I mean, if every line has a number that should dictate its width, how would you suggest accounting for that in the function? (and thanks again!) – Mushon Oct 25 '17 at 11:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.