Establish map boundaries using the longitude and latitude of multiple markers

Utilizing Vue, I have integrated the vue-mapbox component from this location:

I've made sure to update the js and css to the latest versions and added them 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>

The goal is to use this component to define the default view of the map bounds by setting either center, bounds, or fitBounds to a list of Lng, Lat coordinates. Essentially, how can lng, lat coordinates be plugged in to auto-center the map within the container?

Here's a custom Component named Map within Vue that displays the mapbox using the mentioned vue-mapbox component:

<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',
      zoom: 9,
      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
      }
    },
    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>

To achieve this, I am attempting to use the fitBounds method with two Lng,Lat coordinates specified here: [[-79, 43], [-73, 45]]

How can this be done effectively? It seems there may be an error in the code, so adjusting the fitBounds might look something like this instead:

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

Despite some challenges, I managed to create a filter to add space to the bbox like follows:

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 should suffice for now. To implement it, simply use it as shown below:

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

Answer №1

In order to determine a bounding box that encompasses the most southwestern and northeastern corners of given [lng, lat] pairs (markers), I developed a series of basic functions. Utilize the Mapbox GL JS map.fitBounds(bounds, options?) function to adjust the map view to focus on the set of markers.

Remember:
lng (lon): represents longitude (e.g., London = 0, Bern = 7.45, New York = -74)
→ lower values indicate western direction

lat: denotes latitude (e.g., Equator = 0, Bern = 46.95, Cape Town = -33.9)
→ lower values correspond to southern locations

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 implement the function, simply invoke calcBoundsFromCoordinates and input an array containing all marker coordinates:

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

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

Alternatively, utilizing Mapbox's mapboxgl.LngLatBounds() function can simplify the process.

Referencing the response by jscastro in Scale MapBox GL map to fit set of markers, the implementation is as follows:

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

Subsequently, execute the following command:

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

Answer №2

Adjusting the starting position of the map to center over multiple coordinates

If you want to set the initial bounding box for your map to cover all point features, you can utilize Turf.js. This library allows you to calculate the bounding box and then initialize the map with this bbox using the bounds option:

Check out Turf.js documentation on how to use the bounding box method.

To learn more about setting map options in Mapbox GL JS, visit their API documentation.

Answer №3

If you prefer not to rely on another library for this particular task, I have devised a straightforward method to obtain the bounding box. Below is a simplified vue component that demonstrates this.

Remember to exercise caution when storing your map object within a vue component; avoid making it reactive to prevent any interference with mapboxgl functionality.

