added segments
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user