Reroute (↪): only detour when a nogo is actually ahead on the route

The button built a detour unconditionally and picked the target from the globally
nearest route point + 1500m, which on loops/off-route jumped to the start ('first
waypoint'). Now: find current progress on the route, use the compass bearing to pick
travel direction, and only reroute when a nogo sits on the route ahead within a
lookahead window - else toast 'впереди затыка нет'. Target is the first point clear
of nogos past the blockage.
This commit is contained in:
4aiman 2026-10-26 20:57:24 +03:00
commit ac37f36a06

View file

@ -3603,25 +3603,54 @@ function _offsetLatLon(lat, lon, brDeg, d) {
// sits on the route (<=150 m), else the current position's nearest route point. Target = a // sits on the route (<=150 m), else the current position's nearest route point. Target = a
// route point ~1.5 km further along; on failure step further (up to the route end). // route point ~1.5 km further along; on failure step further (up to the route end).
// Returns { points:[{lat,lon}], target:{lat,lon} } or null. // Returns { points:[{lat,lon}], target:{lat,lon} } or null.
// Reroute back onto the active route PAST a blockage ahead. Only acts when a nogo
// actually sits on the route ahead of the current progress point (in the travel
// direction) — otherwise returns {noBlockage:true} and builds nothing (pressing it
// with no blockage used to route to a bogus far point). Returns {points,target} on
// success, null if a detour couldn't be built, {noBlockage:true} if nothing to do.
async function rejoinReroute(cur, bearingDeg, routePts, nogos) { async function rejoinReroute(cur, bearingDeg, routePts, nogos) {
if (!cur || !routePts || routePts.length < 2) return null; if (!cur || !routePts || routePts.length < 2) return null;
let cum = [0]; let cum = [0];
for (let i = 1; i < routePts.length; i++) cum[i] = cum[i-1] + GPS.distance(routePts[i-1].lat, routePts[i-1].lon, routePts[i].lat, routePts[i].lon); for (let i = 1; i < routePts.length; i++) cum[i] = cum[i-1] + GPS.distance(routePts[i-1].lat, routePts[i-1].lon, routePts[i].lat, routePts[i].lon);
let nearestRouteDist = (pt) => { let d = Infinity; for (let p of routePts) { let dd = GPS.distance(pt.lat, pt.lon, p.lat, p.lon); if (dd < d) d = dd; } return d; }; let inNogo = (p) => nogos && nogos.some(n => GPS.distance(p.lat, p.lon, n.lat, n.lon) <= (n.radius || 25) + 15);
// reference point of the blockage
let ref = cur; // current progress = nearest route point to the current position
if (nogos && nogos.length) { let curIdx = 0, curD = Infinity;
let best = null, bestD = Infinity; for (let i = 0; i < routePts.length; i++) { let d = GPS.distance(cur.lat, cur.lon, routePts[i].lat, routePts[i].lon); if (d < curD) { curD = d; curIdx = i; } }
for (let n of nogos) if (nearestRouteDist(n) <= 150) { let dc = GPS.distance(cur.lat, cur.lon, n.lat, n.lon); if (dc < bestD) { bestD = dc; best = n; } }
if (best) ref = best; // travel direction along the route: increasing index unless the compass bearing
// clearly points the other way (handles routes ridden in reverse / loops).
let step = 1;
if (bearingDeg != null && !isNaN(bearingDeg)) {
let ja = Math.min(routePts.length - 1, curIdx + 1), jb = Math.max(0, curIdx - 1);
if (ja !== jb) {
let fb = GPS.bearing(routePts[jb].lat, routePts[jb].lon, routePts[ja].lat, routePts[ja].lon);
let diff = Math.abs(((bearingDeg - fb + 540) % 360) - 180); // 0 = aligned w/ increasing idx, 180 = opposite
if (diff > 90) step = -1; // travelling against increasing index → forward is decreasing idx
}
} }
let refIdx = 0, refD = Infinity;
for (let i = 0; i < routePts.length; i++) { let d = GPS.distance(ref.lat, ref.lon, routePts[i].lat, routePts[i].lon); if (d < refD) { refD = d; refIdx = i; } } // blockage: first route point within a nogo AHEAD, within a lookahead window
const LOOKAHEAD = 2500;
let blocked = null;
for (let i = curIdx; i >= 0 && i < routePts.length; i += step) {
if (Math.abs(cum[i] - cum[curIdx]) > LOOKAHEAD) break;
if (inNogo(routePts[i])) { blocked = i; break; }
}
if (blocked == null) return { noBlockage: true };
// rejoin target: first route point clear of every nogo AND >= margin past the blockage
const CLEAR_MARGIN = 300;
let ti = blocked;
while (ti >= 0 && ti < routePts.length) {
if (!inNogo(routePts[ti]) && Math.abs(cum[ti] - cum[blocked]) >= CLEAR_MARGIN) break;
ti += step;
}
if (ti < 0 || ti >= routePts.length) ti = step > 0 ? routePts.length - 1 : 0;
let helper = (bearingDeg != null && !isNaN(bearingDeg)) ? _offsetLatLon(cur.lat, cur.lon, bearingDeg, 40) : null; let helper = (bearingDeg != null && !isNaN(bearingDeg)) ? _offsetLatLon(cur.lat, cur.lon, bearingDeg, 40) : null;
let skip = 1500; let jump = Math.max(1, Math.round(routePts.length / 50));
for (let attempt = 0; attempt < 8; attempt++) { for (let attempt = 0; attempt < 6; attempt++) {
let want = cum[refIdx] + skip, ti = refIdx;
while (ti < routePts.length - 1 && cum[ti] < want) ti++;
let target = routePts[ti]; let target = routePts[ti];
let straight = GPS.distance(cur.lat, cur.lon, target.lat, target.lon); let straight = GPS.distance(cur.lat, cur.lon, target.lat, target.lon);
let wps = helper ? [[cur.lon, cur.lat], [helper.lon, helper.lat], [target.lon, target.lat]] : [[cur.lon, cur.lat], [target.lon, target.lat]]; let wps = helper ? [[cur.lon, cur.lat], [helper.lon, helper.lat], [target.lon, target.lat]] : [[cur.lon, cur.lat], [target.lon, target.lat]];
@ -3633,8 +3662,9 @@ async function rejoinReroute(cur, bearingDeg, routePts, nogos) {
if (rlen <= Math.max(500, straight * 3)) return { points: rp.map(p => ({ lat: p.lat, lon: p.lon })), target: { lat: target.lat, lon: target.lon } }; if (rlen <= Math.max(500, straight * 3)) return { points: rp.map(p => ({ lat: p.lat, lon: p.lon })), target: { lat: target.lat, lon: target.lon } };
} }
} catch (e) { /* try a further rejoin point */ } } catch (e) { /* try a further rejoin point */ }
if (ti >= routePts.length - 1) break; let next = ti + step * jump;
skip += 1500; if (next < 0 || next >= routePts.length) break;
ti = next;
} }
return null; return null;
} }
@ -5132,6 +5162,7 @@ async function renderRecordingInline(container) {
let ng = await getNogosForRouter(); let ng = await getNogosForRouter();
let bearing = (typeof smoothedHeading === "number") ? smoothedHeading : null; let bearing = (typeof smoothedHeading === "number") ? smoothedHeading : null;
let res = await rejoinReroute(cur, bearing, route.points, ng); let res = await rejoinReroute(cur, bearing, route.points, ng);
if (res && res.noBlockage) { showToast("Впереди затыка нет (нет nogo на маршруте)"); rerouteBtn.classList.remove("active"); return; }
if (!res) { showToast("Объезд не найден"); rerouteBtn.classList.remove("active"); return; } if (!res) { showToast("Объезд не найден"); rerouteBtn.classList.remove("active"); return; }
if (rerouteLayer) { map.removeLayer(rerouteLayer); rerouteLayer = null; } if (rerouteLayer) { map.removeLayer(rerouteLayer); rerouteLayer = null; }
let ll = res.points.map(p => [p.lat, p.lon]); let ll = res.points.map(p => [p.lat, p.lon]);