Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alternative maxBounds implementations #6969

Open
andrewharvey opened this issue Jul 16, 2018 · 8 comments
Open

alternative maxBounds implementations #6969

andrewharvey opened this issue Jul 16, 2018 · 8 comments

Comments

@andrewharvey
Copy link
Collaborator

Map.maxBounds (red box) is implemented as never show the area outside the maxBounds (blue box, must be strictly within red box) . While useful in some contexts it has limitations on zooming out, which is sometimes not desirable.

An alternative approach to maxBounds is to ensure that at least some part of these bounds is shown in the map, potentially with a padding (green box must have some part in the red box).

maxBounds

Another alternative is to stick with the current approach but add a padding in pixels to the maxBounds.

@jfirebaugh
Copy link
Contributor

I'm inclined to keep it simple. Do any other mapping libraries use this alternative definition?

@andrewharvey
Copy link
Collaborator Author

I'm inclined to keep it simple.

I'll see if I can experiment with the options to play with some alternatives to see if they are handy or not, even if it doesn't land that's fine.

Do any other mapping libraries use this alternative definition?

I'm not sure actually.

@stevage
Copy link
Contributor

stevage commented Jul 20, 2018

If I understand this right, then #4268 was implemented, you could achieve this by setting half the width and height as the "global padding" options.

The current solution is definitely too restrictive. I think the padding option is better than this "alternative definition" option, because it allows for alternative options between the two extremes:

  • View nothing at all outside the bounds (current solution)
  • View a bit/a lot outside the bounds (with global padding)
  • View up to a whole screen outside the bounds (this proposal, also achievable with global padding)

@tmcgann
Copy link

tmcgann commented Apr 26, 2022

When I used maxBounds for the first time, it felt like it exhibited behavior inconsistent with fitBounds.

For example, if I supply a bounding box for the country Italy ([6.749955, 36.619987, 18.480247, 47.115393]) to fitBounds I can see the whole country, but when I use that same bounding max for maxBounds the whole country doesn't fit within my viewport. In this scenario, I can I call getBounds() and see that the bounding box actually being used is [-1.087394611812897, 36.496491345756425, 26.31759661181337, 47.21991711120498] not the original bounds I supplied.

I prefer the behavior for fitBounds and initially expected maxBounds to mimic it; however, it seems more important that the behavior between fitBounds and maxBounds is consistent. If there were a simple option to specify how I expected them to work, that might be more ideal since use cases for something more restrictive (i.e. the way maxBounds works today) and something more lax (i.e. the way fitBounds works today where the entire bbox is visible within the map viewport) exist.

The "global padding" option @stevage mentioned above is not intuitive to me. I'm relatively new to mapbox and digital map building in general and it's not obvious how this is a viable solution. So I use setPadding in combination with maxBounds? While it might work (I haven't tried it yet) the bigger problem for me is that the functionality feels inconsistent -- why do I have to set separate "global padding" options with maxBounds and yet fitBounds accepts these same optional padding parameters but I don't have to supply them because it interprets the bounding box differently?

Perhaps, I'm missing something that's just not intuitive yet. I'm open to better solutions or idiomatic ways to make maxBounds effectively work the same as fitBounds.

Related: #5997, #10209, #11630

@tmcgann
Copy link

tmcgann commented Apr 29, 2022

I found a workaround that allows me to calculate a maxBounds that is similar to the bounds resulting from fitBounds.

import bbox from '@turf/bbox'
import { bounds as viewportBounds } from '@mapbox/geo-viewport'

const countryBoundingBox = bbox(countryGeoJson)
const { center, zoom } = map.cameraForBounds(countryBoundingBox)
const dimensions = [
  Math.floor(map.getCanvas().offsetWidth),
  Math.floor(map.getCanvas().offsetHeight),
]
// Mapbox vector tiles are 512x512, as opposed to the library's assumed default of 256x256, hence the final `512` argument
const bounds = viewportBounds(center.toArray(), zoom, dimensions, 512)

I dug into the code for fitBounds and noticed that internally it leverages cameraForBounds which in turn leverages an internal function called _cameraForBoxAndBearing. I don't quite understand all the logic in that function, but it made me realize that something like this might work. I imagine there's probably a more straightforward solution?

I also tried using getBounds immediately after executing fitBounds but the results weren't accurate. If I setTimeout and waited long enough, I would get desired bounds, but I wasn't comfortable with that kind of hack and wanted something reliable and synchronous.

@pomm0
Copy link

pomm0 commented Jun 16, 2022

I also tried using getBounds immediately after executing fitBounds but the results weren't accurate. If I setTimeout and waited long enough, I would get desired bounds, but I wasn't comfortable with that kind of hack and wanted something reliable and synchronous.

@tmcgann you may want to wait for the moveend event, which should ensure fitBounds has finished fitting, no need for setTimeout:

import turfBboxPolygon from '@turf/bbox-polygon';

map.once('moveend', () => {
     const mapBounds = map.getBounds();
     const bboxSquarePolygon = turfBboxPolygon(
        [mapBounds.getWest(), mapBounds.getSouth(), mapBounds.getEast(), mapBounds.getNorth()]
      );
      map.setMaxBounds(bboxSquarePolygon);
});

@nondescriptpointer
Copy link

maxBounds are very restricting on the zoom level. There is no way to zoom out to see the full bounds on most map display ratio's.

Something like this works much better for me but it's not perfect as dragging stops while your drag operation is out of bounds:

map.on('move', function(){
    let center = map.getCenter()
    if(!boundsobj.contains(center)){
        if(center.lng > boundsobj.getEast()){
            center.lng = boundsobj.getEast();
        }else if(center.lng < boundsobj.getWest()){
            center.lng = boundsobj.getWest();
        }
        if(center.lat > boundsobj.getNorth()){
            center.lat = boundsobj.getNorth();
        }else if(center.lat < boundsobj.getSouth()){
            center.lat = boundsobj.getSouth();
        }
        map.stop();
        map.setCenter(center);
    }
})

@sstenn
Copy link

sstenn commented Aug 14, 2024

I have adjusted @nondescriptpointer's solution a bit so that it works a bit more smoothly.

I've used the easeId option to stop the move callback from executing when bouncing back to the restricted area.

I'm using Vue's nextTick method, but I think it can also be done with setTimeout(() => { ... }, 0)

map.on("move", (e) => {
  if (e.target._easeOptions.easeId === 'bouncing') {
    return;
  }
  let center = map.getCenter();
  if (!maxBounds.contains(center)) {
    if (center.lng > maxBounds.getEast()) {
      center.lng = maxBounds.getEast();
    } else if (center.lng < maxBounds.getWest()) {
      center.lng = maxBounds.getWest();
    }
    if (center.lat > maxBounds.getNorth()) {
      center.lat = maxBounds.getNorth();
    } else if (center.lat < maxBounds.getSouth()) {
      center.lat = maxBounds.getSouth();
    }
    map.stop();
    nextTick(() => {
      map.easeTo({
        center: center,
        duration: 500,
        easeId: 'bouncing'
      });
    })
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants