Skip to content

Commit

Permalink
Distance Check Approximation (#302)
Browse files Browse the repository at this point in the history
* Distance check approximation

* Adjust for 180 deg wraparound in equirectangular distance approximation
  • Loading branch information
willhuff0 authored Oct 31, 2024
1 parent 6057c53 commit df32efd
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 31 deletions.
61 changes: 49 additions & 12 deletions server/src/actions/calculateDistance.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,55 @@
const degreesToRadians = (degrees: number) => {
return degrees * Math.PI / 180;
import { Location } from "../types/Location";

const degToRad = Math.PI / 180.0;
const radians = (degrees: number) => degrees * degToRad;

// Radius of Earth in meters
const radiusOfEarth = 6371000;

// Radius squared of Earth in square meters
const radiusOfEarthSqr = radiusOfEarth * radiusOfEarth;

export const isWithinRadiusMeters = (p1: Location, p2: Location, r: number): boolean => {
// The equirectangular approximation becomes significantly inaccurate at distances about about 1.5 km
if (r > 1500.0) {
return haversineDistanceCheck(p1, p2, r);
} else {
return equirectangularDistanceCheck(p1, p2, r);
}
}

const getEquirectangularDistanceSqr = (p1: Location, p2: Location) => {
// Calculate the difference in longitude, adjusting for the 180 deg wraparound
var dLon = p2.lon - p1.lon;
if (dLon > 180.0) dLon -= 360.0;
if (dLon < -180.0) dLon += 360.0;

const dx = radians(dLon) * Math.cos(radians(p2.lat + p1.lat) / 2.0);
const dy = radians(p2.lat - p1.lat);

return radiusOfEarthSqr * (dx * dx + dy * dy);
}

// An approximate distance check which maps spherical points onto a flat surface to quickly estimate distance
const equirectangularDistanceCheck = (p1: Location, p2: Location, r: number): boolean => {
const rSqr = r * r;
const distSqr = getEquirectangularDistanceSqr(p1, p2);

return distSqr <= rSqr;
}

export const calculateDistanceInMeters = (lat1: number, lon1: number, lat2: number, lon2: number) => {
const earthRadiusKm = 6371;
// A more accurate distance check for points on the surface of a sphere
const haversineDistanceCheck = (p1: Location, p2: Location, r: number): boolean => {
const lat1 = radians(p1.lat);
const lon1 = radians(p1.lon);
const lat2 = radians(p2.lat);
const lon2 = radians(p2.lon);

const dLat = degreesToRadians(lat2-lat1);
const dLon = degreesToRadians(lon2-lon1);
const dLat = lat2 - lat1;
const dLon = lon2 - lon1;

lat1 = degreesToRadians(lat1);
lat2 = degreesToRadians(lat2);
const a = Math.pow(Math.sin(dLat / 2.0), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dLon / 2.0), 2);
const c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return earthRadiusKm * c * 1000;
return c * radiusOfEarth <= r;
}
18 changes: 4 additions & 14 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ConnectedUser } from "./types/User";
import { getAuth } from "firebase-admin/auth";
import Mailgun from "mailgun.js";
import { messagesCollection } from "./utilities/firebaseInit";
import { calculateDistanceInMeters } from "./actions/calculateDistance";
import { isWithinRadiusMeters } from "./actions/calculateDistance";
import { scheduleCron } from "./actions/deleter";
import mainRouter from "./routes/mainRouteHandler";

Expand Down Expand Up @@ -80,20 +80,10 @@ io.on("connection", async (socket: any) => {
if (change.type === "added") {
console.log("New message: ", change.doc.data());

const messageLat = change.doc.data().location.lat;
const messageLon = change.doc.data().location.lon;
const messageLocation = change.doc.data().location;
const userLocation = defaultConnectedUser.location;

const userLat = defaultConnectedUser.location.lat;
const userLon = defaultConnectedUser.location.lon;

const distance = calculateDistanceInMeters(
messageLat,
messageLon,
userLat,
userLon
);

if (distance < message_outreach_radius) {
if (isWithinRadiusMeters(userLocation, messageLocation, message_outreach_radius)) {
console.log(`Message is within ${message_outreach_radius} meters of the user ${socket.id}.`);
socket.emit("message", change.doc.data());
} else {
Expand Down
5 changes: 5 additions & 0 deletions server/src/types/Location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Location {
lat: number
lon: number
geohash: string
}
8 changes: 3 additions & 5 deletions server/src/types/User.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Location } from "./Location"

export interface UserConfig {
uid: string
isConnected: boolean
Expand All @@ -21,9 +23,5 @@ export interface UserConfig {
export interface ConnectedUser {
uid: string
socketId: string
location: {
lat: number
lon: number
geohash: string
},
location: Location
}

0 comments on commit df32efd

Please sign in to comment.