Question

I am attempting to sort a knockout observable array using geo-points according to proximity to the users location. I have a function that loops through all the stores in my array and finds the closest marker to the users current location. Then im nesting that inside another loop, using insertion sort to sort all the elements.

I have two issues. First. My swap method is a bit funky.I think its breaking the dom. Dont think i understand how to swap elemts correctly in a knockout observable.

Second. Is this even the right approach? Ko observable array has a built in sort method but im not sure how to implement it using the closest point to the user function. Ive attached the code below. Any help or insight would be appreciated.

var stores = ko.observableArray(); storesRepository.getFeed(stores);

function closestMarker(lat, lng) {

            var pi = Math.PI;
            var R = 6371; //equatorial radius
            var lat1 = lat;
            var lon1 = lng;

            var distances, closest, min, chLat, chLon, dLat, dLon, rLat1, rLat2, a, c, d;

            for (j = 0; j < stores().length; j++) { // outer loop uses insertion sort to "sort" elements.
                 distances = [];
                 closest = -1;
                 min = 0;
                for (i = j+1; i < stores().length; i++) { // inner loop finds closest marker to user

                    var lat2 = stores()[i].latitude();
                    var lon2 = stores()[i].longitude();
                     chLat = lat2 - lat1;
                     chLon = lon2 - lon1;
                     dLat = chLat * (pi / 180);
                     dLon = chLon * (pi / 180);

                     rLat1 = lat1 * (pi / 180);
                    rLat2 = lat2 * (pi / 180);

                     a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                            Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(rLat1) * Math.cos(rLat2);
                     c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
                     d = R * c;

                    distances[i] = d;
                    if (closest == -1 || d < distances[closest]) {
                        closest = i;
                    }


                }
                swap(j, closest);

            }
            function swap(a, b) { // i dont think this is the right approach
               // alert("working");
               var temp = stores()[b];
               stores.replace(stores()[b],stores()[a]);
               stores.replace(stores()[a], temp);

            }

        }
        return stores;
Was it helpful?

Solution

A couple of points:

When performing lots of manipulations on an observable array, it is best to perform them on the underlying array, then signal the observable array when you are done. This will reduce churn on the array.

Here is a "better" implementation of swap which follows the above rule:

function swap(a, b) {
    var ary = stores(),
        temp = ary[a];

    stores.valueWillMutate();
    ary[a] = ary[b];
    ary[b] = temp;
    stores.valueHasMutated();
}

Now that I've said that, you should not use it :) You should instead try to use the built-in sort function instead of using your insertion sort. This follows the first rule even better by only sending out a notification once the sort operation is complete, instead of on every swap. And the built in sort uses native browser code and will likely be faster than JavaScript code you write. All you need to do is write a comparison function which indicates whether a or b is closer to the user, then pass it to the sort method:

var distanceToUser = function (a) {
    var lat2 = a.latitude(),
        lon2 = a.longitude(),
        chLat = lat2 - lat1,
        chLon = lon2 - lon1,
        dLat = chLat * pi / 180,
        dLon = chLon * pi / 180,
        rLat1 = lat1 * pi / 180,
        rLat2 = lat2 * pi / 180,
        aa = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
             Math.sin(dLon / 2) * Math.sin(dLon / 2) *
             Math.cos(rLat1) * Math.cos(rLat2),
        c = 2 * Math.atan2(Math.sqrt(aa), Math.sqrt(1 - aa));

    return R * c;
},
compare = function (a, b) {
    var da = distanceToUser(a),
        db = distanceToUser(b);

    return da < db ? -1 : (da > db ? 1 : 0);
};

stores.sort(compare);

If you have a very large number of items in your stores array, then this could be sped up by looping through the array once to calculate the distance to the user and storing it as a property on each item in the array, then changing the compare method to just compare this distance property. That would prevent continually recalculating the distance over and over.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top