Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b6f3157
docs(spec): GPS track recorder design
msupino Jun 18, 2026
f245f3f
docs(plan): GPS track recorder implementation plan
msupino Jun 18, 2026
c4f79bc
feat(gps): track simplifier + gps.js module skeleton
msupino Jun 18, 2026
1c620bf
feat(gps): start/stop recording with accuracy + min-move filter
msupino Jun 18, 2026
bbc29e8
fix(gps): clamp haversine argument in _gpsMetres (match geo())
msupino Jun 18, 2026
47f3c05
feat(gps): auto-save flown track as a kind:gps library entry; iterati…
msupino Jun 18, 2026
64afb5a
fix(gps): neutralize wind/suppressions in saved track data; faster ov…
msupino Jun 18, 2026
f773017
feat(gps): live breadcrumb + shared own-ship renderer (sim + GPS)
msupino Jun 18, 2026
045f80c
refactor(gps): breadcrumb color/width as tune keys; round caps; tidy …
msupino Jun 18, 2026
dbdde35
feat(gps): View/Set record toggle, i18n strings, ui wiring
msupino Jun 18, 2026
561ff97
fix(gps): place record toggle inside the View/Set section (per design)
msupino Jun 18, 2026
6f00115
fix(gps): reset record button label on GPS error; readout styling + t…
msupino Jun 18, 2026
8da953f
docs(gps): document GPS track recorder + navaid.gpsTrack key
msupino Jun 18, 2026
909ea15
chore: drop stray og-preview.jpg change (unrelated to GPS feature)
msupino Jun 18, 2026
7b3a80a
fix(gps): don't mutate loaded route legs on save; drop unused track c…
msupino Jun 18, 2026
b1571e9
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 18, 2026
4e65725
feat(gps): Show my location toggle (live own-ship, no recording) — fo…
msupino Jun 18, 2026
012d0a5
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 18, 2026
59052c0
fix(gps): keep own-ship when stopping recording while live location i…
msupino Jun 18, 2026
679bbb0
Merge branch 'codex/gps-track-recorder' of https://github.com/msupino…
msupino Jun 18, 2026
b405b7f
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 18, 2026
bf13268
test(tuning): call drawOwnShip (renamed from drawSimAircraft) in canv…
msupino Jun 18, 2026
5d87bbf
Merge branch 'codex/gps-track-recorder' of https://github.com/msupino…
msupino Jun 18, 2026
c78f832
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 19, 2026
49a9ecd
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 19, 2026
ef80db1
test(toolbar): close desktop menubar dropdowns before inspector clicks
msupino Jun 19, 2026
fad3a68
Merge branch 'codex/gps-track-recorder' of https://github.com/msupino…
msupino Jun 19, 2026
2466bae
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 19, 2026
63291fc
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 20, 2026
0e29846
Merge remote-tracking branch 'origin/dev' into codex/gps-track-recorder
msupino Jun 21, 2026
bbf3cc7
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 21, 2026
807fe9c
perf(gps): squared-distance Douglas-Peucker (fix simplifyTrack timeout)
msupino Jun 21, 2026
b5ff0c3
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 22, 2026
2aa63e0
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 23, 2026
63f2e6c
Merge branch 'dev' into codex/gps-track-recorder
msupino Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .ai/navaid-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,12 @@ commit on `main`, `dev`, or an unrelated feature branch by mistake.
An "Include cumulative time" checkbox in the export modal (default on)
includes the cumulative-time kite layer in the exported PNG; disabling
it still renders leg markers but hides cumulative kites.
- **GPS track recorder:** the `📍 Record GPS track` toggle in the View/Set
toolbar section records the flown path from the device GPS (live own-ship
dot + breadcrumb trail on the map). On Stop it auto-saves a timestamped
`kind:'gps'` saved-route entry containing simplified waypoints plus the raw
`track[]` breadcrumb, carried by the existing Drive sync. Requires HTTPS;
backgrounded phones may leave gaps (no wake-lock).

## Persistence (`localStorage` + `sessionStorage`, all keyed `navaid.*`)

