| Line 1269... |
Line 1269... |
| 1269 |
// pings. SUGGESTED route = the Google-optimal road route through the journey's
|
1269 |
// pings. SUGGESTED route = the Google-optimal road route through the journey's
|
| 1270 |
// checkpoints (green dotted). Both are drawn and compared by distance.
|
1270 |
// checkpoints (green dotted). Both are drawn and compared by distance.
|
| 1271 |
function bjDrawRoutes(map, cps, pathPts, takenTime, timedPts){
|
1271 |
function bjDrawRoutes(map, cps, pathPts, takenTime, timedPts){
|
| 1272 |
var legend=bjRouteLegend();
|
1272 |
var legend=bjRouteLegend();
|
| 1273 |
|
1273 |
|
| - |
|
1274 |
// De-jitter the breadcrumb first: stationary GPS drift (e.g. under a roof)
|
| - |
|
1275 |
// scatters points around a fixed spot, which makes the road-snapped route weave
|
| - |
|
1276 |
// back and forth and inflates the taken distance. Collapsing sub-JITTER_MIN_M
|
| - |
|
1277 |
// hops removes those clusters before we draw or measure the trail.
|
| - |
|
1278 |
var trail=bjCleanTrail(pathPts, 30);
|
| - |
|
1279 |
|
| 1274 |
// taken — road route traced through the recorded breadcrumb (in order)
|
1280 |
// taken — road route traced through the recorded breadcrumb (in order)
|
| 1275 |
if(pathPts && pathPts.length>=2){
|
1281 |
if(trail && trail.length>=2){
|
| 1276 |
var ds1=new google.maps.DirectionsService();
|
1282 |
var ds1=new google.maps.DirectionsService();
|
| 1277 |
var sample=bjSample(pathPts, 25); // Directions cap: 25 incl. ends
|
1283 |
var sample=bjSample(trail, 25); // Directions cap: 25 incl. ends
|
| 1278 |
var o=sample[0], d=sample[sample.length-1];
|
1284 |
var o=sample[0], d=sample[sample.length-1];
|
| 1279 |
var vias=sample.slice(1,-1).map(function(p){ return {location:new google.maps.LatLng(p.lat,p.lng), stopover:false}; });
|
1285 |
var vias=sample.slice(1,-1).map(function(p){ return {location:new google.maps.LatLng(p.lat,p.lng), stopover:false}; });
|
| 1280 |
bjRouteRequest(ds1, o, d, vias, false, function(res){
|
1286 |
bjRouteRequest(ds1, o, d, vias, false, function(res){
|
| 1281 |
bjRenderRoute(map, res, {color:'#e03131', dashed:false, z:55, label:'Route taken (actual GPS)', time:takenTime, timed:timedPts});
|
1287 |
bjRenderRoute(map, res, {color:'#e03131', dashed:false, z:55, label:'Route taken (actual GPS)', time:takenTime, timed:timedPts});
|
| 1282 |
legend.set('taken', bjRouteKm(res));
|
1288 |
legend.set('taken', bjRouteKm(res));
|
| 1283 |
}, function(status){
|
1289 |
}, function(status){
|
| 1284 |
console.warn('Directions (taken) failed:', status);
|
1290 |
console.warn('Directions (taken) failed:', status);
|
| 1285 |
// fallback: plot the raw breadcrumb (straight hops), with arrows, and measure along it
|
1291 |
// fallback: plot the cleaned breadcrumb (straight hops), with arrows, and measure along it
|
| 1286 |
var poly=new google.maps.Polyline({path:pathPts,geodesic:false,strokeColor:'#e03131',strokeOpacity:.9,strokeWeight:4,zIndex:55,map:map,
|
1292 |
var poly=new google.maps.Polyline({path:trail,geodesic:false,strokeColor:'#e03131',strokeOpacity:.9,strokeWeight:4,zIndex:55,map:map,
|
| 1287 |
icons:[{icon:{path:google.maps.SymbolPath.FORWARD_CLOSED_ARROW,strokeColor:'#e03131',fillColor:'#e03131',fillOpacity:1,scale:3},offset:'3%',repeat:'120px'}]});
|
1293 |
icons:[{icon:{path:google.maps.SymbolPath.FORWARD_CLOSED_ARROW,strokeColor:'#e03131',fillColor:'#e03131',fillOpacity:1,scale:3},offset:'3%',repeat:'120px'}]});
|
| 1288 |
var km=google.maps.geometry.spherical.computeLength(pathPts.map(function(p){ return new google.maps.LatLng(p.lat,p.lng); }))/1000;
|
1294 |
var km=google.maps.geometry.spherical.computeLength(trail.map(function(p){ return new google.maps.LatLng(p.lat,p.lng); }))/1000;
|
| 1289 |
bjRouteClick(map, poly, '#e03131', 'Route taken (actual GPS)', km, '', takenTime, timedPts);
|
1295 |
bjRouteClick(map, poly, '#e03131', 'Route taken (actual GPS)', km, '', takenTime, timedPts);
|
| 1290 |
legend.set('taken', km);
|
1296 |
legend.set('taken', km);
|
| 1291 |
});
|
1297 |
});
|
| 1292 |
} else {
|
1298 |
} else {
|
| 1293 |
legend.set('taken', false);
|
1299 |
legend.set('taken', false);
|
| Line 1306... |
Line 1312... |
| 1306 |
if(capped) legend.note('Suggested route limited to first 23 stops');
|
1312 |
if(capped) legend.note('Suggested route limited to first 23 stops');
|
| 1307 |
} else {
|
1313 |
} else {
|
| 1308 |
legend.set('suggested', false);
|
1314 |
legend.set('suggested', false);
|
| 1309 |
}
|
1315 |
}
|
| 1310 |
}
|
1316 |
}
|
| - |
|
1317 |
// Collapse stationary GPS jitter: keep the first point, then only points that
|
| - |
|
1318 |
// moved at least `minM` metres from the last kept one; the true final point is
|
| - |
|
1319 |
// always kept so the trail ends at the last known location. This removes the
|
| - |
|
1320 |
// dense drift clusters (weak signal indoors) that otherwise draw back-and-forth
|
| - |
|
1321 |
// loops and inflate the taken distance, while preserving real movement.
|
| - |
|
1322 |
function bjCleanTrail(pts, minM){
|
| - |
|
1323 |
if(!pts || pts.length<2) return pts ? pts.slice() : [];
|
| - |
|
1324 |
var out=[pts[0]], last=pts[0];
|
| - |
|
1325 |
for(var i=1;i<pts.length-1;i++){
|
| - |
|
1326 |
var p=pts[i];
|
| - |
|
1327 |
var d=google.maps.geometry.spherical.computeDistanceBetween(
|
| - |
|
1328 |
new google.maps.LatLng(last.lat,last.lng), new google.maps.LatLng(p.lat,p.lng));
|
| - |
|
1329 |
if(d>=minM){ out.push(p); last=p; }
|
| - |
|
1330 |
}
|
| - |
|
1331 |
out.push(pts[pts.length-1]); // keep the final fix regardless
|
| - |
|
1332 |
return out;
|
| - |
|
1333 |
}
|
| 1311 |
// Evenly downsample a list to at most `max` items, always keeping first and last.
|
1334 |
// Evenly downsample a list to at most `max` items, always keeping first and last.
|
| 1312 |
function bjSample(arr, max){
|
1335 |
function bjSample(arr, max){
|
| 1313 |
if(arr.length<=max) return arr.slice();
|
1336 |
if(arr.length<=max) return arr.slice();
|
| 1314 |
var out=[], step=(arr.length-1)/(max-1);
|
1337 |
var out=[], step=(arr.length-1)/(max-1);
|
| 1315 |
for(var i=0;i<max;i++) out.push(arr[Math.round(i*step)]);
|
1338 |
for(var i=0;i<max;i++) out.push(arr[Math.round(i*step)]);
|