'Set Map bounds based on multiple marker Lng,Lat

Am using vue and have installed the vue-mapbox component located here: https://soal.github.io/vue-mapbox/#/quickstart

I have updated the js and css to the latest versions also that gets added to the index.html:

<!-- Mapbox GL CSS -->
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.css" rel="stylesheet" />
<!-- Mapbox GL JS -->
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.js"></script>

I am trying to utilize this component to set the default view of the map bounds using either center or bounds or fitBounds to a list of Lng,Lat coordinates. So, basically, how to plug in lng,lat coordinates and have the map default to centering these coordinates inside of the container?

Here's a Component I created, called Map in vue to output the mapbox using the component vue-mapbox listed above:

<template>
  <b-row id="map" class="d-flex justify-content-center align-items-center my-2">
    <b-col cols="24" id="map-holder" v-bind:class="getMapType">
        <mgl-map
          id="map-obj"
          :accessToken="accessToken"
          :mapStyle.sync="mapStyle"
          :zoom="zoom"
          :center="center"
          container="map-holder"
          :interactive="interactive"
          @load="loadMap" 
          ref="mapbox" />
    </b-col>
  </b-row>
</template>

<script>
import { MglMap } from 'vue-mapbox'
export default {
  components: {
    MglMap
  },
  data () {
    return {
      accessToken: 'pk.eyJ1Ijoic29sb2dob3N0IiwiYSI6ImNqb2htbmpwNjA0aG8zcWxjc3IzOGI1ejcifQ.nGL4NwbJYffJpjOiBL-Zpg',
      mapStyle: 'mapbox://styles/mapbox/streets-v9', // options:  basic-v9, streets-v9, bright-v9, light-v9, dark-v9, satellite-v9
      zoom: 9,
      map: {}, // Holds the Map...
      fitBounds: [[-79, 43], [-73, 45]]
    }
  },
  props: {
    interactive: {
      default: true
    },
    resizeMap: {
      default: false
    },
    mapType: {
      default: ''
    },
    center: {
      type: Array,
      default: function () { return [4.899, 52.372] }
    }
  },
  computed: {
    getMapType () {
      let classes = 'inner-map'
      if (this.mapType !== '') {
        classes += ' map-' + this.mapType
      }
      return classes
    }
  },
  watch: {
    resizeMap (val) {
      if (val) {
        this.$nextTick(() => this.$refs.mapbox.resize())
      }
    },
    fitBounds (val) {
      if (this.fitBounds.length) {
        this.MoveMapCoords()
      }
    }
  },
  methods: {
    loadMap () {
      if (this.map === null) {
        this.map = event.map // store the map object in here...
      }
    },
    MoveMapCoords () {
      this.$refs.mapbox.fitBounds(this.fitBounds)
    }
  }
}
</script>

<style lang="scss" scoped>
  @import '../../styles/custom.scss';

  #map {
    #map-obj {
      text-align: justify;
      width: 100%;
    }
    #map-holder {
      &.map-modal {
        #map-obj {
          height: 340px;
        }
      }
      &.map-large {
        #map-obj {
          height: 500px;
        }
      }
    }
    .mapboxgl-map {
      border: 2px solid lightgray;
    }
  }
</style>

So, I'm trying to use fitBounds method here to get the map to initialize centered over 2 Lng,Lat coordinates here: [[-79, 43], [-73, 45]]

How to do this exactly? Ok, I think I might have an error in my code a bit, so I think the fitBounds should look something like this instead:

fitBounds: () => {
  return { bounds: [[-79, 43], [-73, 45]] }
}

In any case, having the most difficult time setting the initial location of the mapbox to be centered over 2 or more coordinates. Anyone do this successfully yet?

Ok, so I wound up creating a filter to add space to the bbox like so:

Vue.filter('addSpaceToBBoxBounds', function (value) {
  if (value && value.length) {
    var boxArea = []
    for (var b = 0, len = value.length; b < len; b++) {
      boxArea.push(b > 1 ? value[b] + 2 : value[b] - 2)
    }
    return boxArea
  }
  return value
})

This looks to be good enough for now. Than just use it like so:

let line = turf.lineString(this.markers)
mapOptions['bounds'] = this.$options.filters.addSpaceToBBoxBounds(turf.bbox(line))
return mapOptions


Solution 1:[1]

setting the initial location of the map to be centered over 2 or more coordinates

You could use Turf.js to calculate the bounding box of all point features and initialize the map with this bbox using the bounds map option:

http://turfjs.org/docs#bbox

https://www.mapbox.com/mapbox-gl-js/api/#map

Solution 2:[2]

I created a few simple functions to calculate a bounding box which contains the most southwestern and most northeastern corners of the given [lng, lat] pairs (markers). You can then use Mapbox GL JS map.fitBounds(bounds, options?) function to zoom the map to the set of markers.

