Full disclosure, I did not write the following distance()
PHP function. It’s based on this answer on Stack Overflow.
The last few sites I’ve worked on have needed to return the closest location to the users IP based on an array of latitudes and longitudes.
In order to do this we need to do two things. Find the latitude and longitude of a given IP address, and then do a comparison against other latitudes and longitudes.
To find the latitude and longitude of an IP address we’ll use the free geolocation lookup API, FreeGeoIP.
Start by creating a function, geo_ip()
:
function geo_ip() { $ip = !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; $url = "http://freegeoip.net/json/{$ip}"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); $data = curl_exec($ch); curl_close($ch); if ($data) { return json_decode($data); } else { return false; } }
In the geo_ip()
function, we start by creating a variable for the current IP address using a ternary operator.
We then set the $url variable to query the API using the IP we just set. You can have it return a CSV or XML instead of JSON if you prefer.
After that, cURL the API and if it returns data, return that from the function. Otherwise return false.
Next create the function closeset_location()
:
function closest_location($locations) { if($locations && is_array($locations)) { $ip_data = geo_ip(); if ($ip_data) { $coords[] = $ip_data->latitude; $coords[] = $ip_data->longitude; function distance($a, $b) { list($lat1, $lon1) = $a; list($lat2, $lon2) = $b; $theta = $lon1 - $lon2; $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); $dist = acos($dist); $dist = rad2deg($dist); $miles = $dist * 60 * 1.1515; return $miles; } $distances = array_map(function($item) use ($coords) { $a[] = $item['latitude']; $a[] = $item['longitude']; return distance($a, $coords); }, $locations); asort($distances); return $locations[key($distances)]; } } }
closest_location()
requires an array that contains elements for both latitude and longitude. We then set a variable using the geo_ip()
function we created earlier. If it returns a value, we set up a $coords
array for the latitude and longitude returned from FreeGeoIP.
The distance()
function requires 2 sets of latitudes and longitudes. It then compares the distance between the two coordinates on a sphere.
$distances
uses the distance()
function we just created to compare the latitudes and longitudes from the $locations
array passed into the function. Then sort the array to get the closest location and return it!
Now, say you have a variable called $stores
that is an array of latitudes and longitudes if you call closest_location($stores)
it will return the closest location in the array!
This is great, I’m integrating this but I’m getting an undefined index error on the ‘latitude’ and ‘longitude’.
My $locations array looks like this:
$locations = [
‘Bend’ => [‘44.074’, ‘-121.258’],
];
Any ideas?
Hey Keith, I’m glad this is useful to you! I believe the issue is that you need to define both of the coordinates. So your array should look something like:
It’s built that way so you can store more data in your ‘Bend’ array without any ambiguity as to which coordinate is latitude and which is longitude.