{"id":221,"date":"2019-01-28T12:40:20","date_gmt":"2019-01-28T04:40:20","guid":{"rendered":"https:\/\/www.intelliwolf.com\/?p=221"},"modified":"2021-03-09T12:28:43","modified_gmt":"2021-03-09T04:28:43","slug":"find-nearest-location-from-array-of-coordinates-php","status":"publish","type":"post","link":"https:\/\/wordpress-757293-2559390.cloudwaysapps.com\/find-nearest-location-from-array-of-coordinates-php\/","title":{"rendered":"Find The Nearest Location From An Array of Coordinates In PHP"},"content":{"rendered":"\n
As I discussed in previous tutorials, we have the visitor's latitude and longitude<\/a>, and an array of locations<\/a>. Now we can combine the two to figure out which location in that array is closest to our starting coordinates.<\/p>\n\n\n\n What is the fastest way to find the nearest location out of an array of coordinates?<\/strong><\/p>\n\n\n\n Phew that list sounds a bit daunting! Let's see the code first:<\/p>\n\n\n\n Not so daunting after all. Let's explore what's happening in the code.<\/p>\n\n\n\n In my earlier post on importing a CSV file into an array<\/a>, I covered a quick way to import a lot of data to an array.<\/p>\n\n\n\n I've written the code as above for this tutorial simply for clarity. Because there are so many suburbs in the $locations<\/em> array, I actually put it in a file called locations.php<\/em> and called it using:<\/p>\n\n\n\n That does the same thing. It just makes it easier to manage.<\/p>\n\n\n\n The guts of this code is turning $locations<\/em> into an array of distances from the base coordinates. I've called that array $distances<\/em>.<\/p>\n\n\n\n The key<\/em> of each element in $distances<\/em> will be the same as the key for that same element in $locations<\/em>. This is essential<\/strong> for this code to function later.<\/p>\n\n\n\n We start with our initial location:<\/p>\n\n\n\n You might pull this in dynamically using $_GET<\/em>.<\/p>\n\n\n\n You might also use it on the fly with an AJAX<\/em> call from jQuery based on the visitor's location. I wrote about how to get that here<\/a>.<\/p>\n\n\n\n Next comes the algorithm for calculating the distance from the base location.<\/p>\n\n\n\n I've tested this quite extensively in preparation for this tutorial. Pythagorean Theorem<\/strong> was the fastest and best suited to this project.<\/p>\n\n\n\n See the end of this tutorial for how the tests performed and the code for the other formulae.<\/p>\n\n\n\n The code for looping over each $locations<\/em> location and calculating how far it is from $base_location<\/em> is:<\/p>\n\n\n\n You could make it smaller by putting $a<\/em> and $b<\/em> into the one line, but I broke it apart to make it clear how the algorithm works.<\/p>\n\n\n\n If you want to use a different algorithm, swap out the first three lines inside of the foreach loop.<\/p><\/blockquote>\n\n\n\n Normally I'd use more descriptive variables, but years of schooling has me automatically remembering that<\/p>\n\n\n\n a2<\/sup> + b2<\/sup> = c2<\/sup><\/p>\n\n\n\n I'm sure you probably do too.<\/p>\n\n\n\n In case you weren't familiar with the format, $a**2<\/em> is PHP for a<\/em>2<\/em><\/sup>. This change came in with PHP 5.6, which if you're reading this, you absolutely should be using that or later.<\/p>\n\n\n\n If you're stuck using an earlier version of PHP, swap $a**2<\/em> for<\/p>\n\n\n\n After this foreach<\/em> loop has finished running, we now have an array of distances from the base location in $distances<\/em>. It will look something like this:<\/p>\n\n\n\n Note:<\/strong> these aren't actual distances in kilometres or miles. These are relative distances. They also don't take into account the curvature of the Earth. If you need actual distances, you're best to use the Haversine Formula discussed at the bottom<\/p><\/blockquote>\n\n\n\n Sorting the $distances<\/em> array from smallest to largest is as simple as:<\/p>\n\n\n\n Now the array looks like this:<\/p>\n\n\n\n If we were to look up $locations[10712]<\/em>, we'd find it was actually the closest location to our initial coordinates. So let's do that.<\/p>\n\n\n\n To pull the key of the first element of $distances<\/em>, we'd use the code:<\/p>\n\n\n\n So to use that key to find the corresponding location in $locations<\/em>, we can simply use:<\/p>\n\n\n\n In our example, our base location was -31.959138, 115.858072<\/em>, which if you check on a map is the Belltower in Perth<\/a>.<\/p>\n\n\n\n In my $locations<\/em> array, $locations[10712]<\/em> is Perth, or more specifically:<\/p>\n\n\n\n So to display the name, I just use $closest['name']<\/em>.<\/p>\n\n\n\n I hope you found this tutorial on how to find the nearest location from an array of coordinates useful. Read on for a discussion of the algorithms.<\/p>\n\n\n\n The main algorithms I considered were:<\/p>\n\n\n\n That list is sorted from fastest and least accurate to slowest and most accurate.<\/p>\n\n\n\n My tests, averaged over 10 runs each, on a list of 16,315 locations, showed the speed of run time were as follows:<\/p>\n\n\n\n The 16,000 locations covers all of the suburbs of Australia and New Zealand. There are over 40,000 such locations in USA. The speed will blow out quite a bit on larger data sets.<\/p>\n\n\n\n This code replaces the calculations inside the foreach<\/em> loop from the initial example. Everything else can stay the same.<\/p>\n\n\n\n The Pythagorean Theorem is a<\/em>2<\/em><\/sup> + b<\/em>2<\/em><\/sup> = c<\/em>2<\/em><\/sup> <\/em>. It uses right angle calculations to determine the distance, which in our case would be c<\/em>.<\/p>\n\n\n\n Applied to our location calculations, it looks like this:<\/p>\n\n\n\n You can read more about it here<\/a>.<\/p>\n\n\n\n The Pythagorean Theorem will give quite inaccurate results once you go more than short distances. The problem is that is calculates based on a straight line, not taking into account the curvature of the Earth.<\/p>\n\n\n\n An attempt to improve upon that is called the Spherical Earth Projected To A Plane Formula<\/em>. <\/p>\n\n\n\n Essentially it uses some trigonometry to \"flatten\" the Earth, then calculate distances on that flattened Earth.<\/p>\n\n\n\n Applied to our location calculations, it looks like this:<\/p>\n\n\n\n If you want the actual distances, you can add these lines:<\/p>\n\n\n\n Choose between $earth_radius_km<\/em> and $earth_radius_mi<\/em>, depending on what units you needed.<\/p>\n\n\n\n Then make sure that you change<\/p>\n\n\n\n to<\/p>\n\n\n\n$locations = array (\n array (\n \"postcode\" => \"2850\",\n \"name\" => \"Aarons Pass\",\n \"state_code\" => \"NSW\",\n \"lat\" => \"-32.86328\",\n \"lng\" => \"149.80375\"\n ),\n array (\n \"postcode\" => \"6280\",\n \"name\" => \"Abba River\",\n \"state_code\" => \"WA\",\n \"lat\" => \"-33.68488\",\n \"lng\" => \"115.46334\"\n ),\n array (\n \"postcode\" => \"6280\",\n \"name\" => \"Abbey\",\n \"state_code\" => \"WA\",\n \"lat\" => \"-33.66077\",\n \"lng\" => \"115.25863\"\n )\n ...\n);\n\n$base_location = array(\n 'lat' => \"-31.959138\",\n 'lng' => \"115.858072\"\n);\n\n$distances = array();\n\nforeach ($locations as $key => $location)\n{\n $a = $base_location['lat'] - $location['lat'];\n $b = $base_location['lng'] - $location['lng'];\n $distance = sqrt(($a**2) + ($b**2));\n $distances[$key] = $distance;\n}\n\nasort($distances);\n\n$closest = $locations[key($distances)];\n\necho \"Closest foreach suburb is: \" . $closest['name'];<\/code><\/pre>\n\n\n\n
Import the array of coordinates<\/h2>\n\n\n\n
require('locations.php');<\/code><\/pre>\n\n\n\n
Based on the array of coordinates, create another array of the respective distances from the initial location<\/h2>\n\n\n\n
$base_location = array(\n 'lat' => \"-31.959138\",\n 'lng' => \"115.858072\"\n);<\/code><\/pre>\n\n\n\n
$distances = array();\n\nforeach ($locations as $key => $location)\n{\n $a = $base_location['lat'] - $location['lat'];\n $b = $base_location['lng'] - $location['lng'];\n $distance = sqrt(($a**2) + ($b**2));\n $distances[$key] = $distance;\n}<\/code><\/pre>\n\n\n\n
pow($a, 2)<\/code><\/pre>\n\n\n\n
Array\n(\n [0] => 33.957716761229\n [1] => 1.7703103689433\n [2] => 1.8041292012459\n [3] => 31.298059163015\n [4] => 36.246295056854\n [5] => 35.0622192721\n)<\/code><\/pre>\n\n\n\n
Sort the second array from smallest to largest<\/h2>\n\n\n\n
asort($distances);<\/code><\/pre>\n\n\n\n
Array\n(\n [10712] => 0.0037737207103849\n [10026] => 0.012187097603613\n [4258] => 0.013816152431116\n [14330] => 0.016617217215894\n [12192] => 0.022168618991717\n [6076] => 0.02274286806891\n)<\/code><\/pre>\n\n\n\n
Use the key of the first array element from the second array to get the details of the closest location from the first array<\/h2>\n\n\n\n
$key = key($distances);<\/code><\/pre>\n\n\n\n
$closest = $locations[key($distances)];<\/code><\/pre>\n\n\n\n
array (\n \"postcode\" => \"6000\",\n \"name\" => \"Perth\",\n \"state_code\" => \"WA\",\n \"lat\" => \"-31.9554\",\n \"lng\" => \"115.85859\"\n)<\/code><\/pre>\n\n\n\n
How do the distance calculation algorithms compare?<\/h2>\n\n\n\n
What is the code for calculating distances?<\/h2>\n\n\n\n
Pythagorean Theorem in PHP<\/h3>\n\n\n\n
$a = $base_location['lat'] - $location['lat'];\n$b = $base_location['lng'] - $location['lng'];\n$distance = sqrt(($a**2) + ($b**2));<\/code><\/pre>\n\n\n\n
Spherical Earth Projected To A Plane in PHP<\/h3>\n\n\n\n
$delta_lat = $base_location['lat'] - $location['lat'];\n$delta_lng = $base_location['lng'] - $location['lng'];\n$mean_lat = ($location['lat'] + $base_location['lat']) \/ 2;\n$distance = ($delta_lat**2) + ((cos($mean_lat) * $delta_lng)**2);<\/code><\/pre>\n\n\n\n
$earth_radius_km = 6371.009;\n$earth_radius_mi = 3958.761;\n$actual_distance = $earth_radius_km * sqrt($distance);<\/code><\/pre>\n\n\n\n
$distances[$key] = $distance;<\/code><\/pre>\n\n\n\n
$distances[$key] = $actual_distance;<\/code><\/pre>\n\n\n\n