I recently completed this for a client – it shows the nearest dealer or service center based on the user’s zip code or state. The hardest thing about this didn’t prove to be difficult at all, and that was calculating the distance between 2 different sets of geo-coordinates. As with a lot of things someone had figured out how to do that years ago and everyone uses the same formula. Aside from that the major hurdle is becoming familiar with the Google Maps API but its well-documented with plenty of examples so it too proved to be rather simple.
Here’s a brief list of what this particular Google Map does:
- Loads 2 external XML files via AJAX
- Converts the XML to JSON (I prefer dealing with objects than having to traverse XML nodes)
- Displays markers in the selected state, if any.
- If there is more than one marker the view is centered around all the markers.
- Displays the closest marker to the user’s zip code.
- Due to the asynchronous nature of this app I opted to use the google.maps.event.addDomListener to tell me when Google Maps was loaded and then proceeded to load the XML which itself checks to be sure both have been loaded before proceeding further.
Centering a Cluster of Markers
The most interesting thing about this is centering the view around a cluster of markers. To do this you create a bounding area with all of the geocoordinates. Once that is done you can get the center of the polygon via the bounds.getCenter() method where “bounds” is a LatLngBounds object. The result is a LatLng object that can be applied to your map with map.setCenter() where “map” is a reference to your Google Map.
... // where arr is an array of geocoordinates function _getCenter(arr,map){ var polyCoords = []; // map polygon for (var i=0;i<arr.length;i++){ polyCoords[i] = new google.maps.LatLng(arr[i].Lat,arr[i].Lng); } // close the polygon polyCoords[polyCoords.length] = new google.maps.LatLng(arr[0].Lat,arr[0].Lng); //extend the bounds var bounds = new google.maps.LatLngBounds(); for (var i=0;i<polyCoords.length;i++){ bounds.extend(polyCoords[i]); } // get center of polygon var latlng = bounds.getCenter(); return latlng; } ...
Find The Distance Between Two Geocoordinates
I mentioned previously that this was trivial as some cartography-minded individual had already written the formula, which is:
... function _getDistance(lat1,lng1,lat2,lng2){ var R = 6371; // Radius of the earth in km var dLat = _deg2rad(lat2-lat1); var dLon = _deg2rad(lng2-lng1); var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(_deg2rad(lat1)) * Math.cos(_deg2rad(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); var d = R * c; // Distance in km return d; } ...
Note that JavaScript does not deal with degrees, it prefers radians:
... function _deg2rad(deg) { return deg * (Math.PI/180); } ...
Well, what is happening here is that I calculate the distances and simply create a new property in the current object within the array to hold the value, use array.sort() to sort them by lowest to highest distance, and then take the first index of the array as the closest geocoordinate.
... ns.findClosestToZipCode = function(obj){ dealer.view.clearMarkers(); // clear out all previous markers var type = $('#type').val(); if (type === 'dealer'){ // service locator var data = dealer.model.returnDealerData().ServiceCenter; } else if (type === 'service'){ var data = dealer.model.returnDealerData().DealerData; } for (var i=0;i<data.length;i++){ var d = _getDistance(obj.lat,obj.lng,data[i].Lat,data[i].Lng); data[i].distance = d; } data.sort(function(a,b){return (a.distance < b.distance ? -1 : 1)}); dealer.view.displayMatches([data[0]]); }; ...
The result is an array of coordinates sorted by those closest to your location.
As a point of interest Google provides a method of calculating distances via the computeDistanceBetween method which you can read more about here. The method accepts a latLng object for each of the two arguments it requires:
google.maps.geometry.spherical.computeDistanceBetween(start, end); // distance in metres
If you needed a different unit of measurement some math will get you there:
var meters = google.maps.geometry.spherical.computeDistanceBetween(start, end); var km = Math.round(stuDistances.metres / 1000 *10)/10; var miles = Math.round(stuDistances.metres / 1000 * 0.6214 *10)/10;