Mapbox-gl-js: Add a way to set initial map bounds

Created on 22 Jan 2016  Â·  27Comments  Â·  Source: mapbox/mapbox-gl-js

Is there no way to set the initial bounds of a Map?

options.maxBounds allows one to set the initial bounding box, but also permanently constrains the view to this box.

A combination of .center and .zoom would effectively allow one to set initial bounds, but also requires some computation, and is inexact.

Is there an easy way to set an initial options.bounds ?

feature good first issue

Most helpful comment

A workaround to initialize a map to a bounding box is possible using the geo-viewport library.

Pass a bounding box and the map HTML element dimensions to the geoViewport.viewport() function. Use the returned center and zoom level to initialize the map.

const mapboxgl = require('mapbox-gl');
const geoViewport = require('@mapbox/geo-viewport');

var mapEl = document.getElementById('map').getBoundingClientRect();  
var bounds = [-123.749, 28.613, -61.1718, 48.69096];
var mapDim = [mapEl.height, mapEl.width];
var newbounds = geoViewport.viewport(bounds, mapDim, 0, 20, 512);

mapboxgl.accessToken = 'my-token';
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/light-v9',
    center: newbounds.center,
    zoom: newbounds.zoom
});

All 27 comments

+1

On Fri, Jan 22, 2016 at 2:12 AM, Jon Peck [email protected] wrote:

Is there no way to set the initial bounds of a Map?

optionsmaxBounds https://wwwmapboxcom/mapbox-gl-js/api/#Map allows one
to set the initial bounding box, but also permanently constrains the view
to this box

A combination of center and zoom would effectively allow one to set
initial bounds, but also requires some computation, and is inexact

Is there an easy way to set an initial optionsbounds ?

—
Reply to this email directly or view it on GitHub
https://github.com/mapbox/mapbox-gl-js/issues/1970.

You are correct. That feature does not exist but should :+1:

To get around this at the moment, I've used the 'geojsonExtent' mapbox.js library to facilitate zooming to bounds. Agreed it would be great to have as a standard feature in the API.

<script src='https://api.mapbox.com/mapbox.js/plugins/geojson-extent/v0.0.1/geojson-extent.js'></script>
function fit() {
    //fit gl map to a geojson file bounds
    try {
        map.fitBounds(geojsonExtent( geojson_data ));
    }
    catch (err) {
        console.log(err);
    }
}

@ryanbaumann I think there's a misunderstand there. I believe what @peckjon is asking for is not a method to to zoom to a bounding box (Map#fitBounds) per se, because this is actually already part of the library. The problem is that the current function is animated so you can't instantly change the center/zoom of a map like you can with Map#jumpTo, using a bounding box. What this ticket asks for is a fitBounds without a transition (and possibly also as input when instantiating the map).

Also, getting the bounding box for a geometry I would argue is out of scope for the library, but can easily done with for example turf.js (http://turfjs.org/static/docs/module-turf_extent.html).

Unanimated Map#fitBounds is #1473. This issue is about bounds-based map initialization.

@jfirebaugh Gotcha, but I still got the impression that @ryanbaumann was asking for functionality in the API to actually calculate the bounds for you based on a given geometry, i.e. something like "Map#fitGeojson(geojson)"

@averas I was off-topic on my comment. Apologies.

My intent was to communicate that from a user perspective, I want to initialize a map to fit the bounds of a data layer. Zooming to bounds after the data layer is added to the map is acceptable.

This is missing for my project too!
I am calculating the bounds from an existing list of coordinates, e.g., like this:

var devicesBounds = new mapboxgl.LngLatBounds();

// iterate over all markers
for (var i = 0; i < devices.length; i++)
{
    var device = devices[i];

    // add the current coordinates to the bounds array, so we can fit the map extent to the bounds of all markers
    devicesBounds.extend([
        device.longitude,
        device.latitude
    ]);
}

And then I am setting the map config parameters like this

mapboxConfig.center = devicesBounds.getCenter();
mapboxConfig.zoom = 14;

However, this is far from optimal, as the zoom level is static. I would like to do it like this:

mapboxConfig.initialBounds = devicesBounds;

Closely related to #4029

+1

Being able to specify an initial bounds when the map is instantiated would be very convenient - we definitely have a need for that. Currently I will work around this by calculating the center pt and zoom based on the bounding box and the div dimensions, but even better to have this built in to the api. Bonus points for integrating with the camera options and setting the center & zoom from the bounds, and the bearing and pitch from the camera options.

A workaround to initialize a map to a bounding box is possible using the geo-viewport library.

Pass a bounding box and the map HTML element dimensions to the geoViewport.viewport() function. Use the returned center and zoom level to initialize the map.

const mapboxgl = require('mapbox-gl');
const geoViewport = require('@mapbox/geo-viewport');

var mapEl = document.getElementById('map').getBoundingClientRect();  
var bounds = [-123.749, 28.613, -61.1718, 48.69096];
var mapDim = [mapEl.height, mapEl.width];
var newbounds = geoViewport.viewport(bounds, mapDim, 0, 20, 512);

mapboxgl.accessToken = 'my-token';
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/light-v9',
    center: newbounds.center,
    zoom: newbounds.zoom
});

