mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-09 07:47:58 -05:00
commitd32e3d8fdcMerge:d722e793db959207Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:52:35 2025 +0100 Merge remote-tracking branch 'origin/l10n_pre-release' into pre-release commitdb959207c1Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:51:52 2025 +0100 New translations activitymandabovepillscomponent.json (Portuguese) commit595256934eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:50:51 2025 +0100 New translations editactivitymodalcomponent.json (Portuguese) commitc659dcd84fAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:50:46 2025 +0100 New translations activitysummarycomponent.json (Portuguese) commit1f8e699753Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:41 2025 +0100 New translations editactivitymodalcomponent.json (Portuguese) commitc93884c2c7Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:40 2025 +0100 New translations editactivitymodalcomponent.json (German) commit9a59c21a75Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:39 2025 +0100 New translations editactivitymodalcomponent.json (Catalan) commit72ea5b5467Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:38 2025 +0100 New translations editactivitymodalcomponent.json (Spanish) commit7f309e9e32Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:37 2025 +0100 New translations editactivitymodalcomponent.json (French) commit0ed4e9134dAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:36 2025 +0100 New translations activitysummarycomponent.json (Portuguese) commitb22b45f185Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:35 2025 +0100 New translations activitysummarycomponent.json (Dutch) commit78a49d35afAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:34 2025 +0100 New translations activitysummarycomponent.json (German) commitd03f260e75Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:33 2025 +0100 New translations activitysummarycomponent.json (Catalan) commit1203787f8aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:32 2025 +0100 New translations activitysummarycomponent.json (Spanish) commit9a948aea26Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:31 2025 +0100 New translations activitysummarycomponent.json (French) commitcf2a0e8bddAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:30 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commitd722e79394Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:28:40 2025 +0100 Fix Docker image tag & bump frontend version [docker] fix image tag on docker-compose.yml.example [frontend] bump version on frontend commite15f4e89c7Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:18:17 2025 +0100 Bump dependencies commit319b83ae0fAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:10:53 2025 +0100 Add indoor cycling support & fix config/logging issues [backend] fixed invalid default value for JAEGER_ENABLED [backend] added support for activity type indoor_cycling [backend] fixed logging spacing and added docs to logger [docker] fixed logging spacing [docs] added support for activity type indoor_cycling [frontend] added support for activity type indoor_cycling [frontend] re added activityMaxHR to activitySummaryComponent.json commitf3e298300fMerge:c9e006a710668d3eAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 11:20:16 2025 +0100 Merge branch 'docker_immutable_feature' into pre-release commit10668d3ed6Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 09:26:34 2025 +0100 Update env.js commitb138f81f1eAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Tue Jun 24 23:14:58 2025 +0100 Update database.py commit5c7c81f123Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Tue Jun 24 23:03:14 2025 +0100 Refactor env var usage and runtime config for frontend/backend Backend now uses core.config for environment variables with sensible defaults, reducing direct os.environ access and improving robustness. Dockerfile and start.sh were updated to remove hardcoded env vars and generate a runtime env.js for frontend configuration. Frontend code now reads ENDURAIN_HOST from window.env instead of Vite env, enabling runtime configuration. Obsolete .env file was removed, and documentation was updated to fix a typo in JAEGER_PORT. commitbb2ae4f548Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:27 2025 +0100 New translations activitymandabovepillscomponent.json (Portuguese) commit64f438000fAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:26 2025 +0100 New translations activitymandabovepillscomponent.json (German) commit4129e66768Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:25 2025 +0100 New translations activitymandabovepillscomponent.json (Catalan) commit7c37b2c375Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:23 2025 +0100 New translations activitymandabovepillscomponent.json (Spanish) commited236a0ee7Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:22 2025 +0100 New translations activitymandabovepillscomponent.json (French) commit825ef122d7Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:21 2025 +0100 New translations activitysummarycomponent.json (Portuguese) commitd04742caeeAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:20 2025 +0100 New translations activitysummarycomponent.json (Dutch) commit187b56221bAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:19 2025 +0100 New translations activitysummarycomponent.json (German) commit83847a2d0cAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:18 2025 +0100 New translations activitysummarycomponent.json (Catalan) commit8a2417360aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:17 2025 +0100 New translations activitysummarycomponent.json (Spanish) commit7bf9dfdc66Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:16 2025 +0100 New translations activitysummarycomponent.json (French) commit683b1fd260Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:15 2025 +0100 New translations activitymandabovepillscomponent.json (Dutch) commitc9e006a757Merge:1a5f5cca6cfe4999Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 16:28:37 2025 +0100 Merge branch 'pr/199' into pre-release commit6cfe4999e1Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 16:27:44 2025 +0100 Add HR Zones chart to mobile, move logic to chartUtils [frontend] moved chart functions to chartUtils file [frontend] added HR Zones Bar Chart to mobile view commite475138d11Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 16:15:42 2025 +0100 Refactor HR Zones chart, clean up ActivitySummary [frontend] moved HR Zones graph away from ActivitySummaryComponent [frontend] removed duplicated translation [frontend] reverted changes on ActivitySummaryComponent since chart was removed [frontend] moved chart to ActivityMandAbovePillsComponent [frontend] small adjustments to BarChart commita7d8418d51Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 15:04:05 2025 +0100 Improve formatting, docstrings & fix activityActivityStreams prop [backend] fixed formatting [backend] changed existing docstrings to be more complete [backend] changed imports to be consistent with rest of the code [frontend] fixed formatting [frontend] set new prop on ActivitySummaryComponent to be not required because it is not always necessary (HomeView) [frontend] fixed issue onMounted on ActivitySummaryComponent when new prop is null commit1a5f5cca86Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 13:52:31 2025 +0100 Fix for docs commit1960c3153aMerge:c895ae0d935a5d43Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 13:37:08 2025 +0100 Merge branch 'pr/203' into pre-release commitc895ae0d83Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 12:33:21 2025 +0100 New translations activitybellowmpillscomponent.json (Dutch) commit5338c95879Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 12:33:20 2025 +0100 New translations activitylapscomponent.json (Dutch) commit7d3930bfbaAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 12:33:19 2025 +0100 New translations generalitems.json (Dutch) commit0838b6cf6eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:11:34 2025 +0100 New translations activitybellowmpillscomponent.json (Dutch) commit9776b6461aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:11:31 2025 +0100 New translations activitylapscomponent.json (Dutch) commit8b2b6c6fc8Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:23 2025 +0100 New translations stravacallbackview.json (Dutch) commit6acf86fb47Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:09 2025 +0100 New translations summaryview.json (Dutch) commit79708f1aa9Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:07 2025 +0100 New translations settingsuserprofilezonecomponent.json (Dutch) commit5e93661fa0Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:06 2025 +0100 New translations settingsintegrationszonecomponent.json (Dutch) commitbfa7093fc4Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:04 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commit27036db437Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:03 2025 +0100 New translations navbarcomponent.json (Dutch) commitc4143435d1Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:09:48 2025 +0100 New translations activityview.json (Dutch) commit8b1cc16bc6Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:09:47 2025 +0100 New translations loginview.json (Dutch) commit3cd447368eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:09:46 2025 +0100 New translations generalitems.json (Dutch) commitbac2b90b88Merge:d0f850dd13afbe86Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 11:06:59 2025 +0100 Merge remote-tracking branch 'origin/l10n_pre-release' into pre-release commitd0f850dd0eMerge:ed4c87d4426500ffAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 11:04:10 2025 +0100 Merge branch 'pr/208' into pre-release commit13afbe86aeAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:00:40 2025 +0100 New translations gearslistcomponent.json (German) commit59ad9170b0Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 10:59:50 2025 +0100 New translations gearview.json (Catalan) commit426500ff2eAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 11:59:36 2025 +0200 fixing some typos commited4c87d407Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 10:55:01 2025 +0100 Silence StravaLib token warnings + bump dependencies commit6f24ac158fAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 01:10:40 2025 +0200 fully translated commit386e5ae853Author: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 00:57:42 2025 +0200 done upto strava commit56cb31288dAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 00:54:16 2025 +0200 next to translate - gears commit8d3158ef6cAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 00:25:31 2025 +0200 initial commit for fork commit935a5d43b5Author: Fredrik Fyksen <fredrik@fyksen.me> Date: Wed Jun 18 15:06:48 2025 +0200 Updated hosting guide commit56a7ff881fAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 21:02:22 2025 +0100 New translations summaryview.json (Dutch) commit9c0acab25aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:49 2025 +0100 New translations activityview.json (Dutch) commitbfe8ae1d14Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:48 2025 +0100 New translations summaryview.json (Dutch) commit0c0a375d1aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:47 2025 +0100 New translations activitymandabovepillscomponent.json (Dutch) commit8c4619fc8dAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:46 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commit9748f7776fAuthor: Zuhdi <rccheattest2@gmail.com> Date: Tue Jun 17 22:57:52 2025 +0700 feat: change hr zone to bar chart - return transform_activity_streams to get_public_activity_stream_by_type - commitfcad6ea3e5Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:27 2025 +0100 New translations summaryview.json (Dutch) commit515427d89bAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:26 2025 +0100 New translations activitiesview.json (Dutch) commit50a76062f0Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:24 2025 +0100 New translations activitymandabovepillscomponent.json (Dutch) commit95c6b9ef0eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:23 2025 +0100 New translations searchview.json (Dutch) commit503b943a2eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:22 2025 +0100 New translations settingsuserprofilezonecomponent.json (Dutch) commitb1884f4fe9Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:21 2025 +0100 New translations settingsintegrationszonecomponent.json (Dutch) commit04c899a3a4Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:20 2025 +0100 New translations gearslistcomponent.json (Dutch) commit5163263b88Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:19 2025 +0100 New translations gearsaddeditgearmodalcomponent.json (Dutch) commitae3943d352Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:18 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commit01a900b166Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:16 2025 +0100 New translations gearview.json (Dutch) commite1c006fd7eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:15 2025 +0100 New translations loginview.json (Dutch) commit1f406e5022Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:14 2025 +0100 New translations generalitems.json (Dutch) commit9b43447128Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 12:53:54 2025 +0100 New translations navbarcomponent.json (Dutch) commitc503c35607Author: Zuhdi <rccheattest2@gmail.com> Date: Tue Jun 17 08:01:37 2025 +0700 chore: fix review commit85c8f9410cAuthor: Zuhdi <rccheattest2@gmail.com> Date: Tue Jun 17 07:15:01 2025 +0700 fix: handle when average_heartrate and max_heartrate is null when import from strava commit7e9b3227c4Author: Zuhdi <rccheattest2@gmail.com> Date: Sun Jun 15 11:44:39 2025 +0700 feat: change default value for hrZones commitbd1ec74968Author: Zuhdi <rccheattest2@gmail.com> Date: Sun Jun 15 11:32:40 2025 +0700 feat: show HR based on zone that calculated from user birthdate (if any)
612 lines
26 KiB
Python
612 lines
26 KiB
Python
import gpxpy
|
|
from geopy.distance import geodesic
|
|
from timezonefinder import TimezoneFinder
|
|
from sqlalchemy.orm import Session
|
|
from datetime import datetime
|
|
|
|
from fastapi import HTTPException, status
|
|
|
|
import activities.activity.utils as activities_utils
|
|
import activities.activity.schema as activities_schema
|
|
|
|
import users.user_default_gear.utils as user_default_gear_utils
|
|
|
|
import users.user_privacy_settings.schema as users_privacy_settings_schema
|
|
|
|
import core.logger as core_logger
|
|
import core.config as core_config
|
|
|
|
|
|
def parse_gpx_file(
|
|
file: str,
|
|
user_id: int,
|
|
user_privacy_settings: users_privacy_settings_schema.UsersPrivacySettings,
|
|
db: Session,
|
|
) -> dict:
|
|
try:
|
|
# Create an instance of TimezoneFinder
|
|
tf = TimezoneFinder()
|
|
timezone = core_config.TZ
|
|
|
|
# Initialize default values for various variables
|
|
activity_type = "Workout"
|
|
calories = None
|
|
distance = 0
|
|
avg_hr = None
|
|
max_hr = None
|
|
avg_cadence = None
|
|
max_cadence = None
|
|
first_waypoint_time = None
|
|
last_waypoint_time = None
|
|
avg_power = None
|
|
max_power = None
|
|
ele_gain = None
|
|
ele_loss = None
|
|
np = None
|
|
avg_speed = None
|
|
max_speed = None
|
|
activity_name = "Workout"
|
|
process_one_time_fields = 0
|
|
gear_id = None
|
|
|
|
city = None
|
|
town = None
|
|
country = None
|
|
pace = 0
|
|
|
|
# Arrays to store waypoint data
|
|
lat_lon_waypoints = []
|
|
ele_waypoints = []
|
|
hr_waypoints = []
|
|
cad_waypoints = []
|
|
power_waypoints = []
|
|
vel_waypoints = []
|
|
pace_waypoints = []
|
|
|
|
# Initialize variables to store previous latitude and longitude
|
|
prev_latitude, prev_longitude = None, None
|
|
|
|
# Initialize variables to store whether elevation, power, heart rate, cadence, and velocity are set
|
|
is_lat_lon_set = False
|
|
is_elevation_set = False
|
|
is_power_set = False
|
|
is_heart_rate_set = False
|
|
is_cadence_set = False
|
|
is_velocity_set = False
|
|
|
|
# Parse the GPX file
|
|
with open(file, "r") as gpx_file:
|
|
gpx = gpxpy.parse(gpx_file)
|
|
|
|
if gpx.tracks:
|
|
# Iterate over tracks in the GPX file
|
|
for track in gpx.tracks:
|
|
# Set activity name and type if available
|
|
activity_name = track.name if track.name else "Workout"
|
|
activity_type = track.type if track.type else "Workout"
|
|
|
|
if track.segments:
|
|
# Iterate over segments in each track
|
|
for segment in track.segments:
|
|
# Iterate over points in each segment
|
|
for point in segment.points:
|
|
# Extract latitude and longitude from the point
|
|
latitude, longitude = point.latitude, point.longitude
|
|
|
|
# Calculate distance between waypoints
|
|
if (
|
|
prev_latitude is not None
|
|
and prev_longitude is not None
|
|
):
|
|
distance += geodesic(
|
|
(prev_latitude, prev_longitude),
|
|
(latitude, longitude),
|
|
).meters
|
|
|
|
# Extract elevation, time, and location details
|
|
elevation, time = point.elevation, point.time
|
|
|
|
if elevation != 0:
|
|
is_elevation_set = True
|
|
|
|
if first_waypoint_time is None:
|
|
first_waypoint_time = point.time
|
|
|
|
if process_one_time_fields == 0:
|
|
# Use geocoding API to get city, town, and country based on coordinates
|
|
location_data = (
|
|
activities_utils.location_based_on_coordinates(
|
|
latitude, longitude
|
|
)
|
|
)
|
|
|
|
# Extract city, town, and country from location data
|
|
if location_data:
|
|
city = location_data["city"]
|
|
town = location_data["town"]
|
|
country = location_data["country"]
|
|
|
|
process_one_time_fields = 1
|
|
|
|
# Extract heart rate, cadence, and power data from point extensions
|
|
heart_rate, cadence, power = 0, 0, 0
|
|
|
|
if point.extensions:
|
|
# Iterate through each extension element
|
|
for extension in point.extensions:
|
|
if extension.tag.endswith(
|
|
"TrackPointExtension"
|
|
):
|
|
hr_element = extension.find(
|
|
".//{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}hr"
|
|
)
|
|
if hr_element is not None:
|
|
heart_rate = hr_element.text
|
|
cad_element = extension.find(
|
|
".//{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}cad"
|
|
)
|
|
if cad_element is not None:
|
|
cadence = cad_element.text
|
|
elif extension.tag.endswith("power"):
|
|
# Extract 'power' value
|
|
power = extension.text
|
|
|
|
# Check if heart rate, cadence, power are set
|
|
if heart_rate != 0:
|
|
is_heart_rate_set = True
|
|
|
|
if cadence != 0:
|
|
is_cadence_set = True
|
|
|
|
if power != 0:
|
|
is_power_set = True
|
|
else:
|
|
power = None
|
|
|
|
# Calculate instant speed, pace, and update waypoint arrays
|
|
instant_speed = (
|
|
activities_utils.calculate_instant_speed(
|
|
last_waypoint_time,
|
|
time,
|
|
latitude,
|
|
longitude,
|
|
prev_latitude,
|
|
prev_longitude,
|
|
)
|
|
)
|
|
|
|
# Calculate instance pace
|
|
instant_pace = 0
|
|
if instant_speed > 0:
|
|
instant_pace = 1 / instant_speed
|
|
is_velocity_set = True
|
|
|
|
timestamp = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
|
|
# Append waypoint data to respective arrays
|
|
if latitude is not None and longitude is not None:
|
|
lat_lon_waypoints.append(
|
|
{
|
|
"time": timestamp,
|
|
"lat": latitude,
|
|
"lon": longitude,
|
|
}
|
|
)
|
|
is_lat_lon_set = True
|
|
|
|
activities_utils.append_if_not_none(
|
|
ele_waypoints, timestamp, elevation, "ele"
|
|
)
|
|
activities_utils.append_if_not_none(
|
|
hr_waypoints, timestamp, heart_rate, "hr"
|
|
)
|
|
activities_utils.append_if_not_none(
|
|
cad_waypoints, timestamp, cadence, "cad"
|
|
)
|
|
activities_utils.append_if_not_none(
|
|
power_waypoints, timestamp, power, "power"
|
|
)
|
|
activities_utils.append_if_not_none(
|
|
vel_waypoints, timestamp, instant_speed, "vel"
|
|
)
|
|
activities_utils.append_if_not_none(
|
|
pace_waypoints, timestamp, instant_pace, "pace"
|
|
)
|
|
|
|
# Update previous latitude, longitude, and last waypoint time
|
|
prev_latitude, prev_longitude, last_waypoint_time = (
|
|
latitude,
|
|
longitude,
|
|
time,
|
|
)
|
|
else:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid GPX file - no segments found in the GPX file",
|
|
)
|
|
else:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid GPX file - no tracks found in the GPX file",
|
|
)
|
|
|
|
# Calculate elevation gain/loss, pace, average speed, and average power
|
|
if ele_waypoints:
|
|
ele_gain, ele_loss = activities_utils.compute_elevation_gain_and_loss(
|
|
elevations=ele_waypoints
|
|
)
|
|
|
|
pace = activities_utils.calculate_pace(
|
|
distance, first_waypoint_time, last_waypoint_time
|
|
)
|
|
|
|
# Activity type
|
|
activity_type = activities_utils.define_activity_type(activity_type)
|
|
|
|
gear_id = user_default_gear_utils.get_user_default_gear_by_activity_type(
|
|
user_id, activity_type, db
|
|
)
|
|
|
|
# Calculate average and maximum heart rate
|
|
if hr_waypoints:
|
|
avg_hr, max_hr = activities_utils.calculate_avg_and_max(hr_waypoints, "hr")
|
|
|
|
# Calculate average and maximum cadence
|
|
if cad_waypoints:
|
|
avg_cadence, max_cadence = activities_utils.calculate_avg_and_max(
|
|
cad_waypoints, "cad"
|
|
)
|
|
|
|
# Calculate average and maximum velocity
|
|
if vel_waypoints:
|
|
avg_speed, max_speed = activities_utils.calculate_avg_and_max(
|
|
vel_waypoints, "vel"
|
|
)
|
|
|
|
# Calculate average and maximum power
|
|
if power_waypoints:
|
|
avg_power, max_power = activities_utils.calculate_avg_and_max(
|
|
power_waypoints, "power"
|
|
)
|
|
|
|
# Calculate normalised power
|
|
np = activities_utils.calculate_np(power_waypoints)
|
|
|
|
# Calculate the elapsed time
|
|
elapsed_time = last_waypoint_time - first_waypoint_time
|
|
|
|
if activity_type != 3 and activity_type != 7:
|
|
if is_lat_lon_set:
|
|
timezone = tf.timezone_at(
|
|
lat=lat_lon_waypoints[0]["lat"],
|
|
lng=lat_lon_waypoints[0]["lon"],
|
|
)
|
|
|
|
# Create an Activity object with parsed data
|
|
activity = activities_schema.Activity(
|
|
user_id=user_id,
|
|
name=activity_name,
|
|
distance=round(distance) if distance else 0,
|
|
activity_type=activity_type,
|
|
start_time=first_waypoint_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
|
end_time=last_waypoint_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
|
timezone=timezone,
|
|
total_elapsed_time=elapsed_time.total_seconds(),
|
|
total_timer_time=elapsed_time.total_seconds(),
|
|
city=city,
|
|
town=town,
|
|
country=country,
|
|
elevation_gain=round(ele_gain) if ele_gain else None,
|
|
elevation_loss=round(ele_loss) if ele_loss else None,
|
|
pace=pace,
|
|
average_speed=avg_speed,
|
|
max_speed=max_speed,
|
|
average_power=round(avg_power) if avg_power else None,
|
|
max_power=round(max_power) if max_power else None,
|
|
normalized_power=round(np) if np else None,
|
|
average_hr=round(avg_hr) if avg_hr else None,
|
|
max_hr=round(max_hr) if max_hr else None,
|
|
average_cad=round(avg_cadence) if avg_cadence else None,
|
|
max_cad=round(max_cadence) if max_cadence else None,
|
|
calories=calories,
|
|
visibility=(
|
|
user_privacy_settings.default_activity_visibility
|
|
if user_privacy_settings.default_activity_visibility is not None
|
|
else 0
|
|
),
|
|
gear_id=gear_id,
|
|
strava_gear_id=None,
|
|
strava_activity_id=None,
|
|
garminconnect_activity_id=None,
|
|
garminconnect_gear_id=None,
|
|
hide_start_time=user_privacy_settings.hide_activity_start_time or False,
|
|
hide_location=user_privacy_settings.hide_activity_location or False,
|
|
hide_map=user_privacy_settings.hide_activity_map or False,
|
|
hide_hr=user_privacy_settings.hide_activity_hr or False,
|
|
hide_power=user_privacy_settings.hide_activity_power or False,
|
|
hide_cadence=user_privacy_settings.hide_activity_cadence or False,
|
|
hide_elevation=user_privacy_settings.hide_activity_elevation or False,
|
|
hide_speed=user_privacy_settings.hide_activity_speed or False,
|
|
hide_pace=user_privacy_settings.hide_activity_pace or False,
|
|
hide_laps=user_privacy_settings.hide_activity_laps or False,
|
|
hide_workout_sets_steps=user_privacy_settings.hide_activity_workout_sets_steps
|
|
or False,
|
|
hide_gear=user_privacy_settings.hide_activity_gear or False,
|
|
)
|
|
|
|
# Generate activity laps
|
|
laps = generate_activity_laps(
|
|
lat_lon_waypoints,
|
|
ele_waypoints,
|
|
power_waypoints,
|
|
hr_waypoints,
|
|
cad_waypoints,
|
|
vel_waypoints,
|
|
)
|
|
|
|
# Return parsed data as a dictionary
|
|
return {
|
|
"activity": activity,
|
|
"is_elevation_set": is_elevation_set,
|
|
"ele_waypoints": ele_waypoints,
|
|
"is_power_set": is_power_set,
|
|
"power_waypoints": power_waypoints,
|
|
"is_heart_rate_set": is_heart_rate_set,
|
|
"hr_waypoints": hr_waypoints,
|
|
"is_velocity_set": is_velocity_set,
|
|
"vel_waypoints": vel_waypoints,
|
|
"pace_waypoints": pace_waypoints,
|
|
"is_cadence_set": is_cadence_set,
|
|
"cad_waypoints": cad_waypoints,
|
|
"is_lat_lon_set": is_lat_lon_set,
|
|
"lat_lon_waypoints": lat_lon_waypoints,
|
|
"laps": laps,
|
|
}
|
|
|
|
except HTTPException as http_err:
|
|
raise http_err
|
|
except Exception as err:
|
|
# Log the exception
|
|
core_logger.print_to_log(
|
|
f"Error in parse_gpx_file - {str(err)}", "error", exc=err
|
|
)
|
|
# Raise an HTTPException with a 500 Internal Server Error status code
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Can't open GPX file: {str(err)}",
|
|
) from err
|
|
|
|
|
|
def generate_activity_laps(
|
|
lat_lon_waypoints: list[dict],
|
|
ele_waypoints: list[dict],
|
|
power_waypoints: list[dict],
|
|
hr_waypoints: list[dict],
|
|
cad_waypoints: list[dict],
|
|
vel_waypoints: list[dict],
|
|
distance_per_lap_km: float = 1.0,
|
|
) -> list[dict]:
|
|
laps = []
|
|
current_lap_distance = 0.0
|
|
lap_start = None
|
|
lap_ele_waypoints = []
|
|
lap_power_waypoints = []
|
|
lap_hr_waypoints = []
|
|
lap_cad_waypoints = []
|
|
lap_vel_waypoints = []
|
|
|
|
def filter_waypoints(waypoints, start_time, end_time):
|
|
return [
|
|
waypoint
|
|
for waypoint in waypoints
|
|
if datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S")
|
|
<= datetime.strptime(waypoint["time"], "%Y-%m-%dT%H:%M:%S")
|
|
<= datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%S")
|
|
]
|
|
|
|
for i in range(1, len(lat_lon_waypoints)):
|
|
# Get the current and previous waypoints
|
|
prev_point = lat_lon_waypoints[i - 1]
|
|
current_point = lat_lon_waypoints[i]
|
|
|
|
# Calculate the distance between the two waypoints
|
|
segment_distance = geodesic(
|
|
(prev_point["lat"], prev_point["lon"]),
|
|
(current_point["lat"], current_point["lon"]),
|
|
).kilometers
|
|
|
|
# Accumulate the distance
|
|
current_lap_distance += segment_distance
|
|
|
|
# Set the start of the lap if not already set
|
|
if lap_start is None:
|
|
lap_start = prev_point
|
|
|
|
# Check if the current lap distance exceeds or equals the lap distance
|
|
if current_lap_distance >= distance_per_lap_km:
|
|
# Filter waypoints for the current lap
|
|
start_time = lap_start["time"]
|
|
end_time = current_point["time"]
|
|
lap_ele_waypoints = filter_waypoints(ele_waypoints, start_time, end_time)
|
|
lap_power_waypoints = filter_waypoints(
|
|
power_waypoints, start_time, end_time
|
|
)
|
|
lap_hr_waypoints = filter_waypoints(hr_waypoints, start_time, end_time)
|
|
lap_cad_waypoints = filter_waypoints(cad_waypoints, start_time, end_time)
|
|
lap_vel_waypoints = filter_waypoints(vel_waypoints, start_time, end_time)
|
|
ele_gain = None
|
|
ele_loss = None
|
|
avg_hr = None
|
|
max_hr = None
|
|
avg_cadence = None
|
|
max_cadence = None
|
|
avg_speed = None
|
|
max_speed = None
|
|
avg_power = None
|
|
max_power = None
|
|
np = None
|
|
|
|
# Calculate total ascent and descent
|
|
if lap_ele_waypoints:
|
|
ele_gain, ele_loss = activities_utils.compute_elevation_gain_and_loss(
|
|
elevations=lap_ele_waypoints
|
|
)
|
|
|
|
# Calculate average and maximum heart rate
|
|
if lap_hr_waypoints:
|
|
avg_hr, max_hr = activities_utils.calculate_avg_and_max(
|
|
lap_hr_waypoints, "hr"
|
|
)
|
|
|
|
# Calculate average and maximum cadence
|
|
if lap_cad_waypoints:
|
|
avg_cadence, max_cadence = activities_utils.calculate_avg_and_max(
|
|
lap_cad_waypoints, "cad"
|
|
)
|
|
|
|
# Calculate average and maximum velocity
|
|
if lap_vel_waypoints:
|
|
avg_speed, max_speed = activities_utils.calculate_avg_and_max(
|
|
lap_vel_waypoints, "vel"
|
|
)
|
|
|
|
# Calculate average and maximum power
|
|
if lap_power_waypoints:
|
|
avg_power, max_power = activities_utils.calculate_avg_and_max(
|
|
lap_power_waypoints, "power"
|
|
)
|
|
|
|
# Calculate normalised power
|
|
np = activities_utils.calculate_np(lap_power_waypoints)
|
|
|
|
# Create a lap
|
|
laps.append(
|
|
{
|
|
"start_time": lap_start["time"],
|
|
"start_position_lat": lap_start["lat"],
|
|
"start_position_long": lap_start["lon"],
|
|
"end_position_lat": current_point["lat"],
|
|
"end_position_long": current_point["lon"],
|
|
"total_elapsed_time": (
|
|
datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%S")
|
|
- datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S")
|
|
).total_seconds(),
|
|
"total_timer_time": (
|
|
datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%S")
|
|
- datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S")
|
|
).total_seconds(),
|
|
"total_distance": current_lap_distance * 1000,
|
|
"avg_heart_rate": round(avg_hr) if avg_hr else None,
|
|
"max_heart_rate": round(max_hr) if max_hr else None,
|
|
"avg_cadence": round(avg_cadence) if avg_cadence else None,
|
|
"max_cadence": round(max_cadence) if max_cadence else None,
|
|
"avg_power": round(avg_power) if avg_power else None,
|
|
"max_power": round(max_power) if max_power else None,
|
|
"total_ascent": round(ele_gain) if ele_gain else None,
|
|
"total_descent": round(ele_loss) if ele_loss else None,
|
|
"normalized_power": round(np) if np else None,
|
|
"enhanced_avg_pace": (
|
|
1 / avg_speed
|
|
if avg_speed != 0 and avg_speed is not None
|
|
else None
|
|
),
|
|
"enhanced_avg_speed": avg_speed,
|
|
"enhanced_max_pace": (
|
|
1 / max_speed
|
|
if max_speed != 0 and max_speed is not None
|
|
else None
|
|
),
|
|
"enhanced_max_speed": max_speed,
|
|
}
|
|
)
|
|
|
|
# Reset for the next lap
|
|
lap_start = current_point
|
|
current_lap_distance = 0.0
|
|
|
|
# Add the final lap if it exists and is less than the lap distance
|
|
if lap_start is not None and current_lap_distance > 0:
|
|
start_time = lap_start["time"]
|
|
end_time = lat_lon_waypoints[-1]["time"]
|
|
lap_ele_waypoints = filter_waypoints(ele_waypoints, start_time, end_time)
|
|
lap_power_waypoints = filter_waypoints(power_waypoints, start_time, end_time)
|
|
lap_hr_waypoints = filter_waypoints(hr_waypoints, start_time, end_time)
|
|
lap_cad_waypoints = filter_waypoints(cad_waypoints, start_time, end_time)
|
|
lap_vel_waypoints = filter_waypoints(vel_waypoints, start_time, end_time)
|
|
ele_gain, ele_loss = None, None
|
|
avg_hr, max_hr = None, None
|
|
avg_cadence, max_cadence = None, None
|
|
avg_speed, max_speed = None, None
|
|
avg_power, max_power, np = None, None, None
|
|
|
|
# Calculate total ascent and descent
|
|
if lap_ele_waypoints:
|
|
ele_gain, ele_loss = activities_utils.compute_elevation_gain_and_loss(
|
|
lap_ele_waypoints
|
|
)
|
|
|
|
# Calculate average and maximum heart rate
|
|
if lap_hr_waypoints:
|
|
avg_hr, max_hr = activities_utils.calculate_avg_and_max(
|
|
lap_hr_waypoints, "hr"
|
|
)
|
|
|
|
# Calculate average and maximum cadence
|
|
if lap_cad_waypoints:
|
|
avg_cadence, max_cadence = activities_utils.calculate_avg_and_max(
|
|
lap_cad_waypoints, "cad"
|
|
)
|
|
|
|
# Calculate average and maximum velocity
|
|
if lap_vel_waypoints:
|
|
avg_speed, max_speed = activities_utils.calculate_avg_and_max(
|
|
lap_vel_waypoints, "vel"
|
|
)
|
|
|
|
# Calculate average and maximum power
|
|
if lap_power_waypoints:
|
|
avg_power, max_power = activities_utils.calculate_avg_and_max(
|
|
lap_power_waypoints, "power"
|
|
)
|
|
|
|
# Calculate normalised power
|
|
np = activities_utils.calculate_np(lap_power_waypoints)
|
|
|
|
laps.append(
|
|
{
|
|
"start_time": lap_start["time"],
|
|
"start_position_lat": lap_start["lat"],
|
|
"start_position_long": lap_start["lon"],
|
|
"end_position_lat": lat_lon_waypoints[-1]["lat"],
|
|
"end_position_long": lat_lon_waypoints[-1]["lon"],
|
|
"total_elapsed_time": (
|
|
datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%S")
|
|
- datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S")
|
|
).total_seconds(),
|
|
"total_timer_time": (
|
|
datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%S")
|
|
- datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S")
|
|
).total_seconds(),
|
|
"total_distance": current_lap_distance * 1000,
|
|
"avg_heart_rate": round(avg_hr) if avg_hr else None,
|
|
"max_heart_rate": round(max_hr) if max_hr else None,
|
|
"avg_cadence": round(avg_cadence) if avg_cadence else None,
|
|
"max_cadence": round(max_cadence) if max_cadence else None,
|
|
"avg_power": round(avg_power) if avg_power else None,
|
|
"max_power": round(max_power) if max_power else None,
|
|
"total_ascent": round(ele_gain) if ele_gain else None,
|
|
"total_descent": round(ele_loss) if ele_loss else None,
|
|
"normalized_power": round(np) if np else None,
|
|
"enhanced_avg_pace": (
|
|
1 / avg_speed if avg_speed != 0 and avg_speed is not None else None
|
|
),
|
|
"enhanced_avg_speed": avg_speed,
|
|
"enhanced_max_pace": (
|
|
1 / max_speed if max_speed != 0 and max_speed is not None else None
|
|
),
|
|
"enhanced_max_speed": max_speed,
|
|
}
|
|
)
|
|
|
|
return laps
|