Leng D
Leng D

Reputation: 111

How to find nearest location using latitude and longitude from json data

I'm trying to make a website that asks for the user's location, then finds the closest location (100m radius) from their position using GeoLocation and displays the result in HTML.

What I have tried:

$.getJSON("places.json", function (data) {
        for (var i = 0; i < data.length; i++) {
            if ((data[i].lat - poslat) > 0.00200 || (data[i].lng - poslng) > 0.00200) {
            return data[i];
        }

        html += '<p>' + data[i].location + ' - ' + data[i].code + '</p>';
        $('#nearbystops').append(html);
    }
});

places.json

[
{
"code": "0001",
"lat": "1.28210155945393",
"lng": "103.81722480263163",
"location": "Stop 1"
},
{
"code": "0003",
"lat": "1.2777380589964",
"lng": "103.83749709165197",
"location": "Stop 2"
},
{
"code": "0002",
"lat": "1.27832046633393",
"lng": "103.83762574759974",
"location": "Stop 3"
}
]

Thank you in advance! :)

Upvotes: 11

Views: 28813

Answers (4)

Leonardo Cavalcante
Leonardo Cavalcante

Reputation: 1303

there are a few algorithms you can use:

Haversine Formula Wikipedia

  • Calculates the great-circle distance between two points on a sphere, suitable for small to moderate distances where high precision isn't critical.

Vincenty’s Formula Wikipedia

  • Iterative method that accounts for the Earth's ellipsoidal shape, offering higher precision for long distances compared to the Haversine formula.

Great-circle Distance with Equirectangular Approximation Wikipedia

  • A faster but less accurate method that assumes a flat Earth, useful for quick calculations where precision is not crucial.

Geodesic Calculations Wikipedia

  • Uses advanced models like WGS84 to compute distances with high accuracy, ideal for applications needing precise geodesic measurements.

Spherical Law of Cosines Wikipedia

  • Similar to the Haversine formula but may offer slightly different results; good for spherical distance calculations.

Run all scripts below, and pay attention to how they compare to each other, short distance (Anna Bay to Sydney) and long distance (Anna Bay to São Paulo - real distance is 13,435 km)

Haversine algorithm:

interface City {
  city: string;
  lat: number;
  lng: number;
}

const cityCoordinates: City[] = [
  { city: 'Sydney', lat: -33.8688, lng: 151.2093 },
  { city: 'Melbourne', lat: -37.8136, lng: 144.9631 },
  { city: 'Wollongong', lat: -34.4278, lng: 150.8931 },
  { city: 'Canberra', lat: -35.2802, lng: 149.1310 },
  { city: 'Brisbane', lat: -27.4698, lng: 153.0251 },
  { city: 'Perth', lat: -31.9505, lng: 115.8605 },
  { city: 'Sao Paulo', lat: -23.5558, lng: 46.6396 }
];

const userLocation: { city: string; lat: number; lng: number } = {
  city: 'Anna Bay',
  lat: -32.7790,
  lng: 152.0858
};