Always keep in mind:
lng (lon): longitude (London = 0, Bern = 7.45, New York = -74)
? the lower, the more western

lat: latitude (Equator = 0, Bern = 46.95, Capetown = -33.9)
? the lower, the more southern

getSWCoordinates(coordinatesCollection) {
  const lowestLng = Math.min(
    ...coordinatesCollection.map((coordinates) => coordinates[0])
  );
  const lowestLat = Math.min(
    ...coordinatesCollection.map((coordinates) => coordinates[1])
  );

  return [lowestLng, lowestLat];
}

getNECoordinates(coordinatesCollection) {
  const highestLng = Math.max(
    ...coordinatesCollection.map((coordinates) => coordinates[0])
  );
  const highestLat = Math.max(
    ...coordinatesCollection.map((coordinates) => coordinates[1])
  );

  return [highestLng, highestLat];
}

calcBoundsFromCoordinates(coordinatesCollection) {
  return [
    getSWCoordinates(coordinatesCollection),
    getNECoordinates(coordinatesCollection),
  ];
}

To use the function, you can just call calcBoundsFromCoordinates and enter an array containing all your markers coordinates:

calcBoundsFromCoordinates([
  [8.03287, 46.62789],
  [7.53077, 46.63439],
  [7.57724, 46.63914],
  [7.76408, 46.55193],
  [7.74324, 46.7384]
])

// returns [[7.53077, 46.55193], [8.03287, 46.7384]]

Overall it might even be easier to use Mapbox' mapboxgl.LngLatBounds() function.

As mentioned in the answer from jscastro in Scale MapBox GL map to fit set of markers you can use it like this:

const bounds = mapMarkers.reduce(function (bounds, coord) {
  return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(mapMarkers[0], mapMarkers[0]));

And then just call

map.fitBounds(bounds, {
 padding: { top: 75, bottom: 30, left: 90, right: 90 },
});

Solution 3:[3]

If you don't want to use yet another library for this task, I came up with a simple way to get the bounding box, here is a simplified vue component.

Also be careful when storing your map object on a vue component, you shouldn't make it reactive as it breaks mapboxgl to do so

import mapboxgl from "mapbox-gl";

export default {
    data() {
        return {
            points: [
                {
                    lat: 43.775433,
                    lng: -0.434319
                },
                {
                    lat: 44.775433,
                    lng: 0.564319
                },
                // Etc...
            ]
        }
    },
    computed: {
        boundingBox() {
            if (!Array.isArray(this.points) || !this.points.length) {
                return undefined;
            }

            let w, s, e, n;

            // Calculate the bounding box with a simple min, max of all latitudes and longitudes
            this.points.forEach((point) => {
                if (w === undefined) {
                    n = s = point.lat;
                    w = e = point.lng;
                }

                if (point.lat > n) {
                    n = point.lat;
                } else if (point.lat < s) {
                    s = point.lat;
                }
                if (point.lng > e) {
                    e = point.lng;
                } else if (point.lng < w) {
                    w = point.lng;
                }
            });
            return [
                [w, s],
                [e, n]
            ]
        },
    },
    watch: {
        // Automatically fit to bounding box when it changes
        boundingBox(bb) {
            if (bb !== undefined) {
                const cb = () => {
                    this.$options.map.fitBounds(bb, {padding: 20});
                };
                if (!this.$options.map) {
                    this.$once('map-loaded', cb);
                } else {
                    cb();
                }
            }
        },
        // Watch the points to add the markers
        points: {
            immediate: true, // Run handler on mount (not needed if you fetch the array of points after it's mounted)
            handler(points, prevPoints) {
                // Remove the previous markers
                if (Array.isArray(prevPoints)) {
                    prevPoints.forEach((point) => {
                        point.marker.remove();
                    });
                }

                //Add the new markers
                const cb = () => {
                    points.forEach((point) => {

                        // create a HTML element for each feature
                        const el = document.createElement('div');
                        el.className = 'marker';
                        el.addEventListener('click', () => {
                            // Marker clicked
                        });
                        el.addEventListener('mouseenter', () => {
                            point.hover = true;
                        });
                        el.addEventListener('mouseleave', () => {
                            point.hover = false;
                        });

                        // make a marker for each point and add to the map
                        point.marker = new mapboxgl.Marker(el)
                            .setLngLat([point.lng, point.lat])
                            .addTo(this.$options.map);
                    });
                };
                if (!this.$options.map) {
                    this.$once('map-loaded', cb);
                } else {
                    cb();
                }
            }
        }
    },
    map: null, // This is important to store the map without reactivity
    methods: {
        mapLoaded(map) {
            this.$options.map = map;
            this.$emit('map-loaded');
        },
    },
}

It should work fine as long as your points aren't in the middle of the pacific juggling between 180° and -180° of longitude, if they are, simply adding a check to invert east and west in the return of the bounding box should do the trick

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 André L.
Solution 3