Skip to content

Positions

A Position is exactly seven fields:

lat, lng, accuracy, speed, bearing, altitude, timestamp

The fields are identical on every platform. Numeric widths differ — Android uses Float for accuracy/speed/bearing (matching android.location.Location); iOS uses Double (matching CLLocation). Convert at the bridge layer if you need uniform widths.

FieldUnitSource
lat, lngdegrees (WGS-84)FusedLocationProviderClient.getLastLocation() / CLLocation.coordinate
accuracymetres (horizontal, 1σ)OS provider’s reported accuracy
speedmetres/secondOS provider — already smoothed/fused
bearingdegrees (0–360, true north)OS provider — direction of travel, not device heading
altitudemetres above WGS-84 ellipsoidOS provider
timestampwall-clockOS provider’s fix time, not the time Beekon received it

This is non-negotiable in v1: Beekon does not modify positions. No Kalman filter, no outlier rejection, no speed clamp, no minimum-accuracy filter. Whatever the OS provider produces is what you get on the stream and in history.

A consequence: adjacent positions can disagree dramatically (urban canyon, tunnel, cold start). That’s the OS doing its honest best — surface it to users via the accuracy field rather than hiding it.

The positions stream is hot — it emits the latest gated position to all subscribers. On Android the buffer is replay-1, DROP_OLDEST, capacity 64 (so a slow consumer drops old emissions but never blocks the producer). iOS uses AsyncStream with equivalent semantics.

// SharedFlow<Position> — replay-1, DROP_OLDEST, buffer 64
LaunchedEffect(Unit) {
Beekon.positions.collect { p ->
Log.d("beekon", "${p.lat}, ${p.lng} acc=${p.accuracy}m speed=${p.speed}m/s")
}
}

The most common shape: drop low-accuracy points when rendering on a map.

Beekon.positions
.filter { it.accuracy <= 25f }
.collect { /* render */ }

The persistence layer still records the raw point — filtering happens in your stream consumer, not in storage.

Two separate read paths:

  • positions stream — real-time, replay-1. Drops emissions older than the last seen one. Only flows while the host process is alive (for Flutter: while the Flutter engine is alive).
  • history(from, to) — durable. Reads the native database (Room/SQLite on Android, GRDB/SQLite on iOS). Survives process death and termination. See Persistence & history.

In practice: render the live stream while the user is on a tracking screen; query history when they open a “trips” view.