Expand Down Expand Up @@ -536,7 +542,10 @@ commit on `main`, `dev`, or an unrelated feature branch by mistake.
- `navaid.aircraft` — last-used aircraft profile JSON (fuel planner).
- `navaid.profileVS` — vertical-profile climb/descent rate input for timing and
TOC/TOD ramp distance.
- `navaid.routes` — saved-route library entries and tombstones.
- `navaid.routes` — saved-route library entries and tombstones. An entry
may carry `kind: 'gps'` plus a raw `track[]` (the recorded GPS
breadcrumb: `{lat,lng,t,alt?,acc?}`); loading applies the simplified
waypoint route, the raw track is retained for fidelity.
- `navaid.pageSize` — selected page frame size (`A3` / `A4`) or cleared.
- `navaid.pageOrient` — `'portrait'` / `'landscape'` for page export.
- `navaid.fpPos` — `{x, y}` of the dragged Flight Plan modal.
Expand Down
12 changes: 12 additions & 0 deletions docs/app/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ NavAid.tuningDefaults = {

driftLineColor: { value: '#141414', type: 'color', label: 'Drift line color' },
driftLineAlpha: { value: 0.6, min: 0, max: 1, step: 0.05, label: 'Drift line alpha' },
gpsBreadcrumbColor: { value: '#1e88e5', type: 'color', label: 'GPS breadcrumb color' },
gpsBreadcrumbWidthPx: { value: 3, min: 1, max: 10, step: 0.5, label: 'GPS breadcrumb width' },
overlayLabelHaloColor: { value: '#ffffff', type: 'color', label: 'Overlay label halo color' },
overlayLabelHaloAlpha: { value: 0.85, min: 0, max: 1, step: 0.05, label: 'Overlay label halo alpha' },

Expand Down Expand Up @@ -368,6 +370,7 @@ NavAid.tuningGroups = [
{ name: 'Behaviour', keys: ['undoLimit', 'rotDragPx', 'shareMaxWaypoints', 'commChangeSnapPx', 'originResnapArmPx'] },
{ name: 'Route line', keys: ['routeLineWidthPx', 'routeSelectedLineWidthPx'] },
{ name: 'Drift lines', keys: ['driftAngleDeg', 'driftLengthFactor', 'driftDashOnPx', 'driftDashOffPx', 'driftStrokeWidthPx', 'driftLineColor', 'driftLineAlpha'] },
{ name: 'GPS track', keys: ['gpsBreadcrumbColor', 'gpsBreadcrumbWidthPx'] },
{ name: 'Wind arrows', keys: ['windArrowColor', 'windArrowHaloColor', 'windTextHaloColor'] },
{ name: 'Default marker locations', keys: ['defaultLabelMarginPx', 'defaultKiteHalfWidthPx'] },
{ name: 'Leg kites', keys: ['legKiteFillColor', 'returnKiteFillColor', 'legKiteHeightPx', 'legKiteCellWidthPx', 'legKiteTriangleLenPx', 'legKiteBorderPx', 'legKiteDividerPx', 'legKiteHaloPx', 'legKiteTextPx', 'legKiteHeadingTextPx', 'legKiteHeadingAnchor'] },
Expand Down Expand Up @@ -951,6 +954,15 @@ window.S = Object.assign({
tbPrintTitle: 'Save the framed map + route as a PNG',
tbMagnifier: '🔍 Magnifying glass (M)',
tbMagnifierTitle: 'Magnifying glass (M) — zoomed view at cursor; +/− adjust loupe zoom while open',
tbGpsRecord: '📍 Record GPS track',
tbGpsRecordTitle: 'Record your flown track from the device GPS and save it',
tbGpsStop: '■ Stop & save',
tbGpsLive: '📍 Show my location',
tbGpsLiveTitle: 'Show your live position on the map (device GPS, no recording)',
tbGpsLiveStop: '■ Hide my location',
gpsUnsupported: 'GPS is not available in this browser.',
gpsNoTrack: 'No track recorded.',
gpsError: 'GPS error: ',
magSettingsTitle: 'Magnifier',
magZoomLabel: 'Zoom',
magZoomTitle: 'Magnifier zoom factor',
Expand Down
18 changes: 10 additions & 8 deletions docs/app/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ function legKiteAlongHalfPx(sc) {
}

// --- drawing ---------------------------------------------------------
// Draw the live simulator aircraft at its current position with heading.
// Top-down airplane silhouette: nose points up in local frame, rotated to
// (aircraft heading − map bearing) so it tracks correctly on a rotated map.
function drawSimAircraft() {
if (!simOn || !simAircraft) return;
const s = proj(simAircraft);
// Draws the own-ship symbol at pos {lat,lng} with true heading `hdg` (used for
// both the simulator aircraft and the live GPS position). Top-down airplane
// silhouette: nose up in local frame, rotated to (heading − map bearing) so it
// tracks correctly on a rotated map.
function drawOwnShip(pos, hdg) {
if (!pos) return;
const s = proj(pos);
const mapBearing = (typeof map !== 'undefined' && map.getBearing) ? map.getBearing() : 0;
const screenAngle = ((simAircraft.hdg || 0) - mapBearing) * Math.PI / 180;
const screenAngle = ((hdg || 0) - mapBearing) * Math.PI / 180;
const r = tune('liveAircraftRadiusPx');
octx.save();
octx.translate(s.x, s.y);
Expand Down Expand Up @@ -309,7 +310,8 @@ function draw() {
drawWaypoints();
drawNotes();
if (window.showProfile) drawProfileMarkers(); // TOC/TOD markers (#672)
drawSimAircraft();
if (typeof drawGpsTrack === 'function') drawGpsTrack(); // GPS breadcrumb + own-ship (recording or live location)
if (!gpsRecording && !gpsLiveOn && simOn && simAircraft) drawOwnShip(simAircraft, simAircraft.hdg); // sim own-ship
drawInfo();
drawPageFrame();
drawPlanCard(); // flight-plan card placed for PNG export (#378)
Expand Down
230 changes: 230 additions & 0 deletions docs/app/gps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// gps.js — device-GPS track recorder. Loaded after gdrive.js, before ui.js.
// Records the flown path, shows a live own-ship + breadcrumb, and on stop
// auto-saves a timestamped saved-route entry (simplified route + raw track).

var gpsRecording = false;
var gpsTrack = []; // [{lat,lng,t,alt,acc}]
var gpsWatchId = null;
var gpsOwn = null; // {lat,lng,hdg} last fix for own-ship rendering

const GPS_SIMPLIFY_EPS_DEG = 0.0003; // ~30 m
const GPS_MIN_MOVE_M = 10; // de-jitter: drop sub-10 m steps
const GPS_MAX_ACC_M = 100; // drop low-accuracy fixes
const GPS_MAX_POINTS = 50000;

// Douglas–Peucker simplification. eps in degrees. Endpoints always kept.
// Iterative (explicit-stack) implementation — overflow-safe for up to GPS_MAX_POINTS.
function simplifyTrack(points, eps) {
if (!Array.isArray(points) || points.length < 3) return (points || []).slice();
const n = points.length;
const keep = new Uint8Array(n);
keep[0] = keep[n - 1] = 1;
const eps2 = eps * eps;
// Flat lo/hi stack (no per-split tuple allocation). Squared perpendicular
// distances in the inner loop — no sqrt/hypot per point (it ran O(n²) times
// and dominated the worst-case zigzag). d > eps ⇔ d² > eps², so the kept set
// is identical to the hypot version.
const stack = [0, n - 1];
while (stack.length) {
const hi = stack.pop(), lo = stack.pop();
if (hi - lo < 2) continue;
const x1 = points[lo].lng, y1 = points[lo].lat;
const dx = points[hi].lng - x1, dy = points[hi].lat - y1;
const L2 = dx * dx + dy * dy;
let maxD2 = -1, idx = -1;
for (let i = lo + 1; i < hi; i++) {
const px = points[i].lng - x1, py = points[i].lat - y1;
let d2;
if (L2 === 0) {
d2 = px * px + py * py;
} else {
let t = (px * dx + py * dy) / L2;
t = t < 0 ? 0 : (t > 1 ? 1 : t);
const ex = px - t * dx, ey = py - t * dy;
d2 = ex * ex + ey * ey;
}
if (d2 > maxD2) { maxD2 = d2; idx = i; }
}
if (maxD2 > eps2) { keep[idx] = 1; stack.push(lo, idx, idx, hi); }
}
const out = [];
for (let i = 0; i < n; i++) if (keep[i]) out.push(points[i]);
return out;
}

// Great-circle distance in metres between two {lat,lng}.
function _gpsMetres(a, b) {
const R = 6371000, rad = x => x * Math.PI / 180;
const dLa = rad(b.lat - a.lat), dLo = rad(b.lng - a.lng);
const h = Math.sin(dLa / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLo / 2) ** 2;
return 2 * R * Math.asin(Math.sqrt(Math.min(1, h)));
}

var gpsLiveOn = false;
var gpsLiveWatchId = null;
var _gpsLivePrev = null;

function onLivePosition(pos) {
if (!gpsLiveOn || !pos || !pos.coords) return;
const c = pos.coords;
if (c.accuracy != null && c.accuracy > GPS_MAX_ACC_M) return;
const p = { lat: r5(c.latitude), lng: r5(c.longitude) };
const hdg = (c.heading != null && !isNaN(c.heading)) ? c.heading
: (_gpsLivePrev ? geo(_gpsLivePrev, p).brg : 0);
_gpsLivePrev = p;
gpsOwn = { lat: p.lat, lng: p.lng, hdg };
scheduleDraw();
if (gpsFollow && typeof map !== 'undefined') map.setView([p.lat, p.lng], map.getZoom());
}

function startLiveLocation() {
if (gpsLiveOn) return;
if (!navigator.geolocation) { alert(S.gpsUnsupported || 'GPS is not available in this browser.'); return; }
gpsLiveOn = true; _gpsLivePrev = null;
gpsLiveWatchId = navigator.geolocation.watchPosition(onLivePosition, onGpsError, { enableHighAccuracy: true });
scheduleDraw();
}

function stopLiveLocation() {
if (gpsLiveWatchId != null && navigator.geolocation) navigator.geolocation.clearWatch(gpsLiveWatchId);
gpsLiveWatchId = null;
gpsLiveOn = false;
_gpsLivePrev = null;
if (!gpsRecording) gpsOwn = null; // keep own-ship if a recording is still running
scheduleDraw();
}

var gpsFollow = true; // recenter on own-ship while recording
var gpsStartT = 0;

// Live readout next to the toolbar button (points · elapsed). No-op if absent.
function gpsUpdateReadout() {
const el = document.getElementById('gps-readout');
if (!el) return;
if (!gpsRecording) { el.textContent = ''; return; }
const secs = gpsStartT ? Math.round((Date.now() - gpsStartT) / 1000) : 0;
const mm = String(Math.floor(secs / 60)).padStart(2, '0');
const ss = String(secs % 60).padStart(2, '0');
el.textContent = gpsTrack.length + ' pts · ' + mm + ':' + ss;
}

function onGpsPosition(pos) {
if (!gpsRecording || !pos || !pos.coords) return;
const c = pos.coords;
if (c.accuracy != null && c.accuracy > GPS_MAX_ACC_M) return; // too imprecise
const pt = { lat: r5(c.latitude), lng: r5(c.longitude), t: pos.timestamp || Date.now(),
alt: c.altitude != null ? c.altitude : null,
acc: c.accuracy != null ? c.accuracy : null };
const prev = gpsTrack[gpsTrack.length - 1];
if (prev && _gpsMetres(prev, pt) < GPS_MIN_MOVE_M) return; // de-jitter
if (gpsTrack.length >= GPS_MAX_POINTS) return;
gpsTrack.push(pt);
// heading: device value when moving, else bearing from the previous point.
let hdg = (c.heading != null && !isNaN(c.heading)) ? c.heading
: (prev ? geo(prev, pt).brg : 0);
gpsOwn = { lat: pt.lat, lng: pt.lng, hdg };
gpsUpdateReadout();
scheduleDraw();
if (gpsFollow && typeof map !== 'undefined') map.setView([pt.lat, pt.lng], map.getZoom());
}

function onGpsError(err) {
stopGpsRecording();
stopLiveLocation();
const rb = document.getElementById('gps-record'); if (rb) rb.textContent = S.tbGpsRecord;
const lb = document.getElementById('gps-live'); if (lb) { lb.textContent = S.tbGpsLive; lb.setAttribute('aria-pressed', 'false'); }
alert((S.gpsError || 'GPS error: ') + (err && err.message ? err.message : ''));
}

function startGpsRecording() {
if (gpsRecording) return;
if (!navigator.geolocation) { alert(S.gpsUnsupported || 'GPS is not available in this browser.'); return; }
gpsRecording = true;
gpsTrack = [];
if (!gpsLiveOn) gpsOwn = null;
gpsStartT = Date.now();
gpsWatchId = navigator.geolocation.watchPosition(onGpsPosition, onGpsError, { enableHighAccuracy: true });
gpsUpdateReadout();
scheduleDraw();
}

function gpsTrackName() {
const d = new Date();
const p = n => String(n).padStart(2, '0');
return 'Track ' + d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate())
+ ' ' + p(d.getHours()) + ':' + p(d.getMinutes());
}

// Build a validateRoute-passing route `data` from simplified points by reusing
// the canonical serializer with a guarded temporary state swap.
// Also neutralizes state.commChangeSuppressions and state.wind so the saved GPS
// entry is not polluted with the user's current comm-change suppressions or wind.
function gpsRouteDataFromPoints(points) {
const saved = {
waypoints: state.waypoints, legs: state.legs, notes: state.notes,
commChangeSuppressions: state.commChangeSuppressions,
wind: state.wind,
};
try {
state.waypoints = points.map(p => ({ lat: r5(p.lat), lng: r5(p.lng), name: '' }));
state.legs = [];
state.notes = [];
state.commChangeSuppressions = [];
state.wind = { dir: 270, speed: 0 }; // calm — encodeWind omits speed:0
syncLegs();
return serializeRoute();
} finally {
state.waypoints = saved.waypoints; state.legs = saved.legs; state.notes = saved.notes;
state.commChangeSuppressions = saved.commChangeSuppressions;
state.wind = saved.wind;
// Do NOT call syncLegs() here — saved.legs already has the correct length
// for saved.waypoints, and syncLegs() would call applyLegAltitudesToRoute()
// which overwrites any _legAltitudeAuto leg values (e.g. custom altitudes
// the user set) with NaN when the waypoint names don't match the dataset.
}
}

// Stop recording AND save. Returns the new library entry, or null.
function stopGpsRecordingAndSave() {
const raw = gpsTrack.slice();
stopGpsRecording();
if (raw.length < 2) { alert(S.gpsNoTrack || 'No track recorded.'); return null; }
const simp = simplifyTrack(raw.map(p => ({ lat: p.lat, lng: p.lng })), GPS_SIMPLIFY_EPS_DEG);
const data = gpsRouteDataFromPoints(simp);
const entry = {
id: routeLibraryId(),
name: gpsTrackName(),
savedAt: new Date().toISOString(),
kind: 'gps',
data,
track: raw.map(p => ({ lat: r5(p.lat), lng: r5(p.lng), t: p.t,
...(p.alt != null ? { alt: Math.round(p.alt) } : {}),
...(p.acc != null ? { acc: Math.round(p.acc) } : {}) })),
};
const list = loadRouteLibrary();
list.unshift(entry);
return persistRouteLibrary(list) ? entry : null;
}

// Breadcrumb of the in-progress recording, drawn on the overlay.
function drawGpsTrack() {
if (!gpsRecording && !gpsLiveOn) return;
if (gpsRecording && gpsTrack.length > 1) {
octx.save(); octx.beginPath();
for (let i = 0; i < gpsTrack.length; i++) { const s = proj(gpsTrack[i]); if (i === 0) octx.moveTo(s.x, s.y); else octx.lineTo(s.x, s.y); }
octx.lineWidth = tune('gpsBreadcrumbWidthPx'); octx.strokeStyle = tune('gpsBreadcrumbColor');
octx.lineCap = 'round'; octx.lineJoin = 'round'; octx.stroke(); octx.restore();
if (typeof window !== 'undefined') window.__gpsBreadcrumbDrawn = (window.__gpsBreadcrumbDrawn || 0) + 1;
}
if (gpsOwn && (gpsRecording || gpsLiveOn)) drawOwnShip(gpsOwn, gpsOwn.hdg);
}

// Stop watching without saving. (Save handled in a later task.)
function stopGpsRecording() {
if (gpsWatchId != null && navigator.geolocation) navigator.geolocation.clearWatch(gpsWatchId);
gpsWatchId = null;
gpsRecording = false;
if (!gpsLiveOn) gpsOwn = null;
gpsUpdateReadout();
scheduleDraw();
}
1 change: 1 addition & 0 deletions docs/app/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ html[dir="rtl"] .leaflet-control.zulu-clock {
transform: translateY(46px);
}
.coord-readout.show { display: block; }
.gps-readout { display: block; font-size: 11px; opacity: 0.75; margin-top: 2px; }
.zoom-readout {
display: block;
padding: 2px 4px;
Expand Down
27 changes: 27 additions & 0 deletions docs/app/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,33 @@ document.getElementById('plan').onclick = showFlightPlan;
document.getElementById('freq-table').onclick = showFreqTableModal;
document.getElementById('alt-pairs').onclick = showAltitudePairsModal;
document.getElementById('charts').onclick = showChartsModal;
const gpsBtn = document.getElementById('gps-record');
if (gpsBtn) {
if (!navigator.geolocation) { gpsBtn.disabled = true; }
gpsBtn.addEventListener('click', () => {
if (gpsRecording) {
stopGpsRecordingAndSave();
gpsBtn.textContent = S.tbGpsRecord;
if (typeof window.refreshRouteLibrary === 'function') window.refreshRouteLibrary();
} else {
startGpsRecording();
if (gpsRecording) gpsBtn.textContent = S.tbGpsStop;
}
});
}
const liveBtn = document.getElementById('gps-live');
if (liveBtn) {
if (!navigator.geolocation) { liveBtn.disabled = true; }
liveBtn.addEventListener('click', () => {
if (gpsLiveOn) {
stopLiveLocation();
liveBtn.textContent = S.tbGpsLive; liveBtn.setAttribute('aria-pressed', 'false');
} else {
startLiveLocation();
if (gpsLiveOn) { liveBtn.textContent = S.tbGpsLiveStop; liveBtn.setAttribute('aria-pressed', 'true'); }
}
});
}
const RETURN_KEY = 'navaid.showReturn';
const MIDLEG_KEY = 'navaid.showMidLeg';
const CUMTIME_KEY = 'navaid.showCumTime';
Expand Down
9 changes: 9 additions & 0 deletions docs/i18n/he/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -537,4 +537,13 @@ window.S = {
magZoomLabel: 'תקריב',
magZoomTitle: 'גורם התקריב של הזכוכית המגדלת',
magLoading: 'משכלל…',
tbGpsRecord: '📍 הקלטת מסלול GPS',
tbGpsRecordTitle: 'הקלטת המסלול בפועל מ-GPS המכשיר ושמירתו',
tbGpsStop: '■ עצור ושמור',
tbGpsLive: '📍 הצג מיקום',
tbGpsLiveTitle: 'הצגת המיקום החי שלך על המפה (GPS, ללא הקלטה)',
tbGpsLiveStop: '■ הסתר מיקום',
gpsUnsupported: 'GPS אינו זמין בדפדפן זה.',
gpsNoTrack: 'לא הוקלט מסלול.',
gpsError: 'שגיאת GPS: ',
};
4 changes: 4 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ <h2>Israeli airfields and waypoints</h2>
<input type="checkbox" id="force-snap-cb"> <span data-i18n="tbForceSnap"></span>
</label>
<button id="tool-magnifier" type="button" data-i18n="tbMagnifier" data-i18n-title="tbMagnifierTitle" style="width:100%;margin-top:4px;padding:4px 8px;font-size:12px"></button>
<button id="gps-record" type="button" data-i18n="tbGpsRecord" data-i18n-title="tbGpsRecordTitle" style="width:100%;margin-top:4px;padding:4px 8px;font-size:12px"></button>
<button id="gps-live" type="button" style="width:100%;margin-top:4px;padding:4px 8px;font-size:12px" data-i18n="tbGpsLive" data-i18n-title="tbGpsLiveTitle" aria-pressed="false"></button>
<span id="gps-readout" class="gps-readout"></span>
</div>
</div>

Expand Down Expand Up @@ -635,6 +638,7 @@ <h3 data-i18n="tbSecSim">Simulator</h3>
'app/io.js' + v,
'app/alt-pair-directions.js' + v,
'app/gdrive.js' + v,
'app/gps.js' + v,
'app/ui.js' + v
];
for (var i = 0; i < srcs.length; i++) {
Expand Down
Loading
Loading