import mapboxgl from "mapbox-gl";

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

            let w, s, e, n;

            // Calculate the bounding box using simple min and 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 adjust to fit the bounding box upon 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();
                }
            }
        },
        // Monitor the points array to add markers accordingly
        points: {
            immediate: true, // Execute handler upon mount (not necessary if fetching points after mounting)
            handler(points, prevPoints) {
                // Remove previous markers
                if (Array.isArray(prevPoints)) {
                    prevPoints.forEach((point) => {
                        point.marker.remove();
                    });
                }

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

                        // Create an HTML element for each feature
                        const el = document.createElement('div');
                        el.className = 'marker';
                        el.addEventListener('click', () => {
                            // Handle marker click event
                        });
                        el.addEventListener('mouseenter', () => {
                            point.hover = true;
                        });
                        el.addEventListener('mouseleave', () => {
                            point.hover = false;
                        });

                        // Generate 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, // Crucial to store the map without reactivity
    methods: {
        mapLoaded(map) {
            this.$options.map = map;
            this.$emit('map-loaded');
        },
    },
}

This approach should function correctly unless your points are situated in the middle of the Pacific Ocean, switching between 180° and -180° longitude. In such cases, adding a simple check to swap east and west while returning the bounding box should resolve the issue.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorporating Framer Motion into traditional React class components (non-functional approach)

I'm encountering an issue with a simple animation using Framer Motion that functions correctly in a functional component, but fails to work in a class component. I am new to Framer Motion and here is my react code => import {motion} from 'fr ...

Calculating the sum of table columns with the help of knockout.js

Is there a way to calculate the table columns using knockout.js? I am familiar with jQuery but new to knockout.js and unsure how to approach this. Instead of generating the table data using JSON, I would like to directly create it in the HTML table itself. ...

We apologize, but the module you are looking for cannot be found: Unable to locate 'fs'

Trying to create a new MDX blog website using Next.js 13 with the latest app router, but encountering an error message saying "Module not found: Can't resolve 'fs'". It was working fine in Next.js 12 and pages directory, but not in the lates ...

Switch between light and dark modes with the MUI theme toggle in the header (AppBar)

I'm currently working on implementing a feature that allows users to switch between dark and light themes in my web app. The challenge I am facing is how to ensure that this theme change functionality is available throughout the entire app, not just i ...

Can someone provide instructions on how to convert base64 data to an image file

I'm utilizing the vue-signature Library but I am unsure how to download the base64 data that is generated as an image. Here is the link to the library: https://www.npmjs.com/package/vue-signature. I have gone through the documentation and noticed that ...

The functionality of a generated button has been compromised

My goal is to create a webshop that doesn't rely on MySQL or any database, but instead reads items from JSON and stores them in LocalStorage. However, I've encountered an issue where the functionality of buttons gets lost after using AJAX to gene ...

I struggled to modify the image cropping code to specify a particular image

(I will attempt to explain my issue once again) I came across a script online which can be viewed at this link : Link However, I am having trouble modifying the code to suit my needs. The script currently starts working on image upload, but I want it t ...

Is there a way to retrieve the left offset of a floating element even when it is positioned outside the viewport?

My current situation involves creating several panels that are stacked side by side within a main container. Each panel takes up 100% of the viewport width and height. I want to be able to horizontally scroll to each panel when clicking on their respective ...

Associate text with a color from a predetermined list (JavaScript)

As I work on adding tags to my website for blog posts, I have a specific vision in mind. Each tag should be assigned a unique background color selected from a predefined array of theme colors. My goal is to assign the same background color to tags with id ...

The method firebaseApp.auth does not exist in user authentication using Firebase

Implementing user authentication with Firebase in my React webapp has been a challenge due to issues with the firebaseAuth.app() function. Despite trying various solutions such as reinstalling firebase dependencies, updating imports to SDK 9 syntax, and ad ...

Tips for choosing and deselecting data using jQuery

Is there a way to toggle the selection of data in my code? Currently, when I click on the data it gets selected and a tick image appears. However, I want it so that when I click again on the same data, the tick will disappear. How can I achieve this func ...

Creating Functional Tabs Using CSS and JavaScript

I've been experimenting with this code snippet, trying to get it to work better. It's still a work in progress as I'm new to this and have only customized it for my phone so far. The issue can be seen by clicking on the Projects and Today ta ...

Retrieve all the records from the collection that have a specific reference number in their ID field

Is it feasible to pull together all documents with an ID that includes a specific value? I am working with Angular 7. I attempted using db.collection('items').where.. but unfortunately, this method is not supported. For instance: (collection) ...

When using express and passport-local, the function `req.isAuthenticated()` will typically return a

I'm seeking some insight into my current situation. I've noticed that whenever I call req.isAuthenticated() in an app.router endpoint, running on port 3001 via the fetch API, it always returns false. It seems like the connect.sid is not being pro ...

Enhance the annotation of JS types for arguments with default values

Currently, I am working within a code base that predominantly uses JS files, rather than TS. However, I have decided to incorporate tsc for type validation. In TypeScript, one method of inferring types for arguments is based on default values. For example ...

Invalid controller function path specified in Laravel framework for AJAX request

I am struggling to find the correct ajax URL as the one I am currently using is not working. The console shows the following error message: GET XHR localhost:8000/Controller/getUnitSellingPrice [HTTP/1.0 404 Not Found 203ms] In the create.blade View: ...

A guide to eliminating TextRow and inserting a string into JSON using NodeJs

To remove TextRow and add the string true to JSON in NodeJs, I have included the following code: NodeJs Code: function groupBy(objectArray, property) { return objectArray.reduce(function (acc, obj) { let key = obj[property] if (!acc[key]) { ...

Store the active tab in AngularJS with Bootstrap to easily remember and display

After creating a basic AngularJS application with the Bootstrap directive, I noticed that some of my pages have tabs. The issue arises when I am on a tab other than the first one and click a link to navigate to another view. Upon returning (using either th ...

Error: Attempting to access the 'HotelInfo' property of an undefined variable led to an uncaught TypeError

//initiating an AJAX request to access the API jQuery(document).ready(function() { jQuery.ajax({ url:"http://localhost:8080/activitiesWithRealData?location=%22SEA%22&startDate=%2205-14-16%22&endDate=%2205-16-16%22&a ...

Exploring the realms of Django Administrator with external JavaScript integration

Currently, I am working with django 2.0 that includes jquery 2.2.3. My goal is to implement the ImageViewer Javascript app (https://github.com/s-yadav/ImageViewer) on one of my admin pages. I have added the necessary js and css files to the Media class wit ...