RFC: Screen-adaptive heatmaps
@mourner is already working on this.
Since Jul 25, 2018.
- Dominant language
- TypeScript
- Stars
- 12.4k
- Forks
- 2.4k
- PR merge metrics
- No merged PRs in 30d
Description
## Motivation
Currently, one of the most difficult things you face when working with GL heatmaps is adjusting `heatmap-intensity` so that it would look good on any zoom level — this heavily depends on the data. Also, it's currently impossible to adjust intensity based on what data gets into the viewport — after the user panned away from the most intense spots, the coloring won't be as useful to judge data patterns.
## Design Alternatives
Capturing a few user suggestions, let's explore how we could implement screen-adaptive intensity — enabling a heatmap that automatically adjusts to the data you see on the screen. This would not only eliminate the need to hand-pick the intensity expression, a common pain point, but also enable optimal rendering regardless of where the user pans.
It's not an essential feature, so we could do away with it, but implementing it would make heatmaps more flexible and more enjoyable to use in many use cases.
## Design
The feature should be an option, maybe as an additional property, or a special string/number value for `heatmap-intensity`. After you enable it, you no longer have to worry about `heatmap-intensity` — it will automatically adjust to the maximum density point on the screen so that all densities are exactly in 0..1 range.
If it works well, it may be worth having it on by default.
### Mock-Up
```js
// options
"heatmap-intensity": "auto",
"heatmap-intensity": -1, // or 0?
"heatmap-auto-intensity": true,
```
### Concepts
The adaptive heatmaps concept is inspired by the current heatmaps feature in Snapchat, which adjusts to the screen as you pan — users love it and want an option for Mapbox GL heatmaps to work in a similar way.
The feature will make it easier for users to adopt heatmaps because they will have one less configuration option to worry about to get a good looking map.
### Implementation
One technical challenge is finding a fast way to determine the maximum density value in a heatmap texture so that we could readjust densities. Doing `readPixels` to find the maximum on the CPU side is too expensive, and so is calculating it on the CPU from data points since we have to do it for each pixel on the screen.
The most promising approach I found is described in [this StackOverflow answer](https://stackoverflow.com/a/37504662/244789) — basically, the idea is to progressively reduce the density texture with a "max of 4 pixels" shader in a mipmap fashion until we're left with a 1x1 texture with a maximum value. This will require `log(width)` passes of rendering progressively smaller textures, which should be relatively fast. We could also only limit this calculation to `moveend` rather than do it on every frame, and animate the layer intensity from the old value to the new one after the user moved the map.
Another tricky challenge I don't yet see a solution to is that this approach will break on the UNSIGNED_BYTE "fallback" version of the heatmap — since densities will accumulate in 0-255 range bytes, they'll be hard-capped, so getting the max value won't be useful for readjustment. Is there any way around this?
Finally, by introducing these improvements, the heatmap layer implementation could get too complex to maintain productively. Is there any way we could reduce this complexity?
cc @anandthakker @ryanbaumann
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.