113 lines
3.5 KiB
JavaScript
113 lines
3.5 KiB
JavaScript
const { sequelize } = require('../models');
|
|
const { QueryTypes } = require('sequelize');
|
|
|
|
class LocationService {
|
|
/**
|
|
* Find users within a specified radius of coordinates
|
|
* Uses the Haversine formula to calculate great-circle distance between two points
|
|
*
|
|
* @param {number} latitude - Center point latitude
|
|
* @param {number} longitude - Center point longitude
|
|
* @param {number} radiusMiles - Search radius in miles (default: 10)
|
|
* @returns {Promise<Array>} Array of users with their distance from the center point
|
|
*/
|
|
async findUsersInRadius(latitude, longitude, radiusMiles = 10) {
|
|
if (!latitude || !longitude) {
|
|
throw new Error('Latitude and longitude are required');
|
|
}
|
|
|
|
if (radiusMiles <= 0 || radiusMiles > 100) {
|
|
throw new Error('Radius must be between 1 and 100 miles');
|
|
}
|
|
|
|
try {
|
|
// Haversine formula:
|
|
// distance = 3959 * acos(cos(radians(lat1)) * cos(radians(lat2))
|
|
// * cos(radians(lng2) - radians(lng1))
|
|
// + sin(radians(lat1)) * sin(radians(lat2)))
|
|
// Note: 3959 is Earth's radius in miles
|
|
const query = `
|
|
SELECT
|
|
u.id,
|
|
u.email,
|
|
u."firstName",
|
|
u."lastName",
|
|
ua.latitude,
|
|
ua.longitude,
|
|
(3959 * acos(
|
|
LEAST(1.0,
|
|
cos(radians(:lat)) * cos(radians(ua.latitude))
|
|
* cos(radians(ua.longitude) - radians(:lng))
|
|
+ sin(radians(:lat)) * sin(radians(ua.latitude))
|
|
)
|
|
)) AS distance
|
|
FROM "Users" u
|
|
INNER JOIN "UserAddresses" ua ON u.id = ua."userId"
|
|
WHERE ua."isPrimary" = true
|
|
AND ua.latitude IS NOT NULL
|
|
AND ua.longitude IS NOT NULL
|
|
HAVING distance < :radiusMiles
|
|
ORDER BY distance ASC
|
|
`;
|
|
|
|
const users = await sequelize.query(query, {
|
|
replacements: {
|
|
lat: parseFloat(latitude),
|
|
lng: parseFloat(longitude),
|
|
radiusMiles: parseFloat(radiusMiles)
|
|
},
|
|
type: QueryTypes.SELECT
|
|
});
|
|
|
|
return users.map(user => ({
|
|
id: user.id,
|
|
email: user.email,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
latitude: parseFloat(user.latitude),
|
|
longitude: parseFloat(user.longitude),
|
|
distance: parseFloat(user.distance).toFixed(2) // Round to 2 decimal places
|
|
}));
|
|
} catch (error) {
|
|
console.error('Error finding users in radius:', error);
|
|
throw new Error(`Failed to find users in radius: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate distance between two points using Haversine formula
|
|
*
|
|
* @param {number} lat1 - First point latitude
|
|
* @param {number} lon1 - First point longitude
|
|
* @param {number} lat2 - Second point latitude
|
|
* @param {number} lon2 - Second point longitude
|
|
* @returns {number} Distance in miles
|
|
*/
|
|
calculateDistance(lat1, lon1, lat2, lon2) {
|
|
const R = 3959; // Earth's radius in miles
|
|
const dLat = this.toRadians(lat2 - lat1);
|
|
const dLon = this.toRadians(lon2 - lon1);
|
|
|
|
const a =
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) *
|
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
const distance = R * c;
|
|
|
|
return distance;
|
|
}
|
|
|
|
/**
|
|
* Convert degrees to radians
|
|
* @param {number} degrees
|
|
* @returns {number} Radians
|
|
*/
|
|
toRadians(degrees) {
|
|
return degrees * (Math.PI / 180);
|
|
}
|
|
}
|
|
|
|
module.exports = new LocationService();
|