Meteor Geolocation Package: Display Posts posted from a location within x miles of user

UPDATE: I have gotten each post to stash the lng/lat location of the device it was created from. I am now having trouble display only posts within x miles of the devices current location. Here is updated detailed description of current issue:

Whenever a post is created a value of location is created which is stored with: {“lat”:39.7230949,“lng”:-104.83521619999999}. The lat and longitude of the current location when the post was made.

This is from a function, Geolocation.latLng();, which prints out the object with the lat and lng of the current device.

I am now trying to figure out how to have the page which lists all the post, only show post that fall within a radius of the devices current location.

Here is my current function which lists all the post from my database.

Template.yakResults.searchResults = function () {
var keyword = Session.get(“search-query”);
var query = new RegExp( keyword, ‘i’ );
var results = Yaks.find( { $or: [{‘yak’: query}] }, {sort: {score: -1}});
return {results: results};
}
What do I have to do to get the results to be with say 10 mile radius of the devices current long/lat which can be found with the function mentioned above.

I imagine I use an if statement of some sort or maybe a filer when I am finding the posts in the Yaks collection?

Does any one have any idea on how I can filter the collection of posts to do something similar to this?

If i need to post up more code to make it easier to understand my question please let me know and I will.

Thanks again to any one who helps out!

ORIGINAL: I am wondering if any one has any experience using this plugin for Meteor JS: Meteor Geolocation Package(https://github.com/meteor/mobile-packages/blob/master/packages/mdg:geolocation/README.md) with their app in order to display data that was posted at a location near the current user(similar to how yik yak works)?

If you have experience with this could you please explain to me how would one go about doing something similar to this? Are there any tutorials or posts about this same kind of thing you can refer me to?

Looking at the plugin documentation there doesn’t seem to be much information on how to do something similar to this so I would greatly appreciate any insight any of you may have on this issue.

Thanks in advanced to any one helps out!

Haven’t used the plugin, but I made a simple “mapchat” with Meteor and CoffeeScript: http://mapchat.meteor.com/

Code: https://github.com/erokar/mapchat

Awesome! thank you so much for sharing with with me. I believe this should show me what I am trying to do. I will report back on how it goes :smile:

The chat messages don’t seem to turn up anymore, for some reason. (Haven’t tested it in a while.) But you can see them as console.logs if you open the JS console. And the code should at least demonstrate how this can be done on a conceptual level.

I have figured out how to stash the lat/long of the device the post was created from but I can not figure out exactly how to show get the posts that are within a certain radius the device looking at the list page that displays the post as you have done in your chat.

I have updated my original post showing the function being used to display all the post and would greatly appreciate any input on how to make it work similar to how your messages are loaded for your map chat.

Thanks again in advance for any future input you may give! I really appreciate it.

Have a look at this CoffeeScript code:

  distance = (center, location) ->

    degreesToRadians = (degrees) ->
      return (degrees * Math.PI) / 180 

    startLatRads = degreesToRadians(center.latitude)
    startLongRads = degreesToRadians(center.longitude)
    destLatRads = degreesToRadians(location.latitude)
    destLongRads = degreesToRadians(location.longitude)
    earthRadiusInKm = 6371
    distance = Math.acos(Math.sin(startLatRads) * Math.sin(destLatRads) + Math.cos(startLatRads) * Math.cos(destLatRads) * Math.cos(startLongRads - destLongRads)) * earthRadiusInKmRadius
    return distance * 1000

The function above returns the distance from some center to some location, where center and location are objects with longitude and latitude properties. Then use this function to filter the messages:

    messagesToDisplay = messgages.filter (m) -> distance(centerPostion, m.position) < someMaxRadius

Just showing the pronciple here, haven’t test run the code. Good luck!

Funny, I wrote something very similar (but without using meteor). It would be very interesting to benchmark it vs the vert.x implementation I wrote :slight_smile:
http://idoco.github.io/map-chat/

These few links might help you.

http://blog.mongolab.com/2014/08/a-primer-on-geospatial-data-and-mongodb/

Mongo supports querying geojson directly - I started an app ages ago (i don’t have the references to what I used to create this, but I know it worked…)

Ensure you have an index on your collection.

db.your-collection.createIndex( { geometry : “2dsphere” } )

Then, use a query like this:

db.your-collection.find(
   {
     geometry:
       { $near :
          {
            $geometry:{ type:"Point", coordinates:[ yourLongitude,yourLatitude]},
            $minDistance: 1000,
            $maxDistance: 5000
          }
       }
   }
);

$minDistance and #maxDistance are in meters.

Mongo requires long/lat instead of lat/long, so make sure your documents look like this:

{ “_id” : ObjectId(“5551d95fc3241120a31b6008”), “type” : “Feature”, “geometry” : { “type” : “Point”, “coordinates” : [ 152.934352, -27.592785 ] },

Your documents need to be in geoJson format - for my example app, I had a csv file and uploaded it to
http://www.convertcsv.com/csv-to-geojson.htm to get each row returned as geoJson.

Hope this gives you a start.

Thank you very much for posting this explanation along with he blog post! I was finally able to get it all to work correctly :smile:

Thanks again!

But remember that meteor oplog driver does not support $near so it will fallback to polling.

Thank you for pointing this out to me. so does this mean I need to remove $near and/or replace it with something else?

So I have reindex my db loc field to be a 2dsphere using the command you mentioned(using loc at the start instead of geometry) and the $maxDistance appears to be working correctly but no matter what I do I can not seem to get the $minDistance to work.

Have you experienced this in the past and know what is causing this?

Um, no, sorry! :frowning:

I never really continued with the app I used that code in, so can’t really help you there.

But I had a thought - if $maxDistance is working (ie: don’t show me any people who are more than 5000 meters away, as I understand it) then maybe just don’t bother with $minDistance at all?

That would get everyone within that 5000 meter radius?

Unless your use case requires $minDistance… :smile:

(I’m reminded of the Police song - “Don’t Stand So Close To Me” )

Now that I think of it, that query is effectively saying, “Show me everyone who is within 5000 meters of me, but no closer than 1000 meters” - ie: ignore people who are standing 2 meters away, and ignore people who are 5002 meters away - do you read it that way as well?

Hmmmm.

Over to you!

You can use some basic approximation to find these people. Let’s imagine that you are in some point [latMy, lngMy]. You can put a geodesic square on the map, with a side about 5000 meters and you standing in a center. All points inside this square are [5000…5000*sqrt(2)] meters from you and have their lat && lng from this equation:

lat0 < lat < lat1
lng0 < lng < lng1

, where [lat0,lng0] is left down point and [lat1,lng1] is top right.

We use a function like this to get this points (got somewhere on stackoverflow):

Meteor.util.getBoundingBox = (centerPoint, distance) ->
    MIN_LAT = undefined
    MAX_LAT = undefined
    MIN_LON = undefined
    MAX_LON = undefined
    R = undefined
    radDist = undefined
    degLat = undefined
    degLon = undefined
    radLat = undefined
    radLon = undefined
    minLat = undefined
    maxLat = undefined
    minLon = undefined
    maxLon = undefined
    deltaLon = undefined
    if distance < 0
        return 'Illegal arguments'
    # helper functions (degrees<–>radians)

    Number::degToRad = ->
        this * Math.PI / 180

    Number::radToDeg = ->
        180 * this / Math.PI

    # coordinate limits
    MIN_LAT = (-90).degToRad()
    MAX_LAT = 90.degToRad()
    MIN_LON = (-180).degToRad()
    MAX_LON = 180.degToRad()
    # Earth's radius (km)
    R = 6378.1
    # angular distance in radians on a great circle
    radDist = distance / R
    # center point coordinates (deg)
    degLat = centerPoint.lat
    degLon = centerPoint.lng
    # center point coordinates (rad)
    radLat = degLat.degToRad()
    radLon = degLon.degToRad()
    # minimum and maximum latitudes for given distance
    minLat = radLat - radDist
    maxLat = radLat + radDist
    # minimum and maximum longitudes for given distance
    minLon = undefined
    maxLon = undefined
    # define deltaLon to help determine min and max longitudes
    deltaLon = Math.asin(Math.sin(radDist) / Math.cos(radLat))
    if minLat > MIN_LAT and maxLat < MAX_LAT
        minLon = radLon - deltaLon
        maxLon = radLon + deltaLon
        if minLon < MIN_LON
            minLon = minLon + 2 * Math.PI
        if maxLon > MAX_LON
            maxLon = maxLon - (2 * Math.PI)
    else
        minLat = Math.max(minLat, MIN_LAT)
        maxLat = Math.min(maxLat, MAX_LAT)
        minLon = MIN_LON
        maxLon = MAX_LON
    return {
        lng0: minLon.radToDeg()
        lat0: minLat.radToDeg()
        lng1: maxLon.radToDeg()
        lat1: maxLat.radToDeg()
    }

and then we happily find posts like this. This is not an actually code, just an idea.

# loc = Geolocation.latLng()
# srch = {userId: ..., createdAt: ...}
# distance Km
getPostsCondition = (loc, srch, distance) ->
    return unless loc && loc?.lat
    nsrch = []
    for key of srch
        cond = {}
        cond[key] = srch[key]
        nsrch.push cond
    bbox = Meteor.util.getBoundingBox loc, distance-0
    nsrch.push {lat: $gte: bbox.lat0}
    nsrch.push {lat: $lte: bbox.lat1}
    nsrch.push {lng: $gte: bbox.lng0}
    nsrch.push {lng: $lte: bbox.lng1}
    return {$and: nsrch}

getPosts = (condition) ->
    return Posts.find(condition)

Also you can filter gotten posts thru distance function above to get strictly 5000m circle and not the square. In my experience, users are pretty happy with a square, actually )

haha that is a great song!

Yes that is how I read it, and how I want it to work. For some reason though the $min distance doesn’t take affect and no matter what the number is it, the posts will show up.

How ever the maxdistance is still working like a charm. Hopefully I will be able to solve it this morning and I will report back here how it goes :slight_smile:

Unfortunately I have had no luck in figuring out why mongoDB’s $near command will not use $minDistance when it sorts the post.

Only thing I can think of causing the problem is the my field of the post that holds the location(loc:) isn’t indexed as “2dsphere” but I am not sure why this would be since I have run

db.my-collection.createIndex( { loc : “2dsphere” } )

:expressionless:

Does any one know if there is a way to check if my field that holds the location is actually “2dsphere”?