mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
feat: Add GPS auto-connect for AIS dashboard via gpsd
Automatically connects to gpsd on page load if available. Updates observer location in real-time with GPS indicator in top bar. Includes auto-reconnect on visibility change. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1201,3 +1201,33 @@ body {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* GPS Indicator */
|
||||
.gps-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
border: 1px solid #22c55e;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #22c55e;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.gps-indicator .gps-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #22c55e;
|
||||
border-radius: 50%;
|
||||
animation: gps-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gps-pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.5; transform: scale(0.8); }
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
<span id="trackingStatus">STANDBY</span>
|
||||
</div>
|
||||
<div class="strip-time" id="utcTime">--:--:-- UTC</div>
|
||||
<span id="gpsIndicator" class="gps-indicator" style="display: none;" title="GPS connected via gpsd"><span class="gps-dot"></span> GPS</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -217,6 +218,11 @@
|
||||
let rangeRingsLayer = null;
|
||||
let observerMarker = null;
|
||||
|
||||
// GPS state
|
||||
let gpsConnected = false;
|
||||
let gpsEventSource = null;
|
||||
let gpsReconnectTimeout = null;
|
||||
|
||||
// Statistics
|
||||
let stats = {
|
||||
totalVesselsSeen: new Set(),
|
||||
@@ -948,6 +954,106 @@
|
||||
document.getElementById('utcTime').textContent = utc + ' UTC';
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// GPS FUNCTIONS (gpsd auto-connect)
|
||||
// ============================================
|
||||
async function autoConnectGps() {
|
||||
try {
|
||||
const response = await fetch('/gps/auto-connect', { method: 'POST' });
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'connected') {
|
||||
gpsConnected = true;
|
||||
startGpsStream();
|
||||
showGpsIndicator(true);
|
||||
console.log('GPS: Auto-connected to gpsd');
|
||||
if (data.position) {
|
||||
updateLocationFromGps(data.position);
|
||||
}
|
||||
} else {
|
||||
console.log('GPS: gpsd not available -', data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('GPS: Auto-connect failed -', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function startGpsStream() {
|
||||
if (gpsEventSource) {
|
||||
gpsEventSource.close();
|
||||
}
|
||||
if (gpsReconnectTimeout) {
|
||||
clearTimeout(gpsReconnectTimeout);
|
||||
gpsReconnectTimeout = null;
|
||||
}
|
||||
|
||||
gpsEventSource = new EventSource('/gps/stream');
|
||||
gpsEventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'position' && data.latitude && data.longitude) {
|
||||
updateLocationFromGps(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('GPS parse error:', e);
|
||||
}
|
||||
};
|
||||
gpsEventSource.onerror = (e) => {
|
||||
// Don't log every error - connection suspends are normal
|
||||
if (gpsEventSource) {
|
||||
gpsEventSource.close();
|
||||
gpsEventSource = null;
|
||||
}
|
||||
// Auto-reconnect after 5 seconds if still connected
|
||||
if (gpsConnected && !gpsReconnectTimeout) {
|
||||
gpsReconnectTimeout = setTimeout(() => {
|
||||
gpsReconnectTimeout = null;
|
||||
if (gpsConnected) {
|
||||
startGpsStream();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Reconnect GPS stream when tab becomes visible
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden && gpsConnected && !gpsEventSource) {
|
||||
startGpsStream();
|
||||
}
|
||||
});
|
||||
|
||||
function updateLocationFromGps(position) {
|
||||
observerLocation.lat = position.latitude;
|
||||
observerLocation.lon = position.longitude;
|
||||
document.getElementById('obsLat').value = position.latitude.toFixed(4);
|
||||
document.getElementById('obsLon').value = position.longitude.toFixed(4);
|
||||
|
||||
// Update observer marker position
|
||||
if (observerMarker) {
|
||||
observerMarker.setLatLng([position.latitude, position.longitude]);
|
||||
}
|
||||
|
||||
// Center map on GPS location (on first fix)
|
||||
if (vesselMap && !vesselMap._gpsInitialized) {
|
||||
vesselMap.setView([position.latitude, position.longitude], vesselMap.getZoom());
|
||||
vesselMap._gpsInitialized = true;
|
||||
}
|
||||
|
||||
// Redraw range rings at new position
|
||||
drawRangeRings();
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('ais_observerLocation', JSON.stringify(observerLocation));
|
||||
}
|
||||
|
||||
function showGpsIndicator(show) {
|
||||
const indicator = document.getElementById('gpsIndicator');
|
||||
if (indicator) {
|
||||
indicator.style.display = show ? 'inline-flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Session timer functions
|
||||
function startSessionTimer() {
|
||||
if (!stats.sessionStart) {
|
||||
@@ -1344,7 +1450,11 @@
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', initMap);
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initMap();
|
||||
// Auto-connect to gpsd if available
|
||||
autoConnectGps();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Agent styles -->
|
||||
|
||||
Reference in New Issue
Block a user