fix(gps): upgrade to real-time continuous GPS tracking for walking and driving (v15)

This commit is contained in:
2026-09-12 10:08:58 -07:00
parent 31fdb8b7be
commit adeaab05b5
4 changed files with 343 additions and 161 deletions
+17
View File
@@ -555,6 +555,23 @@ body {
color: #ffffff; color: #ffffff;
} }
#btn-locate-me.active {
background: rgba(6, 182, 212, 0.28);
border-color: var(--accent-cyan);
box-shadow: 0 0 12px rgba(6, 182, 212, 0.45);
}
#btn-locate-me.active svg {
animation: pulseGps 1.8s ease-in-out infinite;
color: #38bdf8;
}
@keyframes pulseGps {
0% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(6, 182, 212, 0)); }
50% { transform: scale(1.18); filter: drop-shadow(0 0 6px rgba(6, 182, 212, 0.9)); }
100% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(6, 182, 212, 0)); }
}
.map-search-box { .map-search-box {
display: flex; display: flex;
gap: 6px; gap: 6px;
+14 -14
View File
@@ -10,11 +10,11 @@
<link rel="canonical" href="https://flockradar.org/"> <link rel="canonical" href="https://flockradar.org/">
<!-- Favicon Suite & App Icons --> <!-- Favicon Suite & App Icons -->
<link rel="icon" type="image/svg+xml" href="favicon.svg?v=14"> <link rel="icon" type="image/svg+xml" href="favicon.svg?v=15">
<link rel="alternate icon" href="favicon.ico?v=14"> <link rel="alternate icon" href="favicon.ico?v=15">
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png?v=14"> <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png?v=15">
<link rel="icon" type="image/png" sizes="192x192" href="icon-192.png?v=14"> <link rel="icon" type="image/png" sizes="192x192" href="icon-192.png?v=15">
<link rel="icon" type="image/png" sizes="512x512" href="icon-512.png?v=14"> <link rel="icon" type="image/png" sizes="512x512" href="icon-512.png?v=15">
<meta name="theme-color" content="#07090e"> <meta name="theme-color" content="#07090e">
<meta name="msapplication-TileColor" content="#07090e"> <meta name="msapplication-TileColor" content="#07090e">
@@ -24,8 +24,8 @@
<meta property="og:site_name" content="FlockRadar"> <meta property="og:site_name" content="FlockRadar">
<meta property="og:title" content="FlockRadar | Live ALPR Camera Awareness & National Directory"> <meta property="og:title" content="FlockRadar | Live ALPR Camera Awareness & National Directory">
<meta property="og:description" content="Drive with real-time awareness of Flock camera surveillance zones. Instant proximity HUD alerts, audio warnings, and 1,439 official municipal transparency portals mapped."> <meta property="og:description" content="Drive with real-time awareness of Flock camera surveillance zones. Instant proximity HUD alerts, audio warnings, and 1,439 official municipal transparency portals mapped.">
<meta property="og:image" content="https://flockradar.org/og-image.png?v=14"> <meta property="og:image" content="https://flockradar.org/og-image.png?v=15">
<meta property="og:image:secure_url" content="https://flockradar.org/og-image.png?v=14"> <meta property="og:image:secure_url" content="https://flockradar.org/og-image.png?v=15">
<meta property="og:image:type" content="image/png"> <meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200"> <meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630"> <meta property="og:image:height" content="630">
@@ -36,7 +36,7 @@
<meta name="twitter:url" content="https://flockradar.org/"> <meta name="twitter:url" content="https://flockradar.org/">
<meta name="twitter:title" content="FlockRadar | Live ALPR Camera Awareness & National Directory"> <meta name="twitter:title" content="FlockRadar | Live ALPR Camera Awareness & National Directory">
<meta name="twitter:description" content="Drive with real-time awareness of Flock camera surveillance zones. Instant proximity HUD alerts, audio warnings, and 1,439 official municipal portals mapped."> <meta name="twitter:description" content="Drive with real-time awareness of Flock camera surveillance zones. Instant proximity HUD alerts, audio warnings, and 1,439 official municipal portals mapped.">
<meta name="twitter:image" content="https://flockradar.org/og-image.png?v=14"> <meta name="twitter:image" content="https://flockradar.org/og-image.png?v=15">
<meta name="twitter:image:alt" content="FlockRadar - Live ALPR Driving Radar & National Transparency Directory"> <meta name="twitter:image:alt" content="FlockRadar - Live ALPR Driving Radar & National Transparency Directory">
<!-- Google Fonts --> <!-- Google Fonts -->
@@ -54,7 +54,7 @@
<meta name="apple-mobile-web-app-title" content="FlockRadar"> <meta name="apple-mobile-web-app-title" content="FlockRadar">
<!-- Main Stylesheet --> <!-- Main Stylesheet -->
<link rel="stylesheet" href="css/styles.css?v=14"> <link rel="stylesheet" href="css/styles.css?v=15">
</head> </head>
<body> <body>
@@ -1156,11 +1156,11 @@
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<!-- Data & Application Logic with Cache Busting --> <!-- Data & Application Logic with Cache Busting -->
<script src="data/portals.js?v=14"></script> <script src="data/portals.js?v=15"></script>
<script src="js/icons.js?v=14"></script> <script src="js/icons.js?v=15"></script>
<script src="js/auth.js?v=14"></script> <script src="js/auth.js?v=15"></script>
<script src="js/radar.js?v=14"></script> <script src="js/radar.js?v=15"></script>
<script src="js/app.js?v=14"></script> <script src="js/app.js?v=15"></script>
<!-- Service Worker Instant Cache Invalidation --> <!-- Service Worker Instant Cache Invalidation -->
<script> <script>
+295 -130
View File
@@ -683,7 +683,11 @@
function recenterOnCar() { function recenterOnCar() {
isFollowCarLocked = true; isFollowCarLocked = true;
updateFollowCarUI(); updateFollowCarUI();
map.setView([currentPosition.lat, currentPosition.lng], Math.max(map.getZoom(), 15), { animate: true }); if (!isContinuousTracking && navigator.geolocation) {
startContinuousGpsTracking({ forceRecenter: true, flyTo: true });
} else {
map.panTo([currentPosition.lat, currentPosition.lng], { animate: true, duration: 0.5 });
}
} }
function updateFollowCarUI() { function updateFollowCarUI() {
@@ -829,74 +833,268 @@
alert('Test Flock camera placed 400 feet ahead of you!\n\nWalk or drive forward into the red circle zone to trigger the live warning chime and voice alert.'); alert('Test Flock camera placed 400 feet ahead of you!\n\nWalk or drive forward into the red circle zone to trigger the live warning chime and voice alert.');
} }
// --- LIVE GPS CONTINUOUS TRACKING --- // --- REAL-TIME CONTINUOUS GPS TRACKING ENGINE ---
let liveGpsWatchId = null; let liveGpsWatchId = null;
let isContinuousTracking = false;
let lastGpsPosition = null; // { lat, lng, timestamp, accuracy, speed, heading }
let lastGpsHeading = 0; // Smooth heading in degrees (0-360)
function toggleLiveGps() { function updateTrackingUI(isActive) {
if (el.btnLocateMe) {
el.btnLocateMe.classList.toggle('active', isActive);
if (isActive) {
const chips = document.querySelectorAll('.location-quick-chips .loc-chip');
chips.forEach(c => {
if (c !== el.btnLocateMe) c.classList.remove('active');
});
}
}
if (el.btnLiveGps) {
el.btnLiveGps.classList.toggle('active', isActive);
const radioIcon = window.FlockIcons ? FlockIcons.radio() : '';
const stopIcon = window.FlockIcons ? FlockIcons.stopCircle() : '';
el.btnLiveGps.innerHTML = isActive
? `${stopIcon}<span>Stop Live GPS</span>`
: `${radioIcon}<span>Live GPS Drive</span>`;
}
}
function handleGpsPositionUpdate(pos, options = {}) {
if (!pos || !pos.coords) return;
const curLat = pos.coords.latitude;
const curLng = pos.coords.longitude;
const accuracy = Math.round(pos.coords.accuracy || 10);
const timestamp = pos.timestamp || Date.now();
// Initial fix
if (!lastGpsPosition) {
currentPosition.lat = curLat;
currentPosition.lng = curLng;
currentSpeed = (pos.coords.speed !== null && !isNaN(pos.coords.speed) && pos.coords.speed > 0)
? Math.round(pos.coords.speed * 2.23694)
: 0;
lastGpsHeading = (pos.coords.heading !== null && !isNaN(pos.coords.heading) && pos.coords.heading >= 0)
? pos.coords.heading
: 0;
if (userMarker) userMarker.setLatLng([curLat, curLng]);
const carElem = document.getElementById('user-vehicle-marker');
if (carElem && lastGpsHeading) {
carElem.style.transform = `rotate(${Math.round(lastGpsHeading)}deg)`;
}
if (options.flyTo) {
map.flyTo([curLat, curLng], 16, { duration: 1.0 });
} else if (isFollowCarLocked) {
map.panTo([curLat, curLng], { animate: true, duration: 0.5 });
}
el.hudSpeedVal.textContent = currentSpeed;
updateSpottedBadge(`Live GPS (±${accuracy}m) • Locked`);
updateHudLocationStatus('Live GPS Active');
checkProximityToCameras();
lastGpsPosition = {
lat: curLat,
lng: curLng,
timestamp,
accuracy,
speed: currentSpeed,
heading: lastGpsHeading
};
return;
}
// Subsequent updates: calculate delta distance & time
const distMeters = getHaversineDistanceMeters(lastGpsPosition.lat, lastGpsPosition.lng, curLat, curLng);
const timeDeltaMs = timestamp - lastGpsPosition.timestamp;
const timeDeltaSec = Math.max(0.1, timeDeltaMs / 1000);
// Skip redundant identical frames if timeDeltaMs < 200ms and dist < 0.4m
if (timeDeltaMs < 200 && distMeters < 0.4) return;
// --- JITTER FILTER ---
// If movement is under 1.5m (GPS drift while stationary or stopped at traffic signal)
if (distMeters < 1.5) {
currentSpeed = 0;
el.hudSpeedVal.textContent = '0';
updateSpottedBadge(`Live GPS (±${accuracy}m) • Stopped`);
updateHudLocationStatus('Stopped • Monitoring Cameras');
checkProximityToCameras();
lastGpsPosition.timestamp = timestamp;
lastGpsPosition.accuracy = accuracy;
return;
}
// --- ACTIVE MOVEMENT DETECTED (Walking or Driving) ---
let calculatedSpeedMph = 0;
if (pos.coords.speed !== null && !isNaN(pos.coords.speed) && pos.coords.speed >= 0) {
calculatedSpeedMph = pos.coords.speed * 2.23694;
} else if (timeDeltaSec > 0.2 && timeDeltaSec < 45) {
// 1 m/s = 2.23694 mph
calculatedSpeedMph = (distMeters / timeDeltaSec) * 2.23694;
}
// Ignore cellular triangulation jumps (> 120 mph when accuracy is low)
if (calculatedSpeedMph > 120 && accuracy > 30) {
console.warn('Ignoring cellular triangulation jump:', distMeters, 'm');
return;
}
// Smooth speed: responsive yet stable
currentSpeed = Math.round(0.7 * calculatedSpeedMph + 0.3 * currentSpeed);
if (currentSpeed < 1) currentSpeed = 0;
el.hudSpeedVal.textContent = currentSpeed;
// Calculate Heading / Compass Bearing
let targetHeading = lastGpsHeading;
if (pos.coords.heading !== null && !isNaN(pos.coords.heading) && pos.coords.heading >= 0) {
targetHeading = pos.coords.heading;
} else if (distMeters >= 2.0) {
// Compute bearing from movement vector
targetHeading = getBearing(lastGpsPosition.lat, lastGpsPosition.lng, curLat, curLng);
}
lastGpsHeading = targetHeading;
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) {
carElem.style.transform = `rotate(${Math.round(targetHeading)}deg)`;
}
// Update actual coordinates
currentPosition.lat = curLat;
currentPosition.lng = curLng;
if (userMarker) {
userMarker.setLatLng([curLat, curLng]);
}
// Map Following
if (isFollowCarLocked) {
map.panTo([curLat, curLng], { animate: true, duration: 0.5 });
if (currentSpeed > 25 && map.getZoom() < 15) {
map.setZoom(15);
}
}
// Recalculate camera proximity immediately on every movement step
checkProximityToCameras();
// Update HUD status badges
const statusLabel = currentSpeed >= 15
? `Driving (${currentSpeed} MPH)`
: currentSpeed >= 2
? `Walking (${currentSpeed} MPH)`
: `Moving (${currentSpeed} MPH)`;
updateSpottedBadge(`Live GPS (±${accuracy}m) • ${statusLabel}`);
updateHudLocationStatus(`Live GPS: ${statusLabel}`);
// Update state record
lastGpsPosition = {
lat: curLat,
lng: curLng,
timestamp,
accuracy,
speed: currentSpeed,
heading: lastGpsHeading
};
}
function handleGpsError(err) {
console.warn('GPS watchPosition event:', err.code, err.message);
if (err.code === 1) { // PERMISSION_DENIED
updateSpottedBadge('GPS Permission Denied');
updateHudLocationStatus('Location access blocked in browser');
if (el.hudBannerAlert) {
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.alertTriangle('flock-svg-lg') : ''}</span>
<div>
<strong>LOCATION PERMISSION REQUIRED</strong>
<p>Please tap the permissions icon in your address bar and select 'Allow Location' to enable live GPS tracking.</p>
</div>
`;
setTimeout(() => {
el.hudBannerAlert.classList.remove('visible');
}, 6000);
}
stopContinuousGpsTracking();
} else if (err.code === 2) { // POSITION_UNAVAILABLE
updateSpottedBadge('Acquiring GPS Signal...');
updateHudLocationStatus('Searching for GPS satellites...');
} else if (err.code === 3) { // TIMEOUT
// Do not popup modal alert; seamlessly continue waiting for the next fix
console.log('GPS fix timeout, awaiting next satellite packet...');
}
}
function startContinuousGpsTracking(options = {}) {
if (!navigator.geolocation) { if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser.'); alert('Geolocation is not supported by your browser.');
return; return;
} }
if (isSimulating) stopDriveSimulation();
if (options.forceRecenter || options.centerMap) {
isFollowCarLocked = true;
updateFollowCarUI();
}
requestWakeLock();
// If watch is already active, recenter if requested and return
if (liveGpsWatchId !== null) { if (liveGpsWatchId !== null) {
navigator.geolocation.clearWatch(liveGpsWatchId); isContinuousTracking = true;
liveGpsWatchId = null; updateTrackingUI(true);
el.btnLiveGps.classList.remove('active'); if (lastGpsPosition && (options.forceRecenter || options.centerMap)) {
const radioIcon = window.FlockIcons ? FlockIcons.radio() : ''; map.setView([lastGpsPosition.lat, lastGpsPosition.lng], Math.max(map.getZoom(), 15), { animate: true });
el.btnLiveGps.innerHTML = `${radioIcon}<span>Live GPS Drive Mode</span>`; }
el.hudSpeedVal.textContent = '0';
return; return;
} }
if (isSimulating) stopDriveSimulation(); isContinuousTracking = true;
updateTrackingUI(true);
updateSpottedBadge('Acquiring Continuous Live GPS...');
updateHudLocationStatus('Connecting to GPS Satellites...');
el.btnLiveGps.classList.add('active'); const gpsOptions = {
const stopIcon = window.FlockIcons ? FlockIcons.stopCircle() : '';
el.btnLiveGps.innerHTML = `${stopIcon}<span>Stop Live GPS</span>`;
speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.');
liveGpsWatchId = navigator.geolocation.watchPosition(
pos => {
currentPosition = {
lat: pos.coords.latitude,
lng: pos.coords.longitude
};
// Speed in mph
if (pos.coords.speed !== null && !isNaN(pos.coords.speed)) {
currentSpeed = Math.round(pos.coords.speed * 2.23694);
} else {
currentSpeed = 0;
}
el.hudSpeedVal.textContent = currentSpeed;
// Heading
if (pos.coords.heading !== null && !isNaN(pos.coords.heading)) {
const carElem = document.getElementById('user-vehicle-marker');
if (carElem) carElem.style.transform = `rotate(${pos.coords.heading}deg)`;
}
if (userMarker) userMarker.setLatLng([currentPosition.lat, currentPosition.lng]);
if (isFollowCarLocked) {
map.panTo([currentPosition.lat, currentPosition.lng], { animate: true, duration: 0.8 });
if (currentSpeed > 15 && map.getZoom() < 16) {
map.setZoom(16);
}
}
checkProximityToCameras();
},
err => {
console.warn('GPS Error:', err);
alert('GPS Location Error: ' + err.message + '\nPlease allow location access in your browser.');
},
{
enableHighAccuracy: true, enableHighAccuracy: true,
maximumAge: 1000, maximumAge: 1000,
timeout: 10000 timeout: 20000
} };
liveGpsWatchId = navigator.geolocation.watchPosition(
pos => handleGpsPositionUpdate(pos, options),
handleGpsError,
gpsOptions
); );
} }
function stopContinuousGpsTracking() {
if (liveGpsWatchId !== null) {
navigator.geolocation.clearWatch(liveGpsWatchId);
liveGpsWatchId = null;
}
isContinuousTracking = false;
currentSpeed = 0;
el.hudSpeedVal.textContent = '0';
updateTrackingUI(false);
updateSpottedBadge('Live GPS Paused');
updateHudLocationStatus('Live GPS Paused');
}
function toggleLiveGps() {
if (isContinuousTracking) {
stopContinuousGpsTracking();
speakVoiceAlert('Live GPS tracking paused.');
} else {
startContinuousGpsTracking({ forceRecenter: true, flyTo: true });
speakVoiceAlert('Live GPS drive mode activated. Monitoring for Flock cameras.');
}
}
// --- MOBILE PHONE CONNECT INSTRUCTIONS --- // --- MOBILE PHONE CONNECT INSTRUCTIONS ---
function showMobileConnectModal() { function showMobileConnectModal() {
const httpUrl = 'http://192.168.1.249:8085'; const httpUrl = 'http://192.168.1.249:8085';
@@ -1034,10 +1232,10 @@
// --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) --- // --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) ---
async function attemptAutoLocate() { async function attemptAutoLocate() {
updateSpottedBadge('Spotting Antelope Valley Location...'); updateSpottedBadge('Spotting Location...');
// Tier 1: Instant IP-based Network Geolocation // Tier 1: Instant IP-based Network Geolocation
// Works 100% reliably even when browser GPS is blocked, disabled, or accessed over HTTP // Provides immediate visual orientation while GPS satellites lock
let ipFound = false; let ipFound = false;
try { try {
const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' }); const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' });
@@ -1050,7 +1248,6 @@
console.log('IP Network Geolocation spotted:', city, region, ipLat, ipLng); console.log('IP Network Geolocation spotted:', city, region, ipLat, ipLng);
// Center directly on Antelope Valley (Palmdale / Lancaster corridor)
let targetLat = ipLat; let targetLat = ipLat;
let targetLng = ipLng; let targetLng = ipLng;
const isAntelopeValley = city.toLowerCase().includes('palmdale') || const isAntelopeValley = city.toLowerCase().includes('palmdale') ||
@@ -1058,17 +1255,17 @@
(ipLat > 34.2 && ipLat < 34.85 && ipLng > -118.45 && ipLng < -117.8); (ipLat > 34.2 && ipLat < 34.85 && ipLng > -118.45 && ipLng < -117.8);
if (isAntelopeValley) { if (isAntelopeValley) {
// Snap directly to Palmdale central corridor (Palmdale Blvd & 10th St W)
targetLat = 34.60194; targetLat = 34.60194;
targetLng = -118.14847; targetLng = -118.14847;
} }
if (!lastGpsPosition) {
updateUserCoordinates(targetLat, targetLng, 0, 0); updateUserCoordinates(targetLat, targetLng, 0, 0);
map.flyTo([targetLat, targetLng], 14, { duration: 1.2 }); map.flyTo([targetLat, targetLng], 14, { duration: 1.0 });
updateSpottedBadge(`Spotted: ${city}, Antelope Valley (Network)`); updateSpottedBadge(`Spotted: ${city} (Network)`);
updateHudLocationStatus(`Monitoring ${city} / Antelope Valley Corridors`); updateHudLocationStatus(`Monitoring ${city} Corridors`);
speakVoiceAlert(`Location spotted: Antelope Valley, California.`);
checkProximityToCameras(); checkProximityToCameras();
}
ipFound = true; ipFound = true;
} }
} catch (e) { } catch (e) {
@@ -1076,9 +1273,9 @@
try { try {
const resp2 = await fetch('https://ipwho.is/', { cache: 'no-cache' }); const resp2 = await fetch('https://ipwho.is/', { cache: 'no-cache' });
const ipData2 = await resp2.json(); const ipData2 = await resp2.json();
if (ipData2 && ipData2.success && ipData2.latitude) { if (ipData2 && ipData2.success && ipData2.latitude && !lastGpsPosition) {
updateUserCoordinates(34.60194, -118.14847, 0, 0); updateUserCoordinates(34.60194, -118.14847, 0, 0);
map.flyTo([34.60194, -118.14847], 14, { duration: 1.2 }); map.flyTo([34.60194, -118.14847], 14, { duration: 1.0 });
updateSpottedBadge(`Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`); updateSpottedBadge(`Spotted: ${ipData2.city || 'Antelope Valley'} (Network)`);
checkProximityToCameras(); checkProximityToCameras();
ipFound = true; ipFound = true;
@@ -1088,32 +1285,16 @@
} }
} }
if (!ipFound) { if (!ipFound && !lastGpsPosition) {
updateSpottedBadge('Antelope Valley: Palmdale, CA (Default)'); updateSpottedBadge('Palmdale / Antelope Valley');
updateHudLocationStatus('Monitoring Antelope Valley Corridors'); updateHudLocationStatus('Monitoring Antelope Valley Corridors');
checkProximityToCameras(); checkProximityToCameras();
} }
// Tier 2: Hardware Device GPS (Sub-meter accuracy if granted) // Tier 2: CONTINUOUS HARDWARE GPS TRACKING
// Seamlessly engages watchPosition so your vehicle/walking dot updates continuously
if (navigator.geolocation && (window.isSecureContext || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) { if (navigator.geolocation && (window.isSecureContext || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
navigator.geolocation.getCurrentPosition( startContinuousGpsTracking({ flyTo: true, centerMap: true });
pos => {
const lat = pos.coords.latitude;
const lng = pos.coords.longitude;
const acc = Math.round(pos.coords.accuracy || 10);
console.log('Hardware GPS lock acquired:', lat, lng, '±' + acc + 'm');
updateUserCoordinates(lat, lng, pos.coords.speed, pos.coords.heading);
map.flyTo([lat, lng], 15, { duration: 1.2 });
updateSpottedBadge(`High-Precision GPS (±${acc}m) • Antelope Valley`);
updateHudLocationStatus('Live Driving GPS Active');
checkProximityToCameras();
},
gpsErr => {
console.log('Hardware GPS not granted/pending:', gpsErr.message);
// Retain the confirmed Antelope Valley IP location smoothly
},
{ enableHighAccuracy: true, timeout: 6000, maximumAge: 30000 }
);
} }
} }
@@ -1153,58 +1334,30 @@
async function useLiveGps() { async function useLiveGps() {
if (isSimulating) stopDriveSimulation(); if (isSimulating) stopDriveSimulation();
updateSpottedBadge('Re-detecting location in Antelope Valley...'); updateSpottedBadge('Activating High-Precision GPS...');
updateHudLocationStatus('Acquiring high-precision location...'); updateHudLocationStatus('Acquiring live satellite coordinates...');
// 1. First ensure IP location is refreshed // If we already have a live GPS fix, fly directly to it
try { if (lastGpsPosition) {
const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' }); isFollowCarLocked = true;
const ipData = await resp.json(); updateFollowCarUI();
if (ipData && ipData.latitude) { map.flyTo([lastGpsPosition.lat, lastGpsPosition.lng], Math.max(map.getZoom(), 16), { duration: 1.0 });
const city = ipData.city || 'Palmdale';
jumpToLocation(34.60194, -118.14847, `${city} (Antelope Valley)`);
updateSpottedBadge(`Spotted: ${city}, Antelope Valley`);
}
} catch (e) {}
// 2. Hardware GPS Attempt
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
pos => {
updateUserCoordinates(pos.coords.latitude, pos.coords.longitude, pos.coords.speed, pos.coords.heading);
map.flyTo([pos.coords.latitude, pos.coords.longitude], 16, { duration: 1.2 });
const acc = Math.round(pos.coords.accuracy || 10);
updateSpottedBadge(`Live GPS Active (±${acc}m)`);
updateHudLocationStatus('High-Precision GPS Locked');
playRadarChime();
speakVoiceAlert('GPS position locked in Antelope Valley.');
checkProximityToCameras(); checkProximityToCameras();
},
err => {
console.warn('GPS hardware notice:', err.message);
// Show non-blocking informative banner
if (err.code === 1) { // PERMISSION_DENIED
el.hudBannerAlert.classList.add('visible');
el.hudBannerAlert.innerHTML = `
<span class="banner-pulse-icon">${window.FlockIcons ? FlockIcons.radar('flock-svg-lg') : ''}</span>
<div>
<strong>ANTELOPE VALLEY NETWORK LOCATION ACTIVE</strong>
<p>To enable turn-by-turn driving GPS, click the permissions icon in your browser address bar and select 'Allow Location'.</p>
</div>
`;
setTimeout(() => {
el.hudBannerAlert.classList.remove('visible');
}, 6000);
}
},
{ enableHighAccuracy: true, timeout: 8000 }
);
} }
// Engage continuous high-accuracy tracking
startContinuousGpsTracking({ forceRecenter: true, flyTo: true });
speakVoiceAlert('Live GPS tracking engaged.');
} }
// --- LOCATION PRESET JUMPS (ANTELOPE VALLEY & SUB-REGIONS) --- // --- LOCATION PRESET JUMPS (ANTELOPE VALLEY & SUB-REGIONS) ---
function jumpToLocation(lat, lng, label, zoom = 15) { function jumpToLocation(lat, lng, label, zoom = 15) {
if (isSimulating) stopDriveSimulation(); if (isSimulating) stopDriveSimulation();
if (isContinuousTracking) {
stopContinuousGpsTracking();
}
isFollowCarLocked = false;
updateFollowCarUI();
updateUserCoordinates(lat, lng, 0, 0); updateUserCoordinates(lat, lng, 0, 0);
map.flyTo([lat, lng], zoom, { duration: 1.2 }); map.flyTo([lat, lng], zoom, { duration: 1.2 });
updateHudLocationStatus(label); updateHudLocationStatus(label);
@@ -2158,18 +2311,30 @@
}); });
} }
// Screen Wake Lock to prevent phone screen from going to sleep while driving // Screen Wake Lock to prevent phone screen from going to sleep while driving or walking
let wakeLock = null; let wakeLock = null;
async function requestWakeLock() { async function requestWakeLock() {
if ('wakeLock' in navigator) { if ('wakeLock' in navigator && document.visibilityState === 'visible') {
try { try {
if (!wakeLock || wakeLock.released) {
wakeLock = await navigator.wakeLock.request('screen'); wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
wakeLock = null;
});
}
} catch (err) { } catch (err) {
console.warn('Wake Lock error:', err); console.warn('Wake Lock error:', err.message);
} }
} }
} }
// Re-acquire wake lock if browser tab returns to foreground while continuous tracking is active
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && isContinuousTracking) {
requestWakeLock();
}
});
// Auto-unlock Web Audio and Wake Lock on first touch (critical for mobile iOS/Android) // Auto-unlock Web Audio and Wake Lock on first touch (critical for mobile iOS/Android)
function unlockMobileFeatures() { function unlockMobileFeatures() {
if (!audioContext) { if (!audioContext) {
+13 -13
View File
@@ -1,20 +1,20 @@
// Service Worker for FlockRadar Offline Driving & Live Updates // Service Worker for FlockRadar Offline Driving & Live Updates
const CACHE_NAME = 'flockradar-v14'; const CACHE_NAME = 'flockradar-v15';
const ASSETS_TO_CACHE = [ const ASSETS_TO_CACHE = [
'./', './',
'./index.html', './index.html',
'./css/styles.css?v=14', './css/styles.css?v=15',
'./js/icons.js?v=14', './js/icons.js?v=15',
'./js/app.js?v=14', './js/app.js?v=15',
'./js/auth.js?v=14', './js/auth.js?v=15',
'./js/radar.js?v=14', './js/radar.js?v=15',
'./data/portals.js?v=14', './data/portals.js?v=15',
'./favicon.svg?v=14', './favicon.svg?v=15',
'./favicon.ico?v=14', './favicon.ico?v=15',
'./apple-touch-icon.png?v=14', './apple-touch-icon.png?v=15',
'./icon-192.png?v=14', './icon-192.png?v=15',
'./icon-512.png?v=14', './icon-512.png?v=15',
'./og-image.png?v=14', './og-image.png?v=15',
'./assets/img/logo.svg', './assets/img/logo.svg',
'./assets/img/flock_pole_cam_palmdale.jpg', './assets/img/flock_pole_cam_palmdale.jpg',
'./manifest.json' './manifest.json'