// Function to calculate the distance between two coordinates using Haversine formula
const calculateDistance = (lat1: number, lng1: number, lat2: number, lng2: number): number => {
  const toRadians = (degree: number) => degree * (Math.PI / 180);
  const R = 6371; // Radius of the Earth in km

  const dLat = toRadians(lat2 - lat1);
  const dLng = toRadians(lng2 - lng1);
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
    Math.sin(dLng / 2) * Math.sin(dLng / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c; // Distance in km
};

// Find the closest city to the user's location
const findClosestCity = (userLat: number, userLng: number, cities: City[]): City => {
  let closest: City & { distance: number } = { city: '', lat: 0, lng: 0, distance: Infinity };

  cities.forEach(city => {
    const distance = calculateDistance(userLat, userLng, city.lat, city.lng);
    console.log(`Distance from ${userLocation.city} to ${city.city}: ${distance.toFixed(2)} km`);
    if (distance < closest.distance) {
      closest = { ...city, distance };
    }
  });

  return closest;
};

const closestCity = findClosestCity(userLocation.lat, userLocation.lng, cityCoordinates);
console.log(closestCity)

Equirectangular Approximation algorithm

// Equirectangular Approximation algorithm
interface City {
  city: string;
  lat: number;
  lng: number;
}

const cityCoordinates: City[] = [
  { city: 'Sydney', lat: -33.8688, lng: 151.2093 },
  { city: 'Melbourne', lat: -37.8136, lng: 144.9631 },
  { city: 'Wollongong', lat: -34.4278, lng: 150.8931 },
  { city: 'Canberra', lat: -35.2802, lng: 149.1310 },
  { city: 'Brisbane', lat: -27.4698, lng: 153.0251 },
  { city: 'Perth', lat: -31.9505, lng: 115.8605 },
  { city: 'Sao Paulo', lat: -23.5558, lng: 46.6396 }
];

const userLocation: { city: string; lat: number; lng: number } = {
  city: 'Anna Bay',
  lat: -32.7790,
  lng: 152.0858
};

// Equirectangular approximation for distance calculation
const equirectangularDistance = (lat1: number, lng1: number, lat2: number, lng2: number): number => {
  const R = 6371; // Radius of the Earth in km
  const dLat = (lat2 - lat1) * (Math.PI / 180);
  const dLng = (lng2 - lng1) * (Math.PI / 180);
  const a = Math.cos(lat1 * (Math.PI / 180)) * Math.sin(dLng);
  const b = Math.sin(dLat);
  return Math.sqrt(a * a + b * b) * R;
};

// Find the closest city to the user's location
const findClosestCity = (userLat: number, userLng: number, cities: City[]): City & { distance: number } => {
  let closest: City & { distance: number } = { city: '', lat: 0, lng: 0, distance: Infinity };

  cities.forEach(city => {
const distance = equirectangularDistance(userLat, userLng, city.lat, city.lng);
console.log(`Distance from ${userLocation.city} to ${city.city}: ${distance.toFixed(2)} km`);
if (distance < closest.distance) {
  closest = { ...city, distance };
}
  });

  return closest;
};

const closestCity = findClosestCity(userLocation.lat, userLocation.lng, cityCoordinates);

console.log('Closest City:', closestCity);

Vincenty’s algorithm (this code is correct but doesn't run well here, copy and paste somewhere else)

interface City {
  city: string;
  lat: number;
  lng: number;
}

const cityCoordinates: City[] = [
  { city: 'Sydney', lat: -33.8688, lng: 151.2093 },
  { city: 'Melbourne', lat: -37.8136, lng: 144.9631 },
  { city: 'Wollongong', lat: -34.4278, lng: 150.8931 },
  { city: 'Canberra', lat: -35.2802, lng: 149.1310 },
  { city: 'Brisbane', lat: -27.4698, lng: 153.0251 },
  { city: 'Perth', lat: -31.9505, lng: 115.8605 },
  { city: 'Sao Paulo', lat: -23.5558, lng: 46.6396 }
];

const userLocation: { city: string; lat: number; lng: number } = {
  city: 'Anna Bay',
  lat: -32.7790,
  lng: 152.0858
};

// Vincenty formula for distance calculation
const vincentyDistance = (lat1: number, lng1: number, lat2: number, lng2: number): number => {
  const a = 6378137; // Major radius (meters)
  const f = 1 / 298.257223563; // Flattening
  const b = (1 - f) * a; // Minor radius

  const toRadians = (degree: number) => degree * (Math.PI / 180);

  const phi1 = toRadians(lat1);
  const phi2 = toRadians(lat2);
  const L = toRadians(lng2 - lng1);

  const U1 = Math.atan((1 - f) * Math.tan(phi1));
  const U2 = Math.atan((1 - f) * Math.tan(phi2));
  const sinU1 = Math.sin(U1);
  const cosU1 = Math.cos(U1);
  const sinU2 = Math.sin(U2);
  const cosU2 = Math.cos(U2);

  let lambda = L;
  let lambdaPrev: number;
  let iterationLimit = 100;
  let sinSigma: number, cosSigma: number, sigma: number;
  let cos2SigmaM: number, sinAlpha: number, cosSqAlpha: number;
  
  do {
lambdaPrev = lambda;
const sinLambda = Math.sin(lambda);
const cosLambda = Math.cos(lambda);
sinSigma = Math.sqrt((cosU2 * sinLambda) ** 2 + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ** 2);
cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
sigma = Math.atan2(sinSigma, cosSigma);
sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
cosSqAlpha = 1 - sinAlpha ** 2;
cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
const C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2)));
  } while (Math.abs(lambda - lambdaPrev) > 1e-12 && --iterationLimit > 0);

  const uSq = cosSqAlpha * (a ** 2 - b ** 2) / (b ** 2);
  const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
  const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));

  const deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ** 2) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ** 2) * (-3 + 4 * cos2SigmaM ** 2)));
  const s = b * A * (sigma - deltaSigma);

  return s / 1000; // Convert meters to kilometers
};

