TransWikia.com

Style line-width proportionally to maintain relative size in Mapbox GL

Geographic Information Systems Asked by Mushon on May 6, 2021

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
etc…

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)?

2 Answers

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.

Correct answer by AndrewHarvey on May 6, 2021

There is now a newer expression-based syntax for this. I followed instructions from rigoneri and jfirebaugh from this github issue to good effect:

paint: {
    'line-width': [
        'interpolate', 
        ['exponential', 2], 
        ['zoom'],
        10, ["*", ["get", "line_width"], ["^", 2, -6]], 
        24, ["*", ["get", "linw_width"], ["^", 2, 8]]
    ],
}

This assumes your layer has a numeric field called line_width. Change that value to whatever your line width variable is called.

Answered by Matt Hampel on May 6, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP