If I was doing it using traditional GIS software, I would create a buffer along the route, and clip the crime points to the buffer. I used the same logic here, but using different tools.
Google Directions API returns a number of vertices along the route, and these vertices could be used to construct a bounding box. While it is possible to construct a bounding box using the beginning and the end points in the routes, the bounding box could be too big, especially if the coordinates were too much off on either the x or y axis. For this reason, it is much better to use a buffer, which would be following the shape of the route.
At first, I constructed a bounding box around each of the vertices returned from the Directions API and I ended up with a lot of boxes. Unfortunately, datasf.org apparently has some limits on how many bounding boxes can be passed into the api call (it appears to be 46 boxes before the api call returns an error).
The next idea was to use a smoothing algorithm, which would straighten up the route a little bit and return a smaller number of vertices. The Ramer–Douglas–Peucker algorithm can be used to reduce the number of points in a curve, and I found a Javascript implementation of it here. Unfortunately, this was not particularly useful. If the curve is straightened out too much, the vertices are too spread out from each other, and the bounding boxes will have gaps in between. Technically, there is a way to construct bounding boxes in such a way that they will touch each other. However, I must admit I am not that knowledgeable about geographic algorithms - while I made some attempts in this direction, I could not quickly find a way to account for directional changes in latitude and longitude. I have some ideas on how to proceed but I will leave them for a later exploration.
In the meanwhile, I have come across a great JS library, called Turf.js. It performs all the necessary GIS processing tasks using GeoJSON data. Using Turf.js, I have come up with the following algorithm of actions: 1) get routing vertices from Google Directions; 2) create a buffer along the route; 3) use the buffer to create a bounding box and pass the bounding box coordinates into the api call, which returns crimes within the box; 4) use the buffer to clip the points to the new area; 5) happy display the points.
Below is the function that performs some of these tasks:
/* Calculates a route between two points
and uses coordinates along the route to create a bounding box and a buffer
around the route. Constructs a callback function into which the coordinates,
crime types, and dates are passed.
*/
function calcRoute(callback) {
var request = {
origin: from,
destination: to,
travelMode: google.maps.TravelMode.WALKING
};
directionsService.route(request, function(response, status)
{
if (status == google.maps.DirectionsStatus.OK) {
var routes = response.routes[0];
var google_coords = [];
for (var i = 0; i < routes.overview_path.length; i++){
var coordinates = routes.overview_path[i];
/*
A reminder to myself what longitude and latitude is.
coordinates.D; // x, longitude
coordinates.k; // y, latitude
*/
google_coords.push([coordinates.D, coordinates.k]);
}
// create a feature collection of line strings for turf library
var feature_collection = turf.linestring(google_coords);
// create a buffer using turf library
var buffer = turf.buffer(feature_collection, 0.25, 'kilometers');
// create a bounding envelope of the buffer using turf library
var envelope = turf.envelope(buffer);
/* get upper left and lower right corners of the bounding box and
concatenete them together to be in the following form:
maxLat, minLong, minLat, maxLong; e.g., 41.885001, -87.645939, 41.867011, -87.618516
*/
var first_corner = envelope.geometry.coordinates[0][3].sort(function(a,b){
return b - a;
});
var second_corner = envelope.geometry.coordinates[0][1].sort(function(a,b){
return b - a;
});
var flattened = [first_corner, second_corner].reduce(function(a,b){
return a.concat(b);
});
/*
use coordinates of the bounding box to costruct query along with data on
crime types from checkboxes and dates from slider to be passed into
the callback function
*/
var query = "within_box(location, " + flattened[0] + ", " +
flattened[1] + ", " + flattened[2] + ", " + flattened[3] + ")";
if (crimeTypeFromCheckbox != ''){
var apiCall = baseUrl + "&$where=" + query + " AND" + crimeTypeFromCheckbox + dateFromSlider;
}else{
var apiCall = '';
}
var arr = [];
arr.push(apiCall);
arr.push(buffer);
callback(arr);
directionsDisplay.setDirections(response);
}
});
}
No comments :
Post a Comment