2026-10-28 10:54:15 -05:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >< meta name = "viewport" content = "width=device-width,initial-scale=1" >
< title > Macker · Work car</ title >
< link rel = "stylesheet" href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" >
< style >
2026-10-28 14:04:39 -05:00
* { box-sizing : border-box } body { margin : 0 ; background : #f3f5f2 ; color : #182d26 ; font : 16 px system-ui , sans-serif } header { padding : 24 px 5 vw ; display : flex ; justify-content : space-between ; align-items : center } h1 { font-size : 24 px ; margin : 0 } header p { margin : 4 px 0 ; color : #607269 } main { margin : 0 5 vw 28 px }. stats { display : flex ; gap : 16 px ; margin-bottom : 20 px }. card { background : white ; border : 1 px solid #dce4dd ; border-radius : 12 px ; padding : 20 px ; flex : 1 }. label { font-size : 12 px ; text-transform : uppercase ; letter-spacing : .1 em ; color : #607269 }. value { display : block ; font-size : 32 px ; margin : 8 px 0 } small { color : #607269 }# address { font-size : 20 px ; overflow-wrap : anywhere }. stats { flex-wrap : wrap }. card { min-width : 220 px }# map { height : 65 vh ; min-height : 320 px ; border-radius : 14 px ; border : 1 px solid #cbd8cd } button { background : #1c593f ; color : white ; border : 0 ; border-radius : 8 px ; padding : 12 px ; cursor : pointer }# status { margin : 12 px 0 ; min-height : 24 px }@ media ( max-width : 540px ) {. stats { flex-direction : column ; gap : 10 px }. card { padding : 14 px }. value { font-size : 26 px } header { gap : 12 px }# address { font-size : 20 px ; overflow-wrap : anywhere }. stats { flex-wrap : wrap }. card { min-width : 220 px }# map { height : 50 vh }}
2026-10-28 10:54:15 -05:00
</ style >
</ head >
< body >
< header >< div >< h1 > Macker</ h1 >< p > Work car · Live location</ p ></ div >< button id = "follow" type = "button" > Follow car: on</ button ></ header >
2026-10-28 14:14:38 -05:00
< main >< div class = "stats" >< section class = "card" >< span class = "label" > Current speed</ span >< strong class = "value" id = "speed" > —</ strong >< small id = "metric" > Waiting for GPS</ small ></ section >< section class = "card" >< span class = "label" > Last update</ span >< strong class = "value" id = "age" > —</ strong >< small id = "time" > No location received yet</ small ></ section >< section class = "card" >< span class = "label" > Nearby address</ span >< strong class = "value" id = "address" > —</ strong >< small id = "address-distance" > Waiting for GPS</ small >< br >< small id = "address-source" ></ small ></ section ></ div >< p id = "status" role = "status" > Waiting for the car’ s first GPS fix…</ p >< p id = "uncertainty" >< small > GPS uncertainty unavailable</ small ></ p >< p id = "imagery-info" role = "status" >< small > Street map · OpenStreetMap</ small ></ p >< div id = "map" aria-label = "Street or aerial car location map with GPS uncertainty circle" ></ div ></ main >
2026-10-28 10:54:15 -05:00
< script src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" ></ script >
< script >
const el = id => document . getElementById ( id );
2026-10-28 13:43:27 -05:00
let map , marker , uncertaintyCircle , latest , follow = true , failed = false ;
2026-10-28 14:14:38 -05:00
// USGS exports Web Mercator images; its ImageServer is not an XYZ tile cache.
// Match each Leaflet tile's projected bounds explicitly to keep fixes aligned.
function aerialTileURL ( coords ){
const halfWorld = 20037508.342789244 ;
const span = 2 * halfWorld / 2 ** coords . z ;
const west =- halfWorld + coords . x * span , north = halfWorld - coords . y * span ;
const query = new URLSearchParams ({
f : 'image' , bbox : [ west , north - span , west + span , north ]. join ( ',' ),
bboxSR : '3857' , imageSR : '3857' , size : '256,256' , format : 'jpg' ,
interpolation : 'RSP_BilinearInterpolation' ,
renderingRule : JSON . stringify ({ rasterFunction : 'NaturalColor' })
});
return `https://imagery.nationalmap.gov/arcgis/rest/services/USGSNAIPImagery/ImageServer/exportImage? ${ query } ` ;
}
if ( window . L ){
map = L . map ( 'map' ). setView ([ 31.4 , - 89.7 ], 7 );
const street = L . tileLayer ( 'https://tile.openstreetmap.org/{z}/{x}/{y}.png' ,{
maxZoom : 19 , attribution : '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}). addTo ( map );
const AerialLayer = L . TileLayer . extend ({ getTileUrl : aerialTileURL });
const aerial = new AerialLayer ( '' ,{
maxZoom : 19 , bounds : [[ 30.1 , - 91.7 ],[ 35.1 , - 88.0 ]],
updateWhenIdle : true , keepBuffer : 1 ,
attribution : 'Aerial imagery: USDA NAIP / <a href="https://www.usgs.gov/programs/national-geospatial-program/national-map">USGS, The National Map</a>'
});
L . control . layers ({ Street : street , Aerial : aerial }, null ,{ collapsed : false }). addTo ( map );
map . on ( 'layeradd' , event =>{
if ( event . layer === aerial ) el ( 'imagery-info' ). textContent = 'Loading aerial imagery · USDA NAIP / USGS · Photography dates vary; imagery is not live' ;
else if ( event . layer === street ) el ( 'imagery-info' ). textContent = 'Street map · OpenStreetMap' ;
});
aerial . on ( 'load' ,()=>{
if ( map . hasLayer ( aerial )) el ( 'imagery-info' ). textContent = 'Aerial imagery · USDA NAIP / USGS · Photography dates vary; imagery is not live' ;
});
aerial . on ( 'tileerror' ,()=>{
if ( ! map . hasLayer ( aerial )) return ;
map . removeLayer ( aerial );
street . addTo ( map );
el ( 'imagery-info' ). textContent = 'Aerial imagery could not load. Showing street map; select Aerial to retry.' ;
});
}
2026-10-28 10:54:15 -05:00
el ( 'follow' ). onclick = ()=>{ follow =! follow ; el ( 'follow' ). textContent = `Follow car: ${ follow ? 'on' : 'off' } ` ; if ( follow && marker ) map . panTo ( marker . getLatLng ());};
2026-10-28 13:43:27 -05:00
// gpsd axis errors are already in metres. Enclose their horizontal error box.
// Zero also represents an omitted estimate in the client's gpsd dependency.
function uncertaintyRadius ( point ){
const { LatErr , LongErr } = point ;
if ( ! Number . isFinite ( LatErr ) ||! Number . isFinite ( LongErr ) || LatErr <= 0 || LongErr <= 0 ) return null ;
const radius = Math . hypot ( LatErr , LongErr );
return Number . isFinite ( radius ) ? radius : null ;
}
function updateUncertainty ( point , pos ){
const radius = uncertaintyRadius ( point );
el ( 'uncertainty' ). textContent = radius === null ? 'GPS uncertainty unavailable' : `Estimated GPS uncertainty: ${ radius . toFixed ( 1 ) } m radius · shaded circle` ;
if ( radius === null ){
if ( uncertaintyCircle ){ map . removeLayer ( uncertaintyCircle ); uncertaintyCircle = null ;}
return ;
}
if ( ! uncertaintyCircle ){
uncertaintyCircle = L . circle ( pos ,{ radius , color : '#287a57' , weight : 2 , fillColor : '#409b73' , fillOpacity : 0.16 , interactive : false }). addTo ( map );
} else { uncertaintyCircle . setLatLng ( pos ). setRadius ( radius );}
}
2026-10-28 14:04:39 -05:00
function renderAddress ( nearby , stale ){
const address = nearby ? . address ;
el ( 'address' ). textContent = address ? . label || '—' ;
el ( 'address-source' ). textContent = address ? . attribution || '' ;
if ( address ){
const m = address . distance_meters ;
const distance = m < 1000 ? ` ${ Math . round ( m ) } m` : ` ${ ( m / 1000 ). toFixed ( 1 ) } km` ;
el ( 'address-distance' ). textContent = `Approximately ${ distance } away · straight-line ${ stale ? ' · last known fix' : '' } ` ;
} else {
const messages = { disabled : 'Address lookup is not configured' , pending : 'Looking up nearby address…' , unavailable : 'No nearby mapped address available' };
el ( 'address-distance' ). textContent = messages [ nearby ? . status ] || 'Address unavailable' ;
}
}
2026-10-28 10:54:15 -05:00
function render (){
if ( ! latest ){ el ( 'status' ). textContent = failed ? 'Cannot reach server. Retrying…' : 'Waiting for the car’ s first GPS fix…' ; return ;}
const seconds = Math . max ( 0 , Math . floor (( Date . now () - Date . parse ( latest . updated_at )) / 1000 ));
const stale = seconds > 30 , p = latest . point ;
2026-10-28 14:04:39 -05:00
renderAddress ( latest . nearby , stale || failed );
2026-10-28 10:54:15 -05:00
el ( 'age' ). textContent = seconds < 60 ? ` ${ seconds } s ago` : seconds < 3600 ? ` ${ Math . floor ( seconds / 60 ) } min ago` : ` ${ Math . floor ( seconds / 3600 ) } h ago` ;
el ( 'time' ). textContent = new Date ( latest . updated_at ). toLocaleString ();
el ( 'speed' ). textContent = stale || p . Speed == null ? '—' : ` ${ ( p . Speed * 2.236936 ). toFixed ( 1 ) } mph` ;
el ( 'metric' ). textContent = stale ? 'Last fix is stale' : p . Speed == null ? 'Speed unavailable' : ` ${ ( p . Speed * 3.6 ). toFixed ( 1 ) } km/h` ;
el ( 'status' ). textContent = failed ? 'Connection lost · showing last known location' : stale ? 'No recent GPS fix · showing last known location' : `GPS active · ${ p . Lat . toFixed ( 5 ) } , ${ p . Long . toFixed ( 5 ) } ` ;
if ( ! map ) el ( 'status' ). textContent += ' · Map failed to load; reload to retry' ;
}
2026-10-28 13:43:27 -05:00
async function poll (){ try { const res = await fetch ( '/api/location' ,{ cache : 'no-store' , signal : AbortSignal . timeout ( 8000 )}); if ( ! res . ok ) throw Error ( res . status ); latest = await res . json (); failed = false ; if ( latest && map ){ const pos = [ latest . point . Lat , latest . point . Long ]; updateUncertainty ( latest . point , pos ); if ( ! marker ){ marker = L . marker ( pos ). addTo ( map ); map . setView ( pos , 15 );} else { marker . setLatLng ( pos ); if ( follow ) map . panTo ( pos );}}} catch ( e ){ failed = true ;} render (); setTimeout ( poll , 2000 );}
2026-10-28 10:54:15 -05:00
setInterval ( render , 1000 ); poll ();
</ script >
</ body ></ html >