Layers Control port from leaflet
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 12.4k
- Forks
- 2.4k
- PR merge metrics
- No merged PRs in 30d
Description
## Motivation
**What problem are we trying to solve?**
Support for layer selection with the layer control that matches Leaflet layer control.
**What use cases are we trying to accommodate?**
Developers migrate from Leaflet to Mapbox GL and need to keep the UX of layer selection unchanged.
That is ability to switch between base layers and toogle on/off overlays.
## Design Alternatives
**How could we accommodate the use cases above?**
The control is initialized with a callback that takes the list of all layers and returns two lists:
- layers that should be treated as base layers
- layers or **layer groups** that should be treated as overlays.
User can group multiple related layers that are selectable together and filter away whatever he/she is not intending to expose which is probably a reasonable approach in this case.
**Is "do nothing" an option?**
Well, the developers will then need to mock the leaflet control with same look and feel which would probably consume them several hours if they are well-familiar with the framework, but still this time can be avoided.
## Example
Let's say user treats all raster layers as base layers and groups other layers based on regex _layer-id_ matches.
```typescript
map.addControl(new LayerSwitchControl(layers => {
const baseLayers = layers.filter(layer => layer.type == 'raster');
const layerGroups = [
{ predicate: /(water|ocean)/, name: 'water'},
{ predicate: /landuse/, name: 'landuse' },
{ predicate: /(road|rail|highway)/, name: 'roads'},
{ predicate: /boundar/, name: 'boundaries'},
{ predicate: /buildings/, name: 'buildings'}
].map(filter => layers
// don't use any of layers designated for highlighting
.filter(layer => !layer.id.endsWith('selected'))
.filter(layer => filter.predicate.exec(layer.id))
.reduce((layerGroup, layer) => ({
...layerGroup,
layers: [...layerGroup.layers, layer]
}), { name: filter.name, layers: [] } as LayerGroup))
return {
baseLayers: baseLayers,
overlays: layerGroups
}
}) as any)
```
### Mock-Up
**What will this design look like to end users?**

**onMouseEnter**

### Implementation
**How you would implement the design in Javascript?**
LayersControl that toogles the layer visibility as a result of radio buttons and checkbox changes.
Something like:
```typescript
export class LayerSwitchControl implements MapboxGL.IControl {
controlGroup: HTMLDivElement | null = null
constructor(private layerSelector: (layers: MapboxGL.Layer[]) => { baseLayers: MapboxGL.Layer[], overlays: MapboxGL.Layer[] | LayerGroup[], initialBaseLayer?: MapboxGL.Layer }){}
isIn: boolean = false
onAdd(map: MapboxGL.Map): HTMLElement {
const container = map.getContainer()
const controlGroup = document.createElement('div')
controlGroup.className = `mapboxgl-ctrl-group mapboxgl-ctrl`
this.controlGroup = controlGroup
this.setAsButton()
this.controlGroup.addEventListener('mouseenter', event => {
if(this.isIn){ return }
this.isIn = true
this.setAsList(map)
})
this.controlGroup.addEventListener('mouseleave', event => {
if(!this.isIn){ return }
this.isIn = false
this.setAsButton()
})
return controlGroup
}
/// trimmed...
private createRadioList(map: MapboxGL.Map, baseLayers: MapboxGL.Layer[], initial?: MapboxGL.Layer): HTMLDivElement | null {
if(baseLayers.length == 0){ return null }
const listContainer = document.createElement('div')
const formElement = document.createElement('form')
listContainer.appendChild(formElement)
const initialIndex = initial ? baseLayers.indexOf(initial) || 0 : 0
baseLayers.forEach((layer, index) => {
formElement.appendChild(this.createInputBlockElement(layer.id, 'radio', 'base', index == initialIndex, event => {
baseLayers.forEach(layer => map.setLayoutProperty(layer.id, 'visibility', 'none'));
map.setLayoutProperty(layer.id, 'visibility', 'visible')
}))
})
return listContainer
}
}
```
**What parts of the Mapbox GL ecosystem will need to change to accomodate this design?**
Extra ~200lines Control in mapbox gl js.
So I have implemented this simple port. If you guys think it is reasonable to include within the SDK, I will create a pull request.
P.S: Also related to https://github.com/mapbox/mapbox-gl-js/issues/4468
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.
Research direction
Start with the MapboxGL.IControl entry point and the proposed LayerSwitchControl.onAdd implementation; review how map.addControl and setLayoutProperty are used in the example. Done means a control matching Leaflet's layer-selection UX, including base-layer radio selection and overlay or layer-group toggles.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- frontend, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100