Find record with closest latitude / longitude from stringify'ed data in localstorage

Hi,

Have a stored some json data for locations in local storage as I dont want to load the list every time. The data looks like this.

If I have the users latitude & longitude how would I go about getting the ID of record with closest latitude & longitude values?

Do I need to loop through them all comparing how different each one is and store the ID of the one that is ‘Least’ different? Or is there a function that could perform a search of this type?

I really dont know where to start with this so any pointers would be appreciated.

{"Locations":{"Location":[{"id":"3066","latitude":"57.6494","longitude":"-3.5606","name":"Kinloss"},{"id":"3080","latitude":"57.077","longitude":"-2.836","name":"Aboyne"},{"id":"3091","latitude":"57.206","longitude":"-2.202","name":"Aberdeen Dyce"},{"id":"3134","latitude":"55.907","longitude":"-4.533","name":"Glasgow/Bishopton"},{"id":"3136","latitude":"55.515","longitude":"-4.585","name":"Prestwick Rnas"},{"id":"3144","latitude":"56.326","longitude":"-3.729","name":"Strathallan"},

Some simple vector math where you get the hypotenuse (with the vectorDistance function), can result in a solution for this.

function closestLocation(targetLocation, locationData) {
    function vectorDistance(dx, dy) {
        return Math.sqrt(dx * dx + dy * dy);
    }

    function locationDistance(location1, location2) {
        var dx = location1.latitude - location2.latitude,
            dy = location1.longitude - location2.longitude;

        return vectorDistance(dx, dy);
    }

    return locationData.reduce(function(prev, curr) {
        var prevDistance = locationDistance(targetLocation , prev),
            currDistance = locationDistance(targetLocation , curr);
        return (prevDistance < currDistance) ? prev : curr;
    });
}

var data = {
    "Locations": {
        "Location": [
            {
            "id": "3066",
            "latitude": "57.6494",
            "longitude": "-3.5606",
            "name": "Kinloss"},
        {
            "id": "3080",
            "latitude": "57.077",
            "longitude": "-2.836",
            "name": "Aboyne"},
        {
            "id": "3091",
            "latitude": "57.206",
            "longitude": "-2.202",
            "name": "Aberdeen Dyce"},
        {
            "id": "3134",
            "latitude": "55.907",
            "longitude": "-4.533",
            "name": "Glasgow/Bishopton"},
        {
            "id": "3136",
            "latitude": "55.515",
            "longitude": "-4.585",
            "name": "Prestwick Rnas"},
        {
            "id": "3144",
            "latitude": "56.326",
            "longitude": "-3.729",
            "name": "Strathallan"}
        ]
    }
},
    targetLocation = {
        latitude: 56,
        longitude: -5
    },
    closest = closestLocation(targetLocation, data.Locations.Location);
// closest is now the location that is closest to the target location

If you need to support older web browsers that don’t know how to do the Array.reduce() method, you can get polyfills from http://www.calormen.com/polyfill/polyfill.js or you can just use the part of that code directly related to providing the reduce functionality itself:


// ES5 15.4.4.21 Array.prototype.reduce ( callbackfn [ , initialValue ] )
// From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce
if (!Array.prototype.reduce) {
  Array.prototype.reduce = function (fun /*, initialValue */) {
    "use strict";

    if (this === void 0 || this === null) { throw new TypeError(); }

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== "function") { throw new TypeError(); }

    // no value to return if no initial value and an empty array
    if (len === 0 && arguments.length === 1) { throw new TypeError(); }

    var k = 0;
    var accumulator;
    if (arguments.length >= 2) {
      accumulator = arguments[1];
    } else {
      do {
        if (k in t) {
          accumulator = t[k++];
          break;
        }

        // if array contains no values, no initial value to return
        if (++k >= len) { throw new TypeError(); }
      }
      while (true);
    }

    while (k < len) {
      if (k in t) {
        accumulator = fun.call(undefined, accumulator, t[k], k, t);
      }
      k++;
    }

    return accumulator;
  };
}

Wow. Thanks Paul. I wasn’t expecting such a finished solution!

I hate to be a pain but could you please explain how the reduce function works? I’ve just been through some definitions where it is used to total up the contents of an array but I can’t see how it working here. Is the distance from one location being compared the the next and the location with the shortest distance is kept and this repeated until one remains?

I hope it’s ok to ask - I’d like to understand the code if possible!

Sure thing - the reduce function compares the first item with either an initial value or the second value, and whatever the custom function returns is then compared with the next item in the array, until there’s only one item left.

Details on how it works are found at https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce

The reduce method is a built-in feature of JavaScript now - it’s just older web browsers that you might need to provide additional support, by adding the reduce method if the browser doesn’t know how to do it.

Ahhhhhh - penny drops it’s a way of doing the same function on two things at once and returning one. I think I can follow the process (function by function) now.

Thank you so much. I’ll give it a whirl and see how I get on!

You code works perfectly. I have created a page that performs the calculation using lon and lat obtained from the browser using geolocation.

However I have strange issue. The first time the page is loaded and the locations data is loaded directly from the json source the find closest function works perfectly. Giving me the nearest location from the list to where I am.

But from there after when the data is pulled from local storage object the result is incorrect.

At first I thought it was because there must be a limit to how much data can be stored in a single localstorage item. When I use Chromes inspector to see whats been stored - sure enough the last viewable record matches that returned as closest.

However, if I pass the lon and lat as fixed values ( not from geolocation) but still use the locations data from local storage the closest returned location is correct again.

I am baffled.

Is there a limit on local storage items?

This sounds like a situation where investigating a test web page that demonstrates the problem, would be required.