feat: log GPS fixes every poll, geocode only on significant movement

Remove the in-memory cache from geocode/nominatim.go (cacheTTL, cacheMu,
cachedLoc, cacheExpiry) so CityCenter always makes a live request when called.
Call frequency is now controlled by the caller rather than a time-based TTL.

In the poll goroutine, log the raw GPS coordinates on every cycle. Track the
last geocoded position and only call CityCenter on the first fix or when the
device has moved more than 5 miles (haversine). WebDAV snapshots are written
every poll cycle using the cached city centroid with fresh weather data.
This commit is contained in:
Adriano Caloiaro 2026-04-25 11:08:10 -06:00
parent fe5fb08fb9
commit 1c8ab19426
No known key found for this signature in database
2 changed files with 38 additions and 39 deletions

View file

@ -6,7 +6,6 @@ import (
"fmt"
"net/http"
"strconv"
"sync"
"time"
)
@ -29,27 +28,12 @@ type nominatimResponse struct {
} `json:"address"`
}
const cacheTTL = 24 * time.Hour
var (
httpClient = &http.Client{Timeout: 10 * time.Second}
cacheMu sync.Mutex
cachedLoc Location
cacheExpiry time.Time
)
var httpClient = &http.Client{Timeout: 10 * time.Second}
// CityCenter reverse-geocodes lat/lon to the centroid of the matched city.
// Results are cached in memory for 24 hours to avoid hammering Nominatim.
// zoom=10 asks Nominatim for city-level granularity; the returned coordinates
// are the centroid of that administrative boundary, not the input coordinates.
func CityCenter(ctx context.Context, lat, lon float64) (Location, error) {
cacheMu.Lock()
defer cacheMu.Unlock()
if time.Now().Before(cacheExpiry) {
return cachedLoc, nil
}
url := fmt.Sprintf(
"https://nominatim.openstreetmap.org/reverse?lat=%f&lon=%f&format=json&zoom=10",
lat, lon,
@ -100,7 +84,5 @@ func CityCenter(ctx context.Context, lat, lon float64) (Location, error) {
name += a.State
}
cachedLoc = Location{Lat: resLat, Lon: resLon, Name: name}
cacheExpiry = time.Now().Add(cacheTTL)
return cachedLoc, nil
return Location{Lat: resLat, Lon: resLon, Name: name}, nil
}

55
main.go
View file

@ -3,6 +3,7 @@ package main
import (
"context"
"log"
"math"
"os"
"strconv"
"sync"
@ -48,6 +49,15 @@ func (c *cache) get() (lat, lon float64, name string, ok bool) {
return c.lat, c.lon, c.name, c.ready
}
func milesApart(lat1, lon1, lat2, lon2 float64) float64 {
const r = 3958.8
lat1r, lat2r := lat1*math.Pi/180, lat2*math.Pi/180
dlat := (lat2 - lat1) * math.Pi / 180
dlon := (lon2 - lon1) * math.Pi / 180
a := math.Sin(dlat/2)*math.Sin(dlat/2) + math.Cos(lat1r)*math.Cos(lat2r)*math.Sin(dlon/2)*math.Sin(dlon/2)
return r * 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
}
func pollInterval() time.Duration {
if s := os.Getenv("POLL_INTERVAL_SECONDS"); s != "" {
if n, err := strconv.Atoi(s); err == nil && n > 0 {
@ -64,30 +74,37 @@ func main() {
wdClient := webdav.NewClientFromEnv()
go func() {
const geocodeTriggerMiles = 5.0
var geocodedLat, geocodedLon float64
var currentCity *geocode.Location
for {
lat, lon, err := gpsd.GetLocation(context.Background())
if err != nil {
log.Printf("location poll error: %v", err)
} else {
city, err := geocode.CityCenter(context.Background(), lat, lon)
if err != nil {
log.Printf("geocode error: %v", err)
} else {
loc.set(city.Lat, city.Lon, city.Name)
log.Printf("location updated: %.6f, %.6f (%s)", city.Lat, city.Lon, city.Name)
if wdClient != nil {
snap := snapshot{
RecordedAt: time.Now().UTC(),
Location: &locationEntry{Lat: city.Lat, Lon: city.Lon, Name: city.Name},
}
if data, ok := wCache.Get(); ok {
snap.Weather = &data
snap.Conditions = weather.Infer(data, city.Lat, city.Lon)
}
if err := wdClient.Append(snap); err != nil {
log.Printf("webdav append error: %v", err)
}
log.Printf("gps location: %.6f, %.6f", lat, lon)
if currentCity == nil || milesApart(lat, lon, geocodedLat, geocodedLon) > geocodeTriggerMiles {
city, err := geocode.CityCenter(context.Background(), lat, lon)
if err != nil {
log.Printf("geocode error: %v", err)
} else {
geocodedLat, geocodedLon = lat, lon
currentCity = &city
loc.set(city.Lat, city.Lon, city.Name)
log.Printf("location updated: %.6f, %.6f (%s)", city.Lat, city.Lon, city.Name)
}
}
if currentCity != nil && wdClient != nil {
snap := snapshot{
RecordedAt: time.Now().UTC(),
Location: &locationEntry{Lat: currentCity.Lat, Lon: currentCity.Lon, Name: currentCity.Name},
}
if data, ok := wCache.Get(); ok {
snap.Weather = &data
snap.Conditions = weather.Infer(data, currentCity.Lat, currentCity.Lon)
}
if err := wdClient.Append(snap); err != nil {
log.Printf("webdav append error: %v", err)
}
}
}