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

What is the process for changing and updating the key of an object based on comparisons with other objects?

My task involves working with an array of objects that contain unique IDs as keys. const sampleObj1 = {0011:[{},{}], 0022:[{}, {}], 0033:[{},{}]} const sampleObj2 = [{id:0011, name:'test1'}, {id:0022, name:'test2'}, {id:0033, name:&apos ...

What is the best way to automatically have the first bar in a column highchart be selected when the page loads?

My highchart consists of a simple column. When I click on any bar in the chart, it gets selected. However, I also want the 1st bar to be selected by default. var chart = $('#container').highcharts(); Upon page load, I have obtained this object. ...

Utilize a while loop in JavaScript to trigger a message when a variable dips below zero

Forgive me if I seem clueless. I am a beginner in the world of Javascript and am currently experimenting with loops. At the moment, I am toying around with this particular piece of code: <!DOCTYPE html> <html> <body> <button oncl ...

PHP not receiving POST information from $.ajax call

My JavaScript triggers a POST method when the datepicker loses focus, calling the script rent-fetch-pick-up-point.php. However, the PHP script gets stuck at the if-statement because it's not receiving the POST data. The datepicker is associated with a ...

Step-by-step guide to installing gatsby-CLI on Windows without requiring admin permissions

Currently, I am facing an issue while trying to install the gatsby CLI using the following npm command: npm install --global gatsby-cli I suspect the problem might be due to lack of admin access. Does anyone have suggestions on how to resolve this error? ...

Is it possible to modify the parameters of a function by utilizing a MethodDecorator without affecting the "this" value?

Consider a scenario where you need to dynamically modify method arguments using a decorator at runtime. To illustrate this concept, let's simplify it with an example: setting all arguments to "Hello World": export const SillyArguments = (): MethodDec ...

Node JS Axios Network Error due to CORS Policy Restrictions

When attempting to make a put axios request, I encounter the following error: I have installed and enabled the CORS module in the server.js file, but it doesn't seem to be working. Additionally, there are no CORS headers in the request: In the serve ...

Transforming a request from Angular to PHP through formatting

I am currently working on creating an add function for my Angular application that communicates with my PHP back-end. I am attempting to send data to the server using a transformationRequest, but I am unsure about the correct format that matches the $_POST ...

Refreshing div content based on dropdown selection without reloading the page

I am currently working on implementing a dynamic dropdown feature that will update text content on a webpage without needing to refresh the entire page. The updated text will be fetched from a PHP function which receives input from the dropdown selection. ...

Learn how to clear form values in react-bootstrap components

My objective upon clicking the register button is: Clear all input fields Hide any error tooltips Check out the CodeSandbox link I attempted to reset using event.target.reset();, but the tooltips persist on the screen. export default function App() { ...

JavaScript and the importance of using commas in arrays

I am developing a system that displays text in a textarea when a checkbox is checked and removes the text when the checkbox is unchecked. The functionality is mostly working as intended, but I am facing an issue where commas remain in the textarea after un ...

Ways to retrieve the baseURL of an axios instance

This question is being posted to provide an easy solution for fellow developers who may be looking for an answer. //Suppose you have an axios instance declared in a module called api.js like this: var axios = require('axios'); var axiosInstance ...

In Typescript, if at least one element in an array is not empty, the function should return false without utilizing iterators

My current approach involves receiving a string array and returning false if any of the elements in the array is false. myMethod(attrs: Array<String>) { for (const element of attrs) { if (!element) { return false; } } ...

Clicking on the button in Angular 2+ component 1 will open and display component 2

I've been developing a Angular 4 application with a unique layout consisting of a left panel and a right panel. In addition to these panels, there are 2 other components within the application. The left panel component is equipped with buttons while ...

Customizable form fields in AngularJS

Consider the scenario of the form below: First Name: string Last Name: string Married: checkbox % Display the following once the checkbox is selected % Partner First Name: Partner Last Name: [Submit] How can a form with optional fields be created in Angu ...

What is the best way to remove input focus when clicked away?

I am in the process of developing an application using next js, and I need assistance with designing a search field. The primary functionality I am looking to implement is displaying search suggestions when the user starts typing, but hiding them when the ...

What is the correct way to have Material-UI's <TextField/> component return with a ref attribute, similar to <input/> in ReactJS?

Using this particular method: handleClick(event) { const inputText = this.refs.inputText console.log(inputText.value.trim()) } I am attempting to make Material-UI's <TextField/> return the input text accurately with a ref, similar ...

Using Node.js to download and install npm packages from the local hard drive

Is there a way to add an npm package to my Node.js project from my hard drive? It seems like the admin at work has restricted access to npm. I managed to install npm, but whenever I attempt to run "npm install express" in the command line, I keep getting ...

The JavaScript popup is not functioning properly when using a comparison operator

There are 5 links with mini preview photos and URLs. Out of the 3 links, two are considered good while the other two are not. Clicking on a good link takes me to a new page, but clicking on an error link changes the href attribute to addressError, triggeri ...

Learn how to convert data to lowercase using Vue.js 2

I am attempting to convert some data to lowercase (always lowercase) I am creating a search input like : <template id="search"> <div> <input type="text" v-model="search"> <li v-show="'hello'.includes(sea ...