Skip to content

feat: drop implausible-speed (teleport) locations via heuristic filter - #2266

Merged
growse merged 1 commit into
owntracks:masterfrom
mvanhorn:feat/2034-implausible-speed-filter
Jul 22, 2026
Merged

feat: drop implausible-speed (teleport) locations via heuristic filter#2266
growse merged 1 commit into
owntracks:masterfrom
mvanhorn:feat/2034-implausible-speed-filter

Conversation

@mvanhorn

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in implausible-speed (teleport) filter to LocationProcessor.publishLocationMessage. When a new fix would imply travel faster than a configurable maximum ground speed relative to the last published location, the fix is dropped before it is queued for sending or persisted as the current published location.

The filter is gated by a new config-import-only integer preference maxImplausibleSpeedKmh, defaulting to 0, which disables the filter and preserves current behaviour. It sits alongside the existing accuracy and network-vs-gps discard heuristics and follows the same Result.failure plus Timber logging pattern.

Why this matters

Users on the OSS (F-Droid/AOSP) flavour report occasional random published locations several miles from the device's true position while the phone is stationary, drawing long straight-line artifacts on the recorder map (#2034, also referenced in #2053). These come from the network/cell-tower provider supplying low-quality fixes that the OSS build forwards without the aggregation Play Services would normally apply.

On 2026-04-30 maintainer @growse scoped the work into two tracked items: (1) Kalman filtering of non-Play-Services locations, and (2) a heuristic to figure out if something looks weirdly wrong and optionally filter it out. This change implements item 2 only. A teleport fix implies an impossible ground speed relative to the previous fix, so dropping fixes above a user-set km/h ceiling removes the artifacts without touching plausible movement.

Behaviour details:

  • Strictly opt-in: maxImplausibleSpeedKmh = 0 (the default) disables the filter.
  • Never blocks the first fix (no previous published location to compare against).
  • Skips USER and CIRCULAR triggered reports so on-demand and region reports are never suppressed.
  • Guards against zero and negative time deltas, so there is no divide-by-zero and a non-advancing clock never drops a fix.

The pure decision lives in an isImplausibleSpeed(distanceMeters, timeDeltaMillis, maxSpeedKmh) helper so it can be unit tested without Android dependencies.

Testing

Added LocationProcessorTest with JVM-pure unit tests for the decision helper covering: disabled threshold, a plausible slow move, a teleport (about 15 miles in 5 seconds, implying roughly 17,000 km/h), a zero time delta, and a negative time delta.

Gradle was not run locally because this machine has no Java runtime; CI will validate the build and tests.

Refs #2034

@compuguy

compuguy commented Jul 7, 2026

Copy link
Copy Markdown

This is also a common and annoying issue with the Google Play build of Owntracks. So this pull request would be greatly appreciated! 👍

@growse

growse commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

@jpmens what's your thoughts here? It's a new preference that allows a user to discard obviously inaccurate points based on a user-defined implausable speed.

Coupled with the work I'm doing on passing the locations through a kalman, I think this could help improve location data quality, but interested in your view.

@jpmens

jpmens commented Jul 10, 2026

Copy link
Copy Markdown
Member

