Finding locations using User Location and Haversine Formula
Learn how to find saved locations around the user using Haversine Formula
In this blog, we are going to learn the following...
What is the Haversine Formula
Why it would be useful
How to use MSSQL or MySQL
Quick React NodeJS/ExpressJS API Example with Sequelize
What Is The Haversine Formula
The Haversine Formula has a kind of weird but fascinating history. It deals with finding the distance between two points on a sphere. Initially, the formula written in English was by James Andrew in 1805, however, historians and mathematicians give José de Mendoza y Ríos credit who used it first in 1801.
The Haversine Formula is written as:
$$hav(θ) = sin2(θ/2)$$
Why The Haversine Formula Is Useful
The Haversine Formula is a perfect tool (to my limited knowledge of trigonometry) for finding the distance between two points on a sphere. Just like I described above. However, we can also modify it to find a list of Longitude and Latitude coordinates that are surrounding a user.
Let’s say we have a user who logins into our app that shows them all the restaurants around them in a 10-mile radius. The Haversine Formula would allow us to grab the user's Longitude and Latitude coordinates and find all restaurants around said, user.
How To Use The Haversine Formula
As I showed above, the Haversine Formula can be written and used in Trigonometry, but because we want to find all locations from a single point within a 10-mile radius (or however far) we have to modify it slightly…
Here is how we can use the Haversine Function in JavaScript via a Function that takes in the Longitude and Latitude arguments
function haversineFormula(userLat, userLong, locationLat, locationLong, reqDist) {
// Convert degrees to radians
function toRadians(degrees) {
return degrees * (Math.PI / 180);
}
// Haversine formula
const R = 3958.8; // Earth's radius in miles
const dLat = toRadians(locationLat - userLat);
const dLong = toRadians(locationLong - userLong);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRadians(userLat)) * Math.cos(toRadians(locationLat)) *
Math.sin(dLong / 2) * Math.sin(dLong / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
return distance <= reqDist;
}
// Example Usage:
console.log(haversineFormula(40.730610, -73.935242, 40.730700, -73.935242, 10)); // Very close points in New York
And here is how we can use it in a SQL Query or SQL Function, again passing in the Longitude and Latitude parameters
CREATE FUNCTION dbo.fnHaversineFormula(
@userLat FLOAT,
@userLong FLOAT,
@locationLat FLOAT,
@locationLong FLOAT,
@reqDistance INT
)
RETURNS BIT
AS
BEGIN
-- Declare result variable
DECLARE @Result BIT
DECLARE @Distance FLOAT
-- Constants for Earth's radius in miles and conversion from degrees to radians
DECLARE @R FLOAT = 3958.8
DECLARE @DegreeToRadians FLOAT = PI() / 180.0
-- Haversine formula calculations
DECLARE @dLat FLOAT = (@locationLat - @userLat) * @DegreeToRadians
DECLARE @dLong FLOAT = (@locationLong - @userLong) * @DegreeToRadians
DECLARE @a FLOAT = SIN(@dLat / 2) * SIN(@dLat / 2) +
COS(@userLat * @DegreeToRadians) * COS(@locationLat * @DegreeToRadians) *
SIN(@dLong / 2) * SIN(@dLong / 2)
DECLARE @c FLOAT = 2 * ATN2(SQRT(@a), SQRT(1 - @a))
SET @Distance = @R * @c
-- Check if distance is within requested distance
IF @Distance <= @reqDistance
SET @Result = 1
ELSE
SET @Result = 0
-- Return result
RETURN @Result
END
With both of these examples, you can feed in or loop over the execution call with the user location, potential location and the distance you want. If the potential location is within the requested distance then it will return true, else false.
Haversine Formula Example
So how can we realistically use this in an application? Currently, I use this in an upcoming app where I have a table that stores locations, that include longitude and latitude as well as some City, State, Zip and Country values as well so we don't overload the server with thousands or hundreds of thousands of requests. I take the users location and feed it to an API that calls the haversineFunction
in SQL and then returns all locations that are within the distance.
Of course, there are some other checks I have in place as well, but here is the implementation of it in a NodeJS ExpressJS and Sequelize
API call.
// Find nearest locations
router.post("/find", async (req, res) => {
const { reqDistance, Longitude, Latitude } = req.body;
try {
const locations = await Locations.findAll({
attributes: {
include: [
[
sequelize.literal(`(
3958.8 * acos (
cos ( radians(${Latitude}) )
* cos( radians( Latitude ) )
* cos( radians( Longitude ) - radians(${Longitude}) )
+ sin ( radians(${Latitude}) )
* sin( radians( Latitude ) )
)
)`),
"distance",
],
],
},
having: sequelize.literal(`distance <= reqDistance `),
order: sequelize.col("distance"),
limit: 10,
});
// If there are no listings nearby return an empty array
if (!listings) {
return res.status(200).json({ locations: [] });
}
return res.status(200).json({ locations });
} catch (err) {
console.log(err);
return res.status(500).json({ message: "Internal server error" });
}
});
This is a very crude and basic example of an API call but I believe I convey the logic and intent well enough. We make a POST request to the API that contains reqDistance, Longitude, Latitude
and then it executes the Haversine Formula checking each location in the locations
table and verifying that it is within the distance we wanted. This isn't calling the SQL function I showed earlier as I wanted to show how we could do without it, if you would (and you should) use it as a function, all you need to do is just call it and return the response.
That's about it. Just like with all entries in my B.L.O.G. series, I post this to not only share with everyone else who might want to use it but also as a backup and a way for myself to step through solutions I've come up with for different types of problems.
If you enjoyed it, let me know on any of my socials or even just a comment below! Thanks!