Question

I have a couchdb that contains events with their starting time and their coordinates. I wrote a list view that calculates the distance from your current location to those events like follows:

locateEvents: function(head, req){ 

        var row, comma = ''; 
        start({
            "headers": {
                "Content-Type": "application/json"
            }
        });
        if(req.query.latitude&&req.query.longitude&&req.query.radius&&req.query.now){

            var R = 6371; // km
            var dLon, dLat, lat1, lat2;

            var results = [];
            while(row = getRow()) { 
                dLon = Math.abs(row.value.venue.longitude-req.query.longitude);
                dLat = Math.abs(row.value.venue.latitude-req.query.latitude);

                dLon = (dLon*3.14159)/180;
                dLat = (dLat*3.14159)/180;

                lat1 = (Math.abs(req.query.longitude)*3.14159)/180;
                lat2 = (Math.abs(req.query.latitude)*3.14159)/180;

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

                if((d < req.query.radius)&&(row.value.start_time > req.query.now)){
                    results.push(row.value);
                }
            }
            send(JSON.stringify(results));
        }else{
            start({"code": 500});
            send("Latitude, longitude, page and radius parameters should be provided. i.e: latitude=value&longitude=value&radius=value&now=value");
        }

I have a simple byDate view for the event like so:

byDate: {
            map: function(doc){ if (doc.resource === 'Event') {emit(doc.venue.start_time, doc);}}
        }

My concern: Is there a way to sort the events first by distance within the list and then resort the sorted list by the starting time?

Was it helpful?

Solution

If I understand you correctly, you want the closest event to come up first. If there are two events within the same distance, show the earliest first.

This can be done by saving the calculated distance in the objects before pushing them to the result set:

row.value._distance = d;
results.push(row);

Note that you can't save the document now since all fields starting with an underscore _ are reserved by couchdb. But since the distance to an event is likely to be different for each request that's fine. Just remember to remove the property if you need to save the document back to the store.

In the next step we need to come up with a clever way of sorting your events - all the information we need is now stored in the document.

Since JavaScript does not really like sorting complex data structures, we have to do some legwork:

var sort = function(a,b) {
  if (JSON.stringify(a) == JSON.stringify(b)) return 0;
  return (JSON.stringify([a,b]) == JSON.stringify([a,b].sort())) ? -1 : 1
};

This function just sorts an array of simple values like so:

> sort(["a",1], ["a", 0])
1

> sort(["a",0], ["a", 1])
-1

> sort(["a",0], ["a", 0])
0

Now for the fun part, before you send the results back to the client, you sort them:

// ...
results.sort(function(a, b) {
  return sort(
    [a.value._distance, a.value.venue.start_time],
    [b.value._distance, b.value.venue.start_time]
  );
});

send(JSON.stringify(results));

Example:

[{"value": {"_distance": 100, "venue": { "start_time": "Wed, 21 Mar 2012 04:31:24 -0700" } } },
 {"value": {"_distance": 212, "venue": { "start_time": "Sat, 13 Oct 2012 02:52:12 -0700" } } },
 {"value": {"_distance": 235, "venue": { "start_time": "Mon, 22 Jul 2013 12:50:20 -0700" } } },
 {"value": {"_distance": 677, "venue": { "start_time": "Thu, 09 May 2013 03:39:55 -0700" } } },
 {"value": {"_distance": 654, "venue": { "start_time": "Thu, 29 Sep 2011 15:31:46 -0700" } } },
 {"value": {"_distance": 100, "venue": { "start_time": "Tue, 20 Sep 2011 19:16:37 -0700" } } }]

becomes this after using the sort function above:

[{"value": {"_distance": 100, "venue": {"start_time": "Tue, 20 Sep 2011 19:16:37 -0700" } } },
 {"value": {"_distance": 100, "venue": {"start_time": "Wed, 21 Mar 2012 04:31:24 -0700" } } },
 {"value": {"_distance": 212, "venue": {"start_time": "Sat, 13 Oct 2012 02:52:12 -0700" } } },
 {"value": {"_distance": 235, "venue": {"start_time": "Mon, 22 Jul 2013 12:50:20 -0700" } } },
 {"value": {"_distance": 654, "venue": {"start_time": "Thu, 29 Sep 2011 15:31:46 -0700" } } },
 {"value": {"_distance": 677, "venue": {"start_time": "Thu, 09 May 2013 03:39:55 -0700" } } }]

Note that after sorting, the first two objects have _distance == 100 but since the first one is earlier, it is sorted first.

Hope that helps!

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