# Location and GPS

> Location (live), LocationService (background) and GPSThread (continuous) — with the right units.

Source: https://crossfly.dev/docs/data-and-services/device-location-gps — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Location and GPS

Three pieces, for three needs. The callbacks fire **off the paint thread** → return with
`Component.onUI(...)`.

| Class | For |
|---|---|
| `Location` | **live** reading (singleton — one instance at a time) |
| `LocationService` | **background** collection, writing to a file ("read and clear") |
| `GPSThread` | **continuous tracking** with callbacks |

## `Location` — live reading
```java
Location loc = new Location();          // opens the GPS (throws IOException)
try {
   if (loc.retrieveGPSData()) {         // false = no signal/error
      double lat = Location.getLatitude();
      double lon = Location.getLongitude();
      double vel = Location.velocity;   // ⚠️ in KNOTS
      if (vel != Location.INVALID) { ... }   // check INVALID before trusting it
   } else {
      Debug.log(Location.lowSignalReason);
   }
} finally {
   loc.stop();                          // ⚠️ ALWAYS stop — forgetting drains the battery
}
```
Static fields: `location` (`double[]` lat/lon), `velocity` (knots), `pdop`, `satellites`, `direction`,
`lastFix`, and the marker `INVALID = Double.MIN_VALUE`.

## `LocationService` — background collection
Writes fixes to a file; you read it and the file is deleted ("read and clear" pattern):
```java
LocationService.start();   // starts collecting (no-op on desktop)
// … later …
List<LocationService.GPSLogEntry> fixes = LocationService.readAll();  // reads AND deletes the file
for (LocationService.GPSLogEntry e : fixes) {
   double vel = e.velocity;   // ⚠️ here in METERS PER SECOND (m/s) — different from Location!
}
LocationService.stop();
```

## ⚠️ `velocity` units differ!
| Source | Unit |
|---|---|
| `Location.velocity` (live) | **knots** |
| `GPSLogEntry.velocity` (history) | **meters per second (m/s)** |

Confusing the two is a silent conversion bug. Both use `INVALID` for "no reading".

## `GPSThread` — continuous tracking
```java
GPSThread gps = new GPSThread(
   true,                 // highPrecision: GPS only (false = GPS+cell+WiFi)
   2000,                 // interval in ms
   () -> Component.onUI(() -> label.setText(Location.getLatitude() + ", " + Location.getLongitude())),
   erro -> Debug.log("GPS erro: " + erro)
);                        // starts right away on construction
// … when leaving the screen:
gps.stopGps();            // stops and waits for the thread to finish
```

## Best practices
- **Always call `stop()`/`stopGps()`** on exit — GPS left on **drains the battery**.
- Check `INVALID`/`pdop` before trusting the position.
- Watch the **units** of `velocity` (knots vs m/s).
- Callbacks return to the UI with `onUI(...)`.

## Common mistakes
- Forgetting `stop()` → drained battery.
- Treating `Location.velocity` as m/s (it is **knots**).
- Trusting the position without checking `INVALID`.

## See also
- [LocationPicker](/docs/ui/widgets/locationpicker) · [Maps](/docs/data-and-services/maps) · [Per-platform permissions](/docs/platforms)
