fix(gps): upgrade to real-time continuous GPS tracking for walking and driving (v15)
This commit is contained in:
@@ -555,6 +555,23 @@ body {
|
||||
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 {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
||||
+14
-14
@@ -10,11 +10,11 @@
|
||||
<link rel="canonical" href="https://flockradar.org/">
|
||||
|
||||
<!-- Favicon Suite & App Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg?v=14">
|
||||
<link rel="alternate icon" href="favicon.ico?v=14">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png?v=14">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="icon-192.png?v=14">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="icon-512.png?v=14">
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg?v=15">
|
||||
<link rel="alternate icon" href="favicon.ico?v=15">
|
||||
<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=15">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="icon-512.png?v=15">
|
||||
<meta name="theme-color" content="#07090e">
|
||||
<meta name="msapplication-TileColor" content="#07090e">
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<meta property="og:site_name" content="FlockRadar">
|
||||
<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:image" content="https://flockradar.org/og-image.png?v=14">
|
||||
<meta property="og:image:secure_url" 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=15">
|
||||
<meta property="og:image:type" content="image/png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
@@ -36,7 +36,7 @@
|
||||
<meta name="twitter:url" content="https://flockradar.org/">
|
||||
<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: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">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
@@ -54,7 +54,7 @@
|
||||
<meta name="apple-mobile-web-app-title" content="FlockRadar">
|
||||
|
||||
<!-- Main Stylesheet -->
|
||||
<link rel="stylesheet" href="css/styles.css?v=14">
|
||||
<link rel="stylesheet" href="css/styles.css?v=15">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1156,11 +1156,11 @@
|
||||
<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 -->
|
||||
<script src="data/portals.js?v=14"></script>
|
||||
<script src="js/icons.js?v=14"></script>
|
||||
<script src="js/auth.js?v=14"></script>
|
||||
<script src="js/radar.js?v=14"></script>
|
||||
<script src="js/app.js?v=14"></script>
|
||||
<script src="data/portals.js?v=15"></script>
|
||||
<script src="js/icons.js?v=15"></script>
|
||||
<script src="js/auth.js?v=15"></script>
|
||||
<script src="js/radar.js?v=15"></script>
|
||||
<script src="js/app.js?v=15"></script>
|
||||
|
||||
<!-- Service Worker Instant Cache Invalidation -->
|
||||
<script>
|
||||
|
||||
+299
-134
@@ -683,7 +683,11 @@
|
||||
function recenterOnCar() {
|
||||
isFollowCarLocked = true;
|
||||
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() {
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
// --- LIVE GPS CONTINUOUS TRACKING ---
|
||||
// --- REAL-TIME CONTINUOUS GPS TRACKING ENGINE ---
|
||||
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) {
|
||||
alert('Geolocation is not supported by your browser.');
|
||||
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) {
|
||||
navigator.geolocation.clearWatch(liveGpsWatchId);
|
||||
liveGpsWatchId = null;
|
||||
el.btnLiveGps.classList.remove('active');
|
||||
const radioIcon = window.FlockIcons ? FlockIcons.radio() : '';
|
||||
el.btnLiveGps.innerHTML = `${radioIcon}<span>Live GPS Drive Mode</span>`;
|
||||
el.hudSpeedVal.textContent = '0';
|
||||
isContinuousTracking = true;
|
||||
updateTrackingUI(true);
|
||||
if (lastGpsPosition && (options.forceRecenter || options.centerMap)) {
|
||||
map.setView([lastGpsPosition.lat, lastGpsPosition.lng], Math.max(map.getZoom(), 15), { animate: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSimulating) stopDriveSimulation();
|
||||
isContinuousTracking = true;
|
||||
updateTrackingUI(true);
|
||||
updateSpottedBadge('Acquiring Continuous Live GPS...');
|
||||
updateHudLocationStatus('Connecting to GPS Satellites...');
|
||||
|
||||
el.btnLiveGps.classList.add('active');
|
||||
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.');
|
||||
const gpsOptions = {
|
||||
enableHighAccuracy: true,
|
||||
maximumAge: 1000,
|
||||
timeout: 20000
|
||||
};
|
||||
|
||||
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,
|
||||
maximumAge: 1000,
|
||||
timeout: 10000
|
||||
}
|
||||
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 ---
|
||||
function showMobileConnectModal() {
|
||||
const httpUrl = 'http://192.168.1.249:8085';
|
||||
@@ -1034,10 +1232,10 @@
|
||||
|
||||
// --- MULTI-TIER AUTO-LOCATION RESOLVER (ANTELOPE VALLEY & BEYOND) ---
|
||||
async function attemptAutoLocate() {
|
||||
updateSpottedBadge('Spotting Antelope Valley Location...');
|
||||
updateSpottedBadge('Spotting Location...');
|
||||
|
||||
// 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;
|
||||
try {
|
||||
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);
|
||||
|
||||
// Center directly on Antelope Valley (Palmdale / Lancaster corridor)
|
||||
let targetLat = ipLat;
|
||||
let targetLng = ipLng;
|
||||
const isAntelopeValley = city.toLowerCase().includes('palmdale') ||
|
||||
@@ -1058,17 +1255,17 @@
|
||||
(ipLat > 34.2 && ipLat < 34.85 && ipLng > -118.45 && ipLng < -117.8);
|
||||
|
||||
if (isAntelopeValley) {
|
||||
// Snap directly to Palmdale central corridor (Palmdale Blvd & 10th St W)
|
||||
targetLat = 34.60194;
|
||||
targetLng = -118.14847;
|
||||
}
|
||||
|
||||
updateUserCoordinates(targetLat, targetLng, 0, 0);
|
||||
map.flyTo([targetLat, targetLng], 14, { duration: 1.2 });
|
||||
updateSpottedBadge(`Spotted: ${city}, Antelope Valley (Network)`);
|
||||
updateHudLocationStatus(`Monitoring ${city} / Antelope Valley Corridors`);
|
||||
speakVoiceAlert(`Location spotted: Antelope Valley, California.`);
|
||||
checkProximityToCameras();
|
||||
if (!lastGpsPosition) {
|
||||
updateUserCoordinates(targetLat, targetLng, 0, 0);
|
||||
map.flyTo([targetLat, targetLng], 14, { duration: 1.0 });
|
||||
updateSpottedBadge(`Spotted: ${city} (Network)`);
|
||||
updateHudLocationStatus(`Monitoring ${city} Corridors`);
|
||||
checkProximityToCameras();
|
||||
}
|
||||
ipFound = true;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1076,9 +1273,9 @@
|
||||
try {
|
||||
const resp2 = await fetch('https://ipwho.is/', { cache: 'no-cache' });
|
||||
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);
|
||||
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)`);
|
||||
checkProximityToCameras();
|
||||
ipFound = true;
|
||||
@@ -1088,32 +1285,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!ipFound) {
|
||||
updateSpottedBadge('Antelope Valley: Palmdale, CA (Default)');
|
||||
if (!ipFound && !lastGpsPosition) {
|
||||
updateSpottedBadge('Palmdale / Antelope Valley');
|
||||
updateHudLocationStatus('Monitoring Antelope Valley Corridors');
|
||||
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')) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
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 }
|
||||
);
|
||||
startContinuousGpsTracking({ flyTo: true, centerMap: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,58 +1334,30 @@
|
||||
async function useLiveGps() {
|
||||
if (isSimulating) stopDriveSimulation();
|
||||
|
||||
updateSpottedBadge('Re-detecting location in Antelope Valley...');
|
||||
updateHudLocationStatus('Acquiring high-precision location...');
|
||||
updateSpottedBadge('Activating High-Precision GPS...');
|
||||
updateHudLocationStatus('Acquiring live satellite coordinates...');
|
||||
|
||||
// 1. First ensure IP location is refreshed
|
||||
try {
|
||||
const resp = await fetch('https://get.geojs.io/v1/ip/geo.json', { cache: 'no-cache' });
|
||||
const ipData = await resp.json();
|
||||
if (ipData && ipData.latitude) {
|
||||
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();
|
||||
},
|
||||
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 }
|
||||
);
|
||||
// If we already have a live GPS fix, fly directly to it
|
||||
if (lastGpsPosition) {
|
||||
isFollowCarLocked = true;
|
||||
updateFollowCarUI();
|
||||
map.flyTo([lastGpsPosition.lat, lastGpsPosition.lng], Math.max(map.getZoom(), 16), { duration: 1.0 });
|
||||
checkProximityToCameras();
|
||||
}
|
||||
|
||||
// Engage continuous high-accuracy tracking
|
||||
startContinuousGpsTracking({ forceRecenter: true, flyTo: true });
|
||||
speakVoiceAlert('Live GPS tracking engaged.');
|
||||
}
|
||||
|
||||
// --- LOCATION PRESET JUMPS (ANTELOPE VALLEY & SUB-REGIONS) ---
|
||||
function jumpToLocation(lat, lng, label, zoom = 15) {
|
||||
if (isSimulating) stopDriveSimulation();
|
||||
if (isContinuousTracking) {
|
||||
stopContinuousGpsTracking();
|
||||
}
|
||||
isFollowCarLocked = false;
|
||||
updateFollowCarUI();
|
||||
updateUserCoordinates(lat, lng, 0, 0);
|
||||
map.flyTo([lat, lng], zoom, { duration: 1.2 });
|
||||
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;
|
||||
async function requestWakeLock() {
|
||||
if ('wakeLock' in navigator) {
|
||||
if ('wakeLock' in navigator && document.visibilityState === 'visible') {
|
||||
try {
|
||||
wakeLock = await navigator.wakeLock.request('screen');
|
||||
if (!wakeLock || wakeLock.released) {
|
||||
wakeLock = await navigator.wakeLock.request('screen');
|
||||
wakeLock.addEventListener('release', () => {
|
||||
wakeLock = null;
|
||||
});
|
||||
}
|
||||
} 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)
|
||||
function unlockMobileFeatures() {
|
||||
if (!audioContext) {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// Service Worker for FlockRadar Offline Driving & Live Updates
|
||||
const CACHE_NAME = 'flockradar-v14';
|
||||
const CACHE_NAME = 'flockradar-v15';
|
||||
const ASSETS_TO_CACHE = [
|
||||
'./',
|
||||
'./index.html',
|
||||
'./css/styles.css?v=14',
|
||||
'./js/icons.js?v=14',
|
||||
'./js/app.js?v=14',
|
||||
'./js/auth.js?v=14',
|
||||
'./js/radar.js?v=14',
|
||||
'./data/portals.js?v=14',
|
||||
'./favicon.svg?v=14',
|
||||
'./favicon.ico?v=14',
|
||||
'./apple-touch-icon.png?v=14',
|
||||
'./icon-192.png?v=14',
|
||||
'./icon-512.png?v=14',
|
||||
'./og-image.png?v=14',
|
||||
'./css/styles.css?v=15',
|
||||
'./js/icons.js?v=15',
|
||||
'./js/app.js?v=15',
|
||||
'./js/auth.js?v=15',
|
||||
'./js/radar.js?v=15',
|
||||
'./data/portals.js?v=15',
|
||||
'./favicon.svg?v=15',
|
||||
'./favicon.ico?v=15',
|
||||
'./apple-touch-icon.png?v=15',
|
||||
'./icon-192.png?v=15',
|
||||
'./icon-512.png?v=15',
|
||||
'./og-image.png?v=15',
|
||||
'./assets/img/logo.svg',
|
||||
'./assets/img/flock_pole_cam_palmdale.jpg',
|
||||
'./manifest.json'
|
||||
|
||||
Reference in New Issue
Block a user