// Find the closest city to the user's location
const findClosestCity = (userLat: number, userLng: number, cities: City[]): City & { distance: number } => {
  let closest: City & { distance: number } = { city: '', lat: 0, lng: 0, distance: Infinity };

  cities.forEach(city => {
const distance = vincentyDistance(userLat, userLng, city.lat, city.lng);
console.log(`Distance from ${userLocation.city} to ${city.city}: ${distance.toFixed(2)} km`); 
if (distance < closest.distance) {
  closest = { ...city, distance };
}
  });

  return closest;
};

const closestCity = findClosestCity(userLocation.lat, userLocation.lng, cityCoordinates);

console.log('Closest City:', closestCity);

Geodesy

interface City {
  city: string;
  lat: number;
  lng: number;
}

const cityCoordinates: City[] = [
  { city: 'Sydney', lat: -33.8688, lng: 151.2093 },
  { city: 'Melbourne', lat: -37.8136, lng: 144.9631 },
  { city: 'Wollongong', lat: -34.4278, lng: 150.8931 },
  { city: 'Canberra', lat: -35.2802, lng: 149.1310 },
  { city: 'Brisbane', lat: -27.4698, lng: 153.0251 },
  { city: 'Perth', lat: -31.9505, lng: 115.8605 },
  {city: 'Sao Paulo', lat: -23.5558, lng: 46.6396}
];

const userLocation: { city: string; lat: number; lng: number } = {
  city: 'Anna Bay',
  lat: -32.7790,
  lng: 152.0858
};

// Function to calculate the geodesic distance using WGS84 ellipsoid
const geodesicDistance = (lat1: number, lng1: number, lat2: number, lng2: number): number => {
  const a = 6378137; // WGS84 major axis (meters)
  const f = 1 / 298.257223563; // Flattening
  const b = (1 - f) * a; // WGS84 minor axis

  const toRadians = (degree: number) => degree * (Math.PI / 180);

  const phi1 = toRadians(lat1);
  const phi2 = toRadians(lat2);
  const lambda1 = toRadians(lng1);
  const lambda2 = toRadians(lng2);

  const L = lambda2 - lambda1;

  const U1 = Math.atan((1 - f) * Math.tan(phi1));
  const U2 = Math.atan((1 - f) * Math.tan(phi2));

  let lambda = L;
  let lambdaPrev;
  let iterationLimit = 100;
  let sinSigma, cosSigma, sigma, sinAlpha, cosSqAlpha, cos2SigmaM, C;

  do {
lambdaPrev = lambda;
const sinLambda = Math.sin(lambda);
const cosLambda = Math.cos(lambda);
sinSigma = Math.sqrt(
  (Math.cos(U2) * sinLambda) ** 2 +
  (Math.cos(U1) * Math.sin(U2) - Math.sin(U1) * Math.cos(U2) * cosLambda) ** 2
);
cosSigma = Math.sin(U1) * Math.sin(U2) + Math.cos(U1) * Math.cos(U2) * cosLambda;
sigma = Math.atan2(sinSigma, cosSigma);
sinAlpha = Math.cos(U1) * Math.cos(U2) * sinLambda / sinSigma;
cosSqAlpha = 1 - sinAlpha ** 2;
cos2SigmaM = cosSigma - 2 * Math.sin(U1) * Math.sin(U2) / cosSqAlpha;
C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
lambda = L + (1 - C) * f * sinAlpha * (
  sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2))
);
  } while (Math.abs(lambda - lambdaPrev) > 1e-12 && --iterationLimit > 0);

  const uSq = cosSqAlpha * (a ** 2 - b ** 2) / (b ** 2);
  const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
  const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));

  const deltaSigma = B * sinSigma * (
cos2SigmaM + B / 4 * (
  cosSigma * (-1 + 2 * cos2SigmaM ** 2) - 
  B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ** 2) * (-3 + 4 * cos2SigmaM ** 2)
)
  );
  const s = b * A * (sigma - deltaSigma);

  return s / 1000; // Convert meters to kilometers
};

