Mapbox GL JS getBounds()/fitBounds()

JavascriptJsonMapbox

Javascript Problem Overview


I'm using Mapbox GL JS v0.14.2 and I've searched high and low through the documentation and very little is clear about this.

If you use the standard JS API, it's very clear to 'fit map to markers' using an example they have provided (https://www.mapbox.com/mapbox.js/example/v1.0.0/fit-map-to-markers/); however the setup when using the GL api is quite different. The GL API has getBounds() (https://www.mapbox.com/mapbox-gl-js/api/#Map.getBounds) but because you don't have a named layer, like the standard JS API, so I'm struggling to work out how to use getBounds() at all.

I've found this (https://stackoverflow.com/questions/34281106/mapbox-gl-js-api-set-bounds) but surely can't be the right answer?

This is the bulk of my setup; excluding JSON setup and other options.

mapboxgl.accessToken = '<myaccesstoken>';

var markers = <?php echo $programme_json; ?>;

var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/richgc/cikyo5bse00nqb0lxebkfn2bm',
    center: [-1.470085, 53.381129],
    zoom: 15
});

map.on('style.load', function() {
    map.addSource('markers', {
        'type': 'geojson',
        'data': markers
    });

    map.addLayer({
        "id": "markers",
        "interactive": true,
        "type": "symbol",
        "source": "markers",
        "layout": {
            "icon-image": "venue-map-icon-blue",
            'icon-size': 0.5,
            "icon-allow-overlap": true
        }
    });
    
    map.scrollZoom.disable();
    
});

I have tried the following:

alert(map.getBounds()); // LngLatBounds(LngLat(-1.4855345239256508, 53.37642500812015), LngLat(-1.4546354760740883, 53.38583247227842))
var bounds = [[-1.4855345239256508, 53.37642500812015],[-1.4546354760740883, 53.38583247227842]]
map.fitBounds(bounds);

So I know how to fitBounds, but I'm unsure how to get them map.getBounds() just seems to return the set centre position lng/lat.

Markers JSON:

var markers = {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"title":"Site Gallery","url":"\/Freelance\/art-sheffield-2016\/programme\/site-gallery\/","summary":"Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Donec id justo. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Suspendisse feugiat. Etiam rhoncus.","image":"\/Freelance\/art-sheffield-2016\/site\/assets\/files\/1032\/site_gallery.jpg","marker-symbol":"venue-map-icon-blue","colour":"blue"},"geometry":{"type":"Point","coordinates":["-1.466439","53.376842"]}},{"type":"Feature","properties":{"title":"Moore Street Substation","url":"\/Freelance\/art-sheffield-2016\/programme\/moore-street-substation\/","summary":"","image":null,"marker-symbol":"venue-map-icon-green","colour":"green"},"geometry":{"type":"Point","coordinates":["-1.477881","53.374798"]}},{"type":"Feature","properties":{"title":"S1 Artspace","url":"\/Freelance\/art-sheffield-2016\/programme\/s1-artspace\/","summary":"","image":null,"marker-symbol":"venue-map-icon-red","colour":"red"},"geometry":{"type":"Point","coordinates":["-1.459620","53.380562"]}}]};

Javascript Solutions


Solution 1 - Javascript

If you want to fit map to markers, you can create bounds that contains all markers.

var bounds = new mapboxgl.LngLatBounds();

markers.features.forEach(function(feature) {
    bounds.extend(feature.geometry.coordinates);
});

map.fitBounds(bounds);

Solution 2 - Javascript

For a solution that will work for all GeoJSON objects, not just a set of markers, check out Mapbox's [Turf.js][1].

This code was very helpful to me: https://bl.ocks.org/danswick/83a8ddff7fb9193176a975a02a896792

But just to repeat the basics in case that link dies:

var bounds = turf.bbox(markers);
map.fitBounds(bounds, {padding: 20});

The extent method mentioned in the linked code has been deprecated in favour of bbox, but the result is the same. [1]: http://turfjs.org/

Solution 3 - Javascript

Mapbox's own geojson-extent plugin will do the trick. Assuming your markers object is valid GeoJSON, you can simply pass it to the geojsonExtent() function to get a set of bounds that you can then pass to fitBounds().

Once you load the geojson-extent.js file (e.g., by using a <script> tag in your HTML code), you should be able to do this to fit your map to the bounds of your GeoJSON markers object:

