mapbox / mapbox/mapbox-gl-js

Create a `getMinZoomForBounds` method on the `Map` object

Open
#13,485 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature :green_apple:
Dominant language
TypeScript
Stars
12.4k
Forks
2.4k
PR merge metrics
No merged PRs in 30d

Description

## Motivation

I have a "zoom out" button that I would like to disable if the map can't be zoomed out any further. This happens in one of two cases:
- the zoom matches the map's `minZoom`
- the zoom matches the minimum zoom for the current `maxBounds`

Unfortunately, a naive `map.getZoom() == map.getMinZoom()` doesn't cut it, given that the method `map.getMinZoom()` [returns the configured `minZoom`](https://github.com/mapbox/mapbox-gl-js/blob/2b2743a69bfbfeb5745180524d9322b554398068/src/ui/map.ts#L1096), without checking the `maxBounds` at all.

For context, the built-in navigation control uses [the "naive" condition](https://github.com/mapbox/mapbox-gl-js/blob/2b2743a69bfbfeb5745180524d9322b554398068/src/ui/control/navigation_control.ts#L93). This effectively leaves the zoom out button enabled in all those scenarios where the `maxBounds` lead to a minimum allowed zoom that is more restrictive than the map's min zoom.

## Design Alternatives

Doing nothing is an option. However, to achieve the desired behaviour, developers are forced to repeat logic that is already present internally. My user-land implementation closely follows what's available in the `Transform` class, [in the `_minZoomForBounds` method](https://github.com/mapbox/mapbox-gl-js/blob/2b2743a69bfbfeb5745180524d9322b554398068/src/geo/transform.ts#L2213):
```ts
protected onMapZoomEnd({ target }: MapEventOf<'zoomend'>) {
const transform = target.transform;
let minZoom = Math.max(0, transform.scaleZoom(transform.height / (transform.worldMaxY - transform.worldMinY)));
minZoom = Math.max(minZoom, transform.scaleZoom(transform.width / (transform.worldMaxX - transform.worldMinX)));
this.isMax.set(target.getMaxZoom() == target.getZoom());
// For unknown reasons, sometimes the fully-zoomed out view and the minZoom won't match
// When that happens, the difference is usually around 8e-16
this.isMin.set(Math.abs(minZoom - target.getZoom()) < Number.EPSILON * 10);
}
```

## Design

A new `map.getMinZoomForBounds()` method. A new method would have the advantage of letting existing code that relies on the current behaviour of `getMinZoom` stay unchanged. In a library this popular, I'd argue this is quite important.

### Mock-Up

The updated code to disable the zoom out button could look like this (React pseudo-code):
```tsx
// Instead of
// let minZoom = Math.max(0, map.transform.scaleZoom(map.transform.height / (map.transform.worldMaxY - map.transform.worldMinY)));
// minZoom = Math.max(minZoom, map.transform.scaleZoom(map.transform.width / (map.transform.worldMaxX - map.transform.worldMinX)));
// const isDisabled = map.getZoom() == minZoom;
// Do
const isDisabled = map.getZoom() == map.getMinZoomForBounds();
return Zoom out;
```

### Concepts

I don't know of any precedents, although the fact that it's an internal function, used by the library to make decisions ("can I zoom out?") that one can't easily probe from the outside, should be plenty of reasons to expose this information.

### Implementation

The implementation would be quite straightforward:
```ts
// mapbox-gl-js/src/ui/map.ts
getMinZoomForBounds(): number {
return this.transform._minZoomForBounds(); // or better: rename to "minZoomForBounds" (drop the _)
}
```

The only quirk I've identified in my user-land code is that, when the user is fully zoomed out, the `minZoomForBounds` doesn't always match the current zoom. The difference is minimal (`±8e-16`), but it's still 5x the size of `Number.EPSILON`, so it can't be attributable to loss of precision, as far as I know. That should probably be a separate issue, though.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/ui/map.ts by reviewing getMinZoom and the proposed getMinZoomForBounds entry point, then read src/geo/transform.ts and its _minZoomForBounds method. Confirm how maxBounds affect the minimum zoom and preserve the existing getMinZoom behavior. Done means the Map API exposes the bounds-aware value for callers such as the navigation control.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.