// Find the closest city to the user's location
const findClosestCity = (userLat: number, userLng: number, cities: City[]): City & { distance: number } => {
  let closest: City & { distance: number } = { city: '', lat: 0, lng: 0, distance: Infinity };

  cities.forEach(city => {
const distance = geodesicDistance(userLat, userLng, city.lat, city.lng);
console.log(`Distance from ${userLocation.city} to ${city.city}: ${distance.toFixed(2)} km\n`);
if (distance < closest.distance) {
  closest = { ...city, distance };
}
  });

  return closest;
};

const closestCity = findClosestCity(userLocation.lat, userLocation.lng, cityCoordinates);

console.log('Closest City:', closestCity);

Spherical Law of Cosines

interface City {
  city: string;
  lat: number;
  lng: number;
}

const cityCoordinates: City[] = [
  { city: 'Sydney', lat: -33.8688, lng: 151.2093 },
  { city: 'Melbourne', lat: -37.8136, lng: 144.9631 },
  { city: 'Wollongong', lat: -34.4278, lng: 150.8931 },
  { city: 'Canberra', lat: -35.2802, lng: 149.1310 },
  { city: 'Brisbane', lat: -27.4698, lng: 153.0251 },
  { city: 'Perth', lat: -31.9505, lng: 115.8605 },
  {city: 'Sao Paulo', lat: -23.5558, lng: 46.6396}
];

const userLocation: { city: string; lat: number; lng: number } = {
  city: 'Anna Bay',
  lat: -32.7790,
  lng: 152.0858
};

// Spherical Law of Cosines for distance calculation
const sphericalLawOfCosinesDistance = (lat1: number, lng1: number, lat2: number, lng2: number): number => {
  const toRadians = (degree: number) => degree * (Math.PI / 180);
  const R = 6371; // Radius of the Earth in km

  const lat1Rad = toRadians(lat1);
  const lat2Rad = toRadians(lat2);
  const dLngRad = toRadians(lng2 - lng1);

  const distance = Math.acos(
Math.sin(lat1Rad) * Math.sin(lat2Rad) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLngRad)
  ) * R;

  return distance;
};

// Find the closest city to the user's location
const findClosestCity = (userLat: number, userLng: number, cities: City[]): City & { distance: number } => {
  let closest: City & { distance: number } = { city: '', lat: 0, lng: 0, distance: Infinity };

  cities.forEach(city => {
const distance = sphericalLawOfCosinesDistance(userLat, userLng, city.lat, city.lng);
console.log(`Distance from ${userLocation.city} to ${city.city}: ${distance.toFixed(2)} km`);
if (distance < closest.distance) {
  closest = { ...city, distance };
}
  });

  return closest;
};

const closestCity = findClosestCity(userLocation.lat, userLocation.lng, cityCoordinates);

console.log('Closest City:', closestCity);

Upvotes: 0

Reinstate Monica Cellio
Reinstate Monica Cellio

Reputation: 26143

To calculate distance between two co-ordinates, you can't just subtract the values. That's fine but it gives you the co-ordinates that are within a square. This may be suitable but mostly people do tend to want to search locations by radius. This function will do that...

function distance(lat1, lon1, lat2, lon2, unit) {
    var radlat1 = Math.PI * lat1/180
    var radlat2 = Math.PI * lat2/180
    var theta = lon1-lon2
    var radtheta = Math.PI * theta/180
    var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
    if (dist > 1) {
        dist = 1;
    }
    dist = Math.acos(dist)
    dist = dist * 180/Math.PI
    dist = dist * 60 * 1.1515
    if (unit=="K") { dist = dist * 1.609344 }
    if (unit=="N") { dist = dist * 0.8684 }
    return dist
}