map.fitBounds(geojsonExtent(markers));

UPDATE

GeoJSONLint reports that your markers object is not valid GeoJSON because the elements in each position are being interpreted as strings, not numbers. If you remove the quotes from the lon-lat coordinates, it should work fine:

var markers = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "title": "Site Gallery",
        "url": "\/Freelance\/art-sheffield-2016\/programme\/site-gallery\/",
        "summary": "Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Donec id justo. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Suspendisse feugiat. Etiam rhoncus.",
        "image": "\/Freelance\/art-sheffield-2016\/site\/assets\/files\/1032\/site_gallery.jpg",
        "marker-symbol": "venue-map-icon-blue",
        "colour": "blue"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          -1.466439,
          53.376842
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "title": "Moore Street Substation",
        "url": "\/Freelance\/art-sheffield-2016\/programme\/moore-street-substation\/",
        "summary": "",
        "image": null,
        "marker-symbol": "venue-map-icon-green",
        "colour": "green"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          -1.477881,
          53.374798
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "title": "S1 Artspace",
        "url": "\/Freelance\/art-sheffield-2016\/programme\/s1-artspace\/",
        "summary": "",
        "image": null,
        "marker-symbol": "venue-map-icon-red",
        "colour": "red"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          -1.459620,
          53.380562
        ]
      }
    }
  ]
};

Solution 4 - Javascript

This is my solution based on the reduce operation from a Mapbox example.

It works for geojson that contain point features and multi-point features like lines.

let bounds = geoJSON.features.reduce(function(bounds, feature) {
  if(!Array.isArray(feature.geometry.coordinates[0])) { // point feature
    return bounds.extend(feature.geometry.coordinates);
  } else {
    return feature.geometry.coordinates.reduce(function(bounds, coord) {
      return bounds.extend(coord);
    }, bounds);
  }
}, new mapboxgl.LngLatBounds());

map.fitBounds(bounds, {
  maxZoom: 12,
  padding: 30, // in px, to make markers on the top edge visible
})

Solution 5 - Javascript

Improved solution (based on @timur solution) to handle large data-sets, by using while loop and promise

setBounds() {
            return new Promise((resolve, reject) => {
                const bounds = new mapboxgl.LngLatBounds();
                let i = 0;
                while (i < GEO_JSON.length) {
                    bounds.extend(GEO_JSON[i].geometry.coordinates/OR/Markers_ARRAY);
                    i++;
                }
                if (bounds) {
                    resolve(bounds);
                } else {
                    reject(`error:${  bounds}`);
                }
            });
        },

fitBoundsToMarkers() {
            this.setBounds().then((bounds) => {
                    map.fitBounds(bounds, {
                        padding: {
                            top: 100,
                            bottom: 100,
                            left: 100,
                            right: 100,
                        },
                        maxZoom: 14,
                    });
            });
        },

Solution 6 - Javascript

If anyone here is using React-Map-GL, has more than two markers, and wants to figure out the bounds before loading the map and retrieving the Mapbox instance you can do this:

const lat = myCoordinatesArray.map(location => parseFloat(location.lat));
const lng = myCoordinatesArray.map(location => parseFloat(location.lng));

// Note that WebMercatorViewport requires this format [lng, lat]
const minCoords = [Math.min.apply(null, lng), Math.min.apply(null, lat)];
const maxCoords = [Math.max.apply(null, lng), Math.max.apply(null, lat)];
const formattedGeoData = [minCoords, maxCoords];

const vPort = new WebMercatorViewport(this.state.viewport).fitBounds(formattedGeoData, {
  padding: 100
});

const { latitude, longitude, zoom } = vPort;

Src: https://stackoverflow.com/a/40506115/1259863

You can do all this in the constructor way before the render function and get your values for the plugin init.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJohn the PainterView Question on Stackoverflow
Solution 1 - JavascriptTimur BilalovView Answer on Stackoverflow
Solution 2 - JavascriptChidGView Answer on Stackoverflow
Solution 3 - JavascriptCharlie GreenbackerView Answer on Stackoverflow
Solution 4 - JavascriptrriemannView Answer on Stackoverflow
Solution 5 - JavascriptEranGrinView Answer on Stackoverflow
Solution 6 - JavascriptScott LeonardView Answer on Stackoverflow