fix: require 50+ mile distance to classify activity segment as flying (avoids GPS drift false positives)
This commit is contained in:
@@ -114,27 +114,33 @@ def _max_speed_from_path(timeline_path):
|
|||||||
return max_speed
|
return max_speed
|
||||||
|
|
||||||
|
|
||||||
def _speed_from_activity_segment(activity, seg_start_ts, seg_end_ts):
|
MIN_FLIGHT_DISTANCE_MILES = 50 # Shorter than this cannot be a flight
|
||||||
"""Calculate implied speed from an activity segment's start/end coords and timestamps.
|
|
||||||
|
|
||||||
Flights appear as UNKNOWN_ACTIVITY_TYPE with valid start/end latLng but no
|
def _speed_from_activity_segment(activity, seg_start_ts, seg_end_ts):
|
||||||
timelinePath GPS trail, so we derive speed directly from the segment geometry.
|
"""Detect flights from an activity segment's start/end coords and timestamps.
|
||||||
|
|
||||||
|
Flights show up as UNKNOWN_ACTIVITY_TYPE with widely-separated start/end latLng.
|
||||||
|
We require at least 50 miles distance to avoid mis-classifying GPS drift as flying
|
||||||
|
(short jumps can imply absurdly high speeds due to imprecise timestamps).
|
||||||
|
|
||||||
|
Returns (distance_miles, speed_mph) or (0, 0) if not usable.
|
||||||
"""
|
"""
|
||||||
start_latlng = activity.get('start', {}).get('latLng', '')
|
start_latlng = activity.get('start', {}).get('latLng', '')
|
||||||
end_latlng = activity.get('end', {}).get('latLng', '')
|
end_latlng = activity.get('end', {}).get('latLng', '')
|
||||||
if not start_latlng or not end_latlng:
|
if not start_latlng or not end_latlng:
|
||||||
return 0.0
|
return 0.0, 0.0
|
||||||
slat, slng = _parse_latlng(start_latlng)
|
slat, slng = _parse_latlng(start_latlng)
|
||||||
elat, elng = _parse_latlng(end_latlng)
|
elat, elng = _parse_latlng(end_latlng)
|
||||||
if slat is None or elat is None:
|
if slat is None or elat is None:
|
||||||
return 0.0
|
return 0.0, 0.0
|
||||||
if not seg_start_ts or not seg_end_ts:
|
if not seg_start_ts or not seg_end_ts:
|
||||||
return 0.0
|
return 0.0, 0.0
|
||||||
dt_hours = (seg_end_ts - seg_start_ts).total_seconds() / 3600.0
|
dt_hours = (seg_end_ts - seg_start_ts).total_seconds() / 3600.0
|
||||||
if dt_hours <= 0:
|
if dt_hours <= 0:
|
||||||
return 0.0
|
return 0.0, 0.0
|
||||||
dist_miles = _haversine_miles(slat, slng, elat, elng)
|
dist_miles = _haversine_miles(slat, slng, elat, elng)
|
||||||
return dist_miles / dt_hours
|
speed = dist_miles / dt_hours
|
||||||
|
return dist_miles, speed
|
||||||
|
|
||||||
|
|
||||||
class WtImportTimelineWizard(models.TransientModel):
|
class WtImportTimelineWizard(models.TransientModel):
|
||||||
@@ -300,14 +306,13 @@ class WtImportTimelineWizard(models.TransientModel):
|
|||||||
# Explicit type wins (driving, walking, transit, cycling)
|
# Explicit type wins (driving, walking, transit, cycling)
|
||||||
pending_mode = mode
|
pending_mode = mode
|
||||||
else:
|
else:
|
||||||
# UNKNOWN type — infer from segment distance / elapsed time
|
# UNKNOWN type — only infer flying if distance is large enough
|
||||||
|
# to rule out GPS drift (short jumps can look fast due to imprecise timestamps)
|
||||||
seg_start = _parse_ts(seg.get('startTime'))
|
seg_start = _parse_ts(seg.get('startTime'))
|
||||||
seg_end = _parse_ts(seg.get('endTime'))
|
seg_end = _parse_ts(seg.get('endTime'))
|
||||||
speed = _speed_from_activity_segment(activity, seg_start, seg_end)
|
dist, speed = _speed_from_activity_segment(activity, seg_start, seg_end)
|
||||||
if speed > 0:
|
if dist >= MIN_FLIGHT_DISTANCE_MILES and speed > 150:
|
||||||
inferred = _speed_to_travel_mode(speed)
|
pending_mode = 'flying'
|
||||||
if inferred != 'unknown':
|
|
||||||
pending_mode = inferred
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# --- visit segment: the stop we record ---
|
# --- visit segment: the stop we record ---
|
||||||
|
|||||||
Reference in New Issue
Block a user