mirror of
https://github.com/acaloiaro/roam-location
synced 2026-07-21 10:12:21 +00:00
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.
116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/acaloiaro/roam-location/geocode"
|
|
"github.com/acaloiaro/roam-location/gpsd"
|
|
"github.com/acaloiaro/roam-location/service"
|
|
"github.com/acaloiaro/roam-location/weather"
|
|
"github.com/acaloiaro/roam-location/webdav"
|
|
)
|
|
|
|
type locationEntry struct {
|
|
Lat float64 `json:"lat"`
|
|
Lon float64 `json:"lon"`
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
type snapshot struct {
|
|
RecordedAt time.Time `json:"recorded_at"`
|
|
Location *locationEntry `json:"location,omitempty"`
|
|
Weather *weather.Data `json:"weather,omitempty"`
|
|
Conditions *weather.Condition `json:"conditions,omitempty"`
|
|
}
|
|
|
|
type cache struct {
|
|
mu sync.RWMutex
|
|
lat float64
|
|
lon float64
|
|
name string
|
|
ready bool
|
|
}
|
|
|
|
func (c *cache) set(lat, lon float64, name string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.lat, c.lon, c.name, c.ready = lat, lon, name, true
|
|
}
|
|
|
|
func (c *cache) get() (lat, lon float64, name string, ok bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
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 {
|
|
return time.Duration(n) * time.Second
|
|
}
|
|
}
|
|
return 30 * time.Second
|
|
}
|
|
|
|
func main() {
|
|
loc := &cache{}
|
|
wCache := weather.NewCache()
|
|
interval := pollInterval()
|
|
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 {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
time.Sleep(interval)
|
|
}
|
|
}()
|
|
|
|
service.Listen(loc.get, wCache)
|
|
}
|