Parallele Arbeit (auf Staging mitgetestet): KI-Vision-Model (VISION_MODEL in ki.py/routes, im KI-Status sichtbar), Breed-Scraper-Anpassungen (breed_enricher/breed_evaluator, evaluate_enrichment mit user_id), Karten-/Routen-Änderungen (map.js, routes.js), kleinere UI-Anpassungen (admin.js, components.css), docker-compose, MARKETING, nav-loop-Test. Version-Bump auf 1292 (VERSION, sw.js, app.js, index.html, landing.html).
98 lines
4.7 KiB
JavaScript
98 lines
4.7 KiB
JavaScript
// Navi-Erst-Fix bei RUNDEN: der Startindex darf nicht ans Track-Ende springen.
|
|
//
|
|
// Spiegelt die _closestIdx-Erst-Fix-Logik aus js/pages/routes.js (_startNav). An einem
|
|
// Start/Ende-Knoten einer Runde ist der ENDPUNKT oft ein paar Meter näher als der
|
|
// Startpunkt; die alte globale Suche sprang dann sofort ans Track-Ende → 100 % / 0 km ab
|
|
// Sekunde 1 (Angie, Deining-Runde 09.06.2026). Bei Änderung BEIDE Stellen anpassen.
|
|
//
|
|
// Hinweis: bewusst eine Nachbildung — die echte Funktion ist eine Closure in _startNav
|
|
// und nicht exportierbar, ohne routes.js umzubauen.
|
|
|
|
const _haversineKm = (lat1, lon1, lat2, lon2) => {
|
|
const R = 6371, dLat = (lat2 - lat1) * Math.PI / 180, dLon = (lon2 - lon1) * Math.PI / 180;
|
|
const a = Math.sin(dLat / 2) ** 2 +
|
|
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) ** 2;
|
|
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
};
|
|
|
|
// Erst-Fix-Index für gegebenen track + Userposition (1:1 aus routes.js).
|
|
function firstFixIdx(track, lat, lon) {
|
|
const search = (from, to) => {
|
|
let best = from, bestD = Infinity;
|
|
for (let i = from; i <= to; i++) {
|
|
const d = _haversineKm(lat, lon, track[i].lat, track[i].lon);
|
|
if (d < bestD) { bestD = d; best = i; }
|
|
}
|
|
return { best, bestD };
|
|
};
|
|
const isLoop = track.length > 2 &&
|
|
_haversineKm(track[0].lat, track[0].lon,
|
|
track[track.length - 1].lat, track[track.length - 1].lon) < 0.06;
|
|
const g = search(0, track.length - 1);
|
|
if (isLoop) {
|
|
const win = Math.min(track.length - 1, Math.max(30, Math.floor(track.length * 0.15)));
|
|
const s = search(0, win);
|
|
return { idx: s.bestD < 0.15 ? s.best : g.best, isLoop };
|
|
}
|
|
const s = search(0, Math.min(track.length - 1, 30));
|
|
return { idx: (s.bestD - g.bestD) * 1000 < 25 ? s.best : g.best, isLoop };
|
|
}
|
|
|
|
// Die ALTE Logik (vor dem Fix) — nur zum Beweis, dass der Fix wirklich etwas ändert.
|
|
function firstFixIdxOld(track, lat, lon) {
|
|
const search = (from, to) => {
|
|
let best = from, bestD = Infinity;
|
|
for (let i = from; i <= to; i++) {
|
|
const d = _haversineKm(lat, lon, track[i].lat, track[i].lon);
|
|
if (d < bestD) { bestD = d; best = i; }
|
|
}
|
|
return { best, bestD };
|
|
};
|
|
const g = search(0, track.length - 1);
|
|
const s = search(0, Math.min(track.length - 1, 30));
|
|
return (s.bestD - g.bestD) * 1000 < 25 ? s.best : g.best;
|
|
}
|
|
|
|
// --- Synthetische Deining-artige Runde -------------------------------------
|
|
const C = { lat: 48.07, lon: 11.50 };
|
|
const mLat = m => m / 111320;
|
|
const mLon = (m, lat) => m / (111320 * Math.cos(lat * Math.PI / 180));
|
|
// Punkt auf einem Kreis: Winkel von Nord, im Uhrzeigersinn.
|
|
const onCircle = (deg, r) => {
|
|
const rad = deg * Math.PI / 180;
|
|
return { lat: C.lat + mLat(r * Math.cos(rad)), lon: C.lon + mLon(r * Math.sin(rad), C.lat) };
|
|
};
|
|
|
|
const N = 40, R = 80; // 40 Punkte auf 80-m-Kreis, lange Runde von 0°→329°
|
|
const track = [];
|
|
for (let i = 0; i < N; i++) track.push(onCircle(i / (N - 1) * 329, R));
|
|
// User steht 3 m außerhalb des ENDpunkts (329°) → näher am Ende als am Start.
|
|
const user = onCircle(329, R + 3);
|
|
|
|
const startEndM = _haversineKm(track[0].lat, track[0].lon,
|
|
track[N - 1].lat, track[N - 1].lon) * 1000;
|
|
const dStart = _haversineKm(user.lat, user.lon, track[0].lat, track[0].lon) * 1000;
|
|
const dEnd = _haversineKm(user.lat, user.lon, track[N - 1].lat, track[N - 1].lon) * 1000;
|
|
console.log(`Runde: Start↔Ende ${startEndM.toFixed(0)} m | User→Start ${dStart.toFixed(0)} m, User→Ende ${dEnd.toFixed(0)} m`);
|
|
|
|
// 1. Loop wird erkannt (Start ≈ Ende < 60 m)
|
|
const res = firstFixIdx(track, user.lat, user.lon);
|
|
if (!res.isLoop) throw new Error('Runde nicht als Loop erkannt');
|
|
|
|
// 2. Erst-Fix landet im STARTbereich, NICHT am Track-Ende
|
|
console.log('Erst-Fix-Index:', res.idx, '(von', N - 1 + ')');
|
|
if (res.idx > Math.floor(N * 0.15)) throw new Error(`Erst-Fix sprang weg vom Start (idx ${res.idx})`);
|
|
|
|
// 3. Beweis: die alte Logik wäre hier ans Ende gesprungen (100 %)
|
|
const old = firstFixIdxOld(track, user.lat, user.lon);
|
|
console.log('Alte Logik-Index:', old);
|
|
if (old !== N - 1) throw new Error('Erwartet: alte Logik springt ans Ende — Testfall trifft den Bug nicht mehr');
|
|
|
|
// 4. Punkt-zu-Punkt-Route (kein Loop): User am Start → 0 %, am Ende → bleibt sinnvoll
|
|
const ptp = [];
|
|
for (let i = 0; i < N; i++) ptp.push({ lat: C.lat + mLat(i * 25), lon: C.lon }); // 25-m-Schritte nach Norden
|
|
const ptpRes = firstFixIdx(ptp, ptp[0].lat, ptp[0].lon);
|
|
if (ptpRes.isLoop) throw new Error('Gerade Route fälschlich als Loop erkannt');
|
|
if (ptpRes.idx !== 0) throw new Error(`Punkt-zu-Punkt am Start sollte idx 0 sein, war ${ptpRes.idx}`);
|
|
|
|
console.log('\nALLE NAV-LOOP-TESTS BESTANDEN');
|