added segments

This commit is contained in:
2026-01-09 12:10:58 -08:00
parent 55e37fbca8
commit 67357b5038
55 changed files with 2310 additions and 75 deletions

View File

@@ -11,10 +11,8 @@ from ..utils.config import config
# New Sync Imports
from ..services.job_manager import job_manager
from ..models.activity_state import GarminActivityState
import fitdecode
import io
import xml.etree.ElementTree as ET
from datetime import datetime
from ..services.parsers import extract_points_from_file
router = APIRouter()
@@ -480,64 +478,7 @@ async def get_sync_status_summary(db: Session = Depends(get_db)):
return {}
def _extract_points_from_fit(file_content: bytes) -> List[List[float]]:
"""
Extract [lon, lat] points from a FIT file content.
Returns a list of [lon, lat].
"""
points = []
try:
with io.BytesIO(file_content) as f:
with fitdecode.FitReader(f) as fit:
for frame in fit:
if frame.frame_type == fitdecode.FIT_FRAME_DATA and frame.name == 'record':
# Check for position_lat and position_long
# Garmin stores lat/long as semicircles. Convert to degrees: semicircle * (180 / 2^31)
if frame.has_field('position_lat') and frame.has_field('position_long'):
lat_sc = frame.get_value('position_lat')
lon_sc = frame.get_value('position_long')
if lat_sc is not None and lon_sc is not None:
lat = lat_sc * (180.0 / 2**31)
lon = lon_sc * (180.0 / 2**31)
points.append([lon, lat])
except Exception as e:
logger.error(f"Error parsing FIT file: {e}")
# Return what we have or empty
return points
def _extract_points_from_tcx(file_content: bytes) -> List[List[float]]:
"""
Extract [lon, lat] points from a TCX file content.
"""
points = []
try:
# TCX is XML
# Namespace usually exists
root = ET.fromstring(file_content)
# Namespaces are annoying in ElementTree, usually {http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}
# We can just iterate and ignore namespace or handle it.
# Let's try ignoring namespace by using local-name() in xpath if lxml, but this is stdlib ET.
# Just strip namespace for simplicity
for trkpt in root.iter():
if trkpt.tag.endswith('Trackpoint'):
lat = None
lon = None
for child in trkpt.iter():
if child.tag.endswith('LatitudeDegrees'):
try: lat = float(child.text)
except: pass
elif child.tag.endswith('LongitudeDegrees'):
try: lon = float(child.text)
except: pass
if lat is not None and lon is not None:
points.append([lon, lat])
except Exception as e:
logger.error(f"Error parsing TCX file: {e}")
return points
@router.get("/activities/{activity_id}/geojson")
async def get_activity_geojson(activity_id: str, db: Session = Depends(get_db)):
@@ -550,14 +491,9 @@ async def get_activity_geojson(activity_id: str, db: Session = Depends(get_db)):
raise HTTPException(status_code=404, detail="Activity or file content not found")
points = []
if activity.file_type == 'fit':
points = _extract_points_from_fit(activity.file_content)
elif activity.file_type == 'tcx':
points = _extract_points_from_tcx(activity.file_content)
if activity.file_type in ['fit', 'tcx']:
points = extract_points_from_file(activity.file_content, activity.file_type)
else:
# Try FIT or TCX anyway?
# Default to FIT check headers?
# For now just log warning
logger.warning(f"Unsupported file type for map: {activity.file_type}")
if not points: