POSTS
Show routes to current geohash
The Plan
My geohashing alert2016-01-13 15:28-system at 123k.org shows the current geohash for my home graticule. The plan is to the nearest route which passes by.
Finding Something
As mentioned earlier I have all points i ever visited in a collection.
This collection holds a geospatial index. Therefore I can issue mongo queries with
the $nearSphere
command. This command finds the first points from a given location.
def route_to | |
lat = params[:lat].to_f | |
lon = params[:lon].to_f | |
first_point = TrackPoint.where({:location => {'$nearSphere' => [lat, lon]}}).limit(1).first() | |
route = Route.find(first_point.route_id) | |
gjson_line = { | |
:type => "Feature", | |
:geometry => { | |
:type => "LineString", | |
:coordinates => route.track_points.map { |p| p.location } | |
} | |
} | |
feature_collection = { | |
:type => "FeatureCollection", | |
:features => [gjson_line] | |
} | |
render :json => Yajl::Encoder.encode(feature_collection), :content_type => 'application/json' | |
end |
So this just works. But what about other routes which may also be nice and just around the corner? We can select many points around the hash point and check which routes they’re on.
def routes_to | |
lat = params[:lat].to_f | |
lon = params[:lon].to_f | |
features = [] | |
TrackPoint.collection.aggregate([{'$geoNear' => {near: [lon, lat], spherical: true, num: 500, maxDistance: 5/6371, distanceMultiplier: 6371, distanceField: 'dist.calculated'}}, {'$group' => {'_id' => '$route_id'}}]).each { |route_id| | |
route = Route.find(route_id[:_id]) | |
gjson_line = { | |
:type => "Feature", | |
:geometry => { | |
:type => "LineString", | |
:coordinates => route.track_points.map { |p| p.location } | |
} | |
} | |
features << gjson_line | |
} | |
feature_collection = { | |
:type => "FeatureCollection", | |
:features => features | |
} | |
render :json => Yajl::Encoder.encode(feature_collection), :content_type => 'application/json' | |
end |
The trick here is that we have a implicit distinct
by grouping the points according to their route-id. So we really find
every route within 5km just once.
So this also works, but it is not 100% predictable how many routes we really find. If the point sits just around the corner,
this algorithm will find tons of routes which then are all queried and displayed. Also not useful. I’ll have to invent a custom
limit
here. Or I find a solution to limit the results of the aggregation run - this is just open now.
I think I’ll stick with the first approach as it nicely stops finding things after the first point and as nobody really uses my site anyways I provide the second approach via button click.
Showing The Routes
Showing the routes is dead easy again. We just fetch some geo-json and add it to the map:
firstRouteToPoint: (hashPoint) -> | |
callback = (response) => | |
L.geoJson(response).addTo(@map) | |
$.get '/map/first/route/to/' + hashPoint[0] + '/' + hashPoint[1], callback, 'json' | |
addRoutesToPoint: (hashPoint) -> | |
callback = (response) => | |
L.geoJson(response, { | |
style: (feature) => | |
return {color: @random_color()} | |
}).addTo(@map) | |
$.get '/map/routes/to/' + hashPoint[0] + '/' + hashPoint[1], callback, 'json' | |
random_color: () -> | |
letters = '0123456789ABCDEF'.split('') | |
color = '#' | |
color += letters[Math.round(Math.random() * 15)] for [0..5] | |
color | |
As my geohashing page shows the whole graticule, but my bike-rides just cover some 20km radius I only display the route when the point sits within 35km of my home. So we need a distance calculation. This is again easy as leaflet just provides this.
if L.latLng(@location.lat, @location.lon).distanceTo(L.latLng(lat, lon)) < 35000
@firstRouteToPoint([lon, lat])
As this feature is not visible every day I took a screenshot for reference. You can see, that the additional routes are not 100% useful, especially because they are not clickable yet. But this is another feature.
The first route is not so bad. It looks good but is also not clickable yet.