Thanks Ryan, looks like a good option.

That is very similar to what I am doing, except I am setting a "default" zoom level.

I think this can be closed if I'm not mistaken?

I was able to accomplish this by doing:
map.fitBounds(bbox, { duration: 0 })

Technically that is a Map#fitBounds without transition, but still accomplishes the task at hand (albeit not the smoothest transition in the world)...

I think it'd make more sense to supply the bounds on init.
It's a very common need and feels quite messy to set an arbitrary zoom and center to just jump via fitBounds.

@iplanwebsites exactly. The solution provided by @eltonjuan is the temporary fix for the problem. It solves the task at hand, but it makes the map "jump" after initialiation, which I (and many others) consider as poor user experience.

Thanks @anx-ckreuzberger. Upon coding an ugly workaround, I realized that firBounds doesn't even work with bounds over the dateline. Unless I'm mistaken, it seems impossible to fit the bounds of Alaska or a flight from HK to SF. I'm debating coding a workaround making use of a center point and a custom zoom-level but it feel so counter-productive to do so much ground work for such a basic need. Do you know if the Google-maps API has figured this out?

here's the forked pen with the fix: https://codepen.io/mcginnisb/pen/Jrxowe?editors=0011

A related fundamental issue is that it is impossible to correctly render a geometry that crosses the dateline.

Connecting the dots, I came up with the following "workaround":

// init the map _without_ a center (it defaults to [0,0])
var map = new mapboxgl.Map({...})

// immediately after, not after the `load` event
// the goal is for the map to load at the specified bounds, 
// without loading any unrelated resources related to [0,0] 
map.fitBounds(myBounds, {linear: true, duration: 0})

I'm not sure this is officially supported, also then the console shows:

Uncaught TypeError: Cannot read property 'transition' of undefined
    at e._render (map.js:1484)
    at map.js:1569

(happens when using mapboxgl v0.43.0)

My questions:

  1. Is this error safe to ignore?
  2. Would any of you say my hack is acceptable/reasonable? (or am I voiding the warranty by not waiting for the map to fully load first)

We are running into this issue as well. There are a lot of comments that do not seem to grasp the importance of initializing to a bounding box instead of initializing and then immediately calling fitBounds() (animated or not), so please consider:

  • Load performance from the user’s perspective (everyone’s highest priority, correct? Even if immediately calling fitBounds(), there can be a delay)
  • Efficient use of network resources (reducing the number of tilesets fetched)
  • Code reduction & performance (Less client side JS is better, plus fewer calculations & redraws; in our case, we actually work exclusively with bboxes)
  • Reduced MapBox API use & reduction of map views (we pay more because it costs MapBox more)

@ryanbaumann’s workaround using geo-viewport is the most appropriate, but is still additional code that shouldn’t be required.

If @ryanbaumann map.fitBounds approach is not appropriate for your use case, you can use the excellent viewport-mercator-project. It contains a fitBounds method which returns center and zoom. These can then be used to initialize your map. It also contains handy padding and offset options.

@sharkinsspatial Just to clarify, @ryanbaumann’s solution uses geoViewport.viewport() instead of fitBounds() (just for initialization) and hence is avoids calling fitBounds() post initialization.

Thanks for pointing out viewport-mercator-project. I’d personally lean towards @ryanbaumann’s solution using geo-viewport as it is developed & supported by MapBox, plus is <5K (minimized; viewport-mercator-project is significantly larger).

Any timeline on this issue?

duration 0 is not a good solution:

1) you still see jumping map after load
2) you pay for extra tiles 💸 💸 💸

This was fixed in #5518

Was this page helpful?
0 / 5 - 0 ratings