It's a common piece of code which I copied from here...

https://www.geodatasource.com/developers/javascript

And here it is, used in your example...

function distance(lat1, lon1, lat2, lon2, unit) {
  var radlat1 = Math.PI * lat1/180
  var radlat2 = Math.PI * lat2/180
  var theta = lon1-lon2
  var radtheta = Math.PI * theta/180
  var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
  if (dist > 1) {
    dist = 1;
  }
  dist = Math.acos(dist)
  dist = dist * 180/Math.PI
  dist = dist * 60 * 1.1515
  if (unit=="K") { dist = dist * 1.609344 }
  if (unit=="N") { dist = dist * 0.8684 }
  return dist
}

var data = [{
    "code": "0001",
    "lat": "1.28210155945393",
    "lng": "103.81722480263163",
    "location": "Stop 1"
}, {
    "code": "0003",
    "lat": "1.2777380589964",
    "lng": "103.83749709165197",
    "location": "Stop 2"
}, {
    "code": "0002",
    "lat": "1.27832046633393",
    "lng": "103.83762574759974",
    "location": "Stop 3"
}];

var html = "";
var poslat = 1.28210155945393;
var poslng = 103.81722480263163;

for (var i = 0; i < data.length; i++) {
    // if this location is within 0.1KM of the user, add it to the list
    if (distance(poslat, poslng, data[i].lat, data[i].lng, "K") <= 0.1) {
        html += '<p>' + data[i].location + ' - ' + data[i].code + '</p>';
    }
}

$('#nearbystops').append(html);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="nearbystops"></div>

Upvotes: 29

Shiv Kumar Baghel
Shiv Kumar Baghel

Reputation: 2480

get current user's location using HTML5 geolocation and find nearest location within 100 meters.

include and use below google maps libs

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry"> </script>


Snippet

//calculates distance between two points in km's
function calcDistance(p1, p2) {
  return (google.maps.geometry.spherical.computeDistanceBetween(p1, p2) / 1000).toFixed(2);
}

function getPosition(position) {
  var userPosition = {
    lat: position.coords.latitude,
    lng: position.coords.longitude
  };

  $.getJSON("places.json", function(data) {
    for (var i = 0; i < data.length; i++) {

      var p1 = new google.maps.LatLng(userPosition.lat, userPosition.lng);
      var p2 = new google.maps.LatLng(data[i].lat, data[i].lng);

      var distance = calcDistance(p1, p2) * 1000;

      if ((distance * 1000) <= 100) {
        html += '<p>' + data[i].location + ' - ' + data[i].code + '</p>';
        $('#nearbystops').append(html);
      }

    }

  })
}

// get user's current latitude & longitude
function getLocation() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(getPosition);
  } else {
    alert("Geolocation is not supported by this browser.");
  }
}

getLocation();

Upvotes: 4

madeinQuant
madeinQuant

Reputation: 1813

To calculate the distance between two points (latitude, longitude), implemented a function of haversine formula in typescript.

//There are 6200 points in the JSON file
import data from './json/test.json';

let radians = function (degree: number) {

  // degrees to radians
  let rad: number = degree * Math.PI / 180;

  return rad;
}

const haversine = (lat1: number, lon1: number, lat2: number, lon2: number) => {

  let dlat, dlon, a, c, R: number;

  R = 6372.8; // km
  dlat = radians(lat2 - lat1);
  dlon = radians(lon2 - lon1);
  lat1 = radians(lat1);
  lat2 = radians(lat2);
  a = Math.sin(dlat / 2) * Math.sin(dlat / 2) + Math.sin(dlon / 2) * Math.sin(dlon / 2) * Math.cos(lat1) * Math.cos(lat2)
  c = 2 * Math.asin(Math.sqrt(a));
  return R * c;
}

let test = function () {
  const keys = Object.keys(data);

  let count: number = keys.length;
  for (var _i = 0; _i < count; _i++) {
    var _dummy: number = haversine(
      36.12, -86.67, data[_i].position.lat, data[_i].position.lng);
  }

}

Upvotes: 3

Related Questions