I think this could be quite useful to suppress "teleporting" (but I'm not much of a scifi person so I cannot really judge that :-) )

I think we should take this, as long as you are satisfied with the code, @growse, and I would make the knob configuration-editor - only, i.e. I suggest not adding a UI, not that it would hurt.

@growse

growse commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Could you rebase?

@mvanhorn

Copy link
Copy Markdown
Contributor Author

@jpmens good news - it's already configuration-editor-only. The PR adds no settings UI: maxImplausibleSpeedKmh is a config-import-only integer preference (default 0, disabled), and the only UI-adjacent change is the PreferenceKeyLabels entry so the key renders with a readable label in the config editor. @growse happy to adjust anything else once you've settled on direction; it should compose fine with the Kalman work since this filter only drops the obvious teleports before publishing.

@mvanhorn
mvanhorn force-pushed the feat/2034-implausible-speed-filter branch from 97d6ad9 to 9d219d2 Compare July 11, 2026 18:52
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Rebased onto master in 9d219d2. The only conflict was additive in DefaultsProvider.kt (the new useGNSSInSignificantMonitoringMode default next to this PR's maxImplausibleSpeedKmh); kept both. GitHub reports the branch mergeable again.

pletch added a commit to pletch/android-nxt that referenced this pull request Jul 11, 2026
…-pass fixes

New location-quality features on the continuous DEFAULT stream:
- Implausible-speed gate: drops fixes whose implied speed from the last
  published location exceeds maxImplausibleSpeedKmh (default 1000; key matches
  upstream PR owntracks#2266 for config compatibility). Keeps the last rejected fix so a
  persistent relocation corroborates itself instead of being locked out by a
  bad anchor.
- Experimental Kalman smoothing (smoothLocationsWithKalmanFilter): single-state
  filter in the location-kalman module smooths lat/lon only — sensor accuracy
  survives untouched so the ignoreInaccurateLocations gate, waypoint-transition
  tolerance, and published acc stay truthful. Process noise adapts to the
  fix-implied displacement so speedless (network) fixes can't lag the filter.
- tst validation: reject non-positive or implausibly-future (>1 day) timestamps.
- Driving-boost hardening: 20-min watchdog reverts a boost stuck on when
  GPS/AR go silent; toggling the feature off mid-drive reverts immediately;
  the entry dwell re-checks the toggle; forced reverts clear a speed-engaged
  "automotive" motionactivities so it can't go stale.
- MQTT robustness: per-client generation tags on connected/disconnected/publish
  callbacks no-op superseded clients (state clobber + zombie-client guards);
  reconnect jobs use KEEP to preserve WorkManager backoff, with expedited
  REPLACE nudges for queued work and publish timeouts.
- ContactsActivity: collect contact events in repeatOnLifecycle(STARTED) with a
  reconcile on re-entry (stops background reverse-geocoding; upstream candidate).
- Experimental preferences screen is now always visible; docs updated.
- CI runs location-kalman:test; unit tests for the gate, deserializer,
  controller, and filter.

Full findings log in IMPROVEMENT_PROJECT.md ("Review findings 2026-07-11").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@growse

growse commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

/run-tests

@github-actions

Copy link
Copy Markdown

Build & Test complete. ✨
View workflow run

@growse
growse added this pull request to the merge queue Jul 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 20, 2026
@growse growse self-assigned this Jul 20, 2026
@growse

growse commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Sorry, one more rebase. There's shuffling in LocationProcessor so just need to fix the merge conflicts.

@growse growse assigned mvanhorn and unassigned growse Jul 20, 2026
@growse
growse self-requested a review July 20, 2026 11:08
Add an opt-in implausible-speed filter in LocationProcessor.publishLocationMessage
that drops a new fix when it implies travel faster than a configurable maximum
ground speed relative to the last published location. Gated by a new
config-import-only integer preference maxImplausibleSpeedKmh, defaulting to 0
(disabled) so existing behaviour is unchanged.

The filter sits alongside the existing accuracy and network-vs-gps discard
heuristics, skips USER and CIRCULAR triggered reports, never blocks the first
fix, and guards against zero or negative time deltas. The pure decision lives in
an isImplausibleSpeed helper with JVM unit tests.

Refs owntracks#2034

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ye471qeNk1R7b7aQsZF5s
@mvanhorn
mvanhorn force-pushed the feat/2034-implausible-speed-filter branch from ec20164 to 1273494 Compare July 21, 2026 04:28
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Rebased onto current master and resolved the LocationProcessor conflicts. The branch is now a single commit on top of master.

@growse

growse commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

/run-tests

@github-actions

Copy link
Copy Markdown

Build & Test complete. ✨
View workflow run

@growse
growse added this pull request to the merge queue Jul 22, 2026
Merged via the queue into owntracks:master with commit 434baec Jul 22, 2026
1 check passed
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Thanks for taking the teleport filter, @growse - implausible-speed points getting dropped should clean up a lot of tracks.

growse added a commit that referenced this pull request Jul 27, 2026
The implausible-speed filter added in #2266 exempted USER and CIRCULAR
triggers but not RESPONSE, so a remote "reportLocation" request could be
silently dropped if the reply looked fast relative to the last published
fix - unlike every other place in this file that treats RESPONSE as one of
the "must always get an answer" triggers (see responseMessageTypes). The
filter's exemption now reuses that same set instead of hand-picking two of
its three members.

Also de-duplicates the speed calculation (previously computed once inside
isImplausibleSpeed and again by hand for the log message), and adds a
regression test exercising the trigger-exemption behavior at the
publishLocationMessage level, which the existing pure-function tests
couldn't catch since they don't know about triggers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants