Google Maps Distance Matrix API - Outputting More Than 25 Destinations

Published on
-
4 min read

The Google Maps Distance Matrix API gives us the capability to calculate travel distance and time between multiple locations across different modes of transportation, such as driving walking, or cycling. This is just one of the many other APIs Google provides to allow us to get the most out of location and route related data.

I needed to use the Google Distance Matrix API (GDMA) to calculate the distance of multiple points of interests (destinations) from one single origin. The dataset of destinations consisted of sixty to one-hundred rows of data containing the following:

  • Title
  • Latitude
  • Longitude

This dataset would need to be parsed to the GDMA as destinations in order get the information on how far each item was away from the origin. One thing came to light during integration was that the API is limited to only outputting 25 items of distance data per request.

The limit posed by the GDMA would be fine for the majority of use-cases, but in my case this posed a small problem as I needed to parse the whole dataset of destinations to ensure all points of interests were ordered by the shortest distance.

The only way I could get around the limits posed by the GDMA was to batch my requests 25 destinations at a time. The dataset of data I would be parsing would never exceed 100 items, so I was fairly confident this would be an adequate approach. However, I cannot be 100% certain what the implications of such an approach would be if you were dealing with thousands of destinations.

The code below demonstrates a small sample-set of destination data that will be used to calculate distance from a single origin.

/*
	Initialise the application functionality.
*/
const initialise = () => {
	const destinationData = [
                    {
                      title: "Wimbledon",
                      lat: 51.4273717,
                      long: -0.2444923,
                    },
                    {
                      title: "Westfields Shopping Centre",
                      lat: 51.5067724,
                      long: -0.2289425,
                    },
                    {
                      title: "Sky Garden",
                      lat: 51.3586154,
                      long: -0.9027887,
                    }
                  ];
                  
	getDistanceFromOrigin("51.7504091", "-1.2888729", destinationData);
}

/*
	Processes a list of destinations and outputs distances closest to the origin.
*/
const getDistanceFromOrigin = (originLat, originLong, destinationData) => {
  const usersMarker = new google.maps.LatLng(originLat, originLong);
  let distanceInfo = [];
  
  if (destinationData.length > 0) {
  	// Segregate dealer locations into batches.
    const destinationBatches = chunkArray(destinationData, 25);

    // Make a call to Google Maps in batches.
    const googleMapsRequestPromises = destinationBatches.map(batch => googleMapsDistanceMatrixRequest(usersMarker, batch));

    // Iterate through all the aynchronous promises returned by Google Maps batch requests.
    Promise.all(googleMapsRequestPromises).then(responses => {
      const elements = responses.flatMap(item => item.rows).flatMap(item => item.elements);

      // Set the distance for each dealer in the dealers data
      elements.map(({ distance, status }, index) => {
        if (status === "OK") {
          destinationData[index].distance = distance.text;
          destinationData[index].distance_value = distance.value;
        }
      });
      
      renderTabularData(destinationData.sort((a, b) => (a.distance_value > b.distance_value ? 1 : -1)));
    })
    .catch(error => {
      console.error("Error calculating distances:", error);
    });
  }
}

/*
	Outputs tabular data of distances.
*/
renderTabularData = (destinationData) => {
	let tableHtml = "";
  
    tableHtml = `<table>
                    <tr>
                        <th>No.</th>
                        <th>Destination Name</th>
                        <th>Distance</th>
                    </tr>`;

	if (destinationData.length === 0) {
        tableHtml += `<tr colspan="2">
                        <td>No data</td>
                    </tr>`;
  }
  else {
        destinationData.map((item, index) => {
  		        tableHtml += `<tr>
                                <td>${index+1}</td>
                                <td>${item.title}</td>
                                <td>${item.distance}</td>
                            </tr>`;
            });
  }
  
  tableHtml += `</table>`;
  
  document.getElementById("js-destinations").innerHTML = tableHtml;
}

/*
	Queries Google API Distance Matrix to get distance information.
*/
const googleMapsDistanceMatrixRequest = (usersMarker, destinationBatch) => {
  const distanceService = new google.maps.DistanceMatrixService();
  let destinationsLatLong = [];
  
  if (destinationBatch.length === 0) {
  	return;
  }
  
  destinationBatch.map((item, index) => {
    destinationsLatLong.push({
      lat: parseFloat(item.lat),
      lng: parseFloat(item.long),
    });
  });
  
  const request = 
        {
          origins: [usersMarker],
          destinations: destinationsLatLong,
          travelMode: "DRIVING",
        };

  return new Promise((resolve, reject) => {
    distanceService.getDistanceMatrix(request, (response, status) => {
      if (status === "OK") {
        resolve(response);
      } 
      else {
        reject(new Error(`Unable to retrieve distances: ${status}`));
      }
    });
  });
};

/*
	Takes an array and resizes to specified size.
*/
const chunkArray = (array, chunkSize) => {
  const chunks = [];

  for (let i = 0; i < array.length; i += chunkSize) {
    chunks.push(array.slice(i, i + chunkSize));
  }

  return chunks;
}

/*
	Load Google Map Distance Data.
*/
initialise();

The getDistanceFromOrigin() and googleMapsDistanceMatrixRequest() are the key functions that take the list of destinations, batches them into chunks of 25 and returns a tabular list of data. This code can be expanded further to be used alongside visual representation to render each destination as pins on an embedded Google Map, since we have the longitude and latitude points.

The full working demo can be found via the following link: https://jsfiddle.net/sbhomra/ns2yhfju/. To run this demo, a Google Maps API key needs to be provided, which you will be prompted to enter on load.

Before you go...

If you've found this post helpful, you can buy me a coffee. It's certainly not necessary but much appreciated!

Buy Me A Coffee

Leave A Comment

If you have any questions or suggestions, feel free to leave a comment. I do get inundated with messages regarding my posts via LinkedIn and leaving a comment below is a better place to have an open discussion. Your comment will not only help others, but also myself.