From 93735a449f015af13843e256b84eb5c08abf949d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Wed, 22 Nov 2023 17:13:30 +0000 Subject: [PATCH] Added missing attribute pace to waypoints array when processing Strava activities. --- controllers/stravaController.py | 493 ++------------------------------ 1 file changed, 31 insertions(+), 462 deletions(-) diff --git a/controllers/stravaController.py b/controllers/stravaController.py index eabf0b950..c68197142 100644 --- a/controllers/stravaController.py +++ b/controllers/stravaController.py @@ -10,6 +10,7 @@ from sqlalchemy import func from stravalib.client import Client from pint import Quantity from concurrent.futures import ThreadPoolExecutor +from math import radians, sin, cos, sqrt, atan2 import logging import requests @@ -240,20 +241,36 @@ def get_strava_activities(start_date: datetime): # response.raise_for_status() # Raise an error for bad responses # store_strava_activities_stravaLib(response.json(), user.id, user.strava_token) - #store_strava_activities_stravaLib(user.id, user.strava_token, start_date) + # store_strava_activities_stravaLib(user.id, user.strava_token, start_date) stravaClient = Client(access_token=user.strava_token) - strava_activities = list(stravaClient.get_activities(after=start_date)) - chunk_size = len(strava_activities) // 4 # Adjust the number of threads as needed - activity_chunks = [strava_activities[i:i + chunk_size] for i in range(0, len(strava_activities), chunk_size)] - + strava_activities = list( + stravaClient.get_activities(after=start_date) + ) + chunk_size = ( + len(strava_activities) // 4 + ) # Adjust the number of threads as needed + activity_chunks = [ + strava_activities[i : i + chunk_size] + for i in range(0, len(strava_activities), chunk_size) + ] + with ThreadPoolExecutor() as executor: # Process each chunk of activities using threads - results = list(executor.map(lambda chunk: process_activities(chunk, user.id, stravaClient), activity_chunks)) + results = list( + executor.map( + lambda chunk: process_activities( + chunk, user.id, stravaClient + ), + activity_chunks, + ) + ) # Flatten the list of results - activities_to_insert = [activity for sublist in results for activity in sublist] + activities_to_insert = [ + activity for sublist in results for activity in sublist + ] # Bulk insert all activities with get_db_session() as db_session: @@ -269,6 +286,7 @@ def get_strava_activities(start_date: datetime): except NameError as db_err: logger.error(f"Database error: {db_err}") + def process_activities(strava_activities, user_id, stravaClient): activities_to_insert = [] @@ -289,6 +307,7 @@ def process_activities(strava_activities, user_id, stravaClient): return activities_to_insert + def process_activity(activity, user_id, stravaClient): start_date_parsed = activity.start_date # Ensure activity.elapsed_time is a numerical value @@ -297,9 +316,7 @@ def process_activity(activity, user_id, stravaClient): if isinstance(activity.elapsed_time, timedelta) else activity.elapsed_time ) - end_date_parsed = start_date_parsed + timedelta( - seconds=elapsed_time_seconds - ) + end_date_parsed = start_date_parsed + timedelta(seconds=elapsed_time_seconds) latitude = 0 longitude = 0 @@ -362,9 +379,7 @@ def process_activity(activity, user_id, stravaClient): heart_rates = streams["heartrate"].data if "heartrate" in streams else [] cadences = streams["cadence"].data if "cadence" in streams else [] powers = streams["watts"].data if "watts" in streams else [] - velocities = ( - streams["velocity_smooth"].data if "velocity_smooth" in streams else [] - ) + velocities = streams["velocity_smooth"].data if "velocity_smooth" in streams else [] for i in range(len(heart_rates)): waypoint = { @@ -376,6 +391,7 @@ def process_activity(activity, user_id, stravaClient): "cad": cadences[i] if i < len(cadences) else None, "power": powers[i] if i < len(powers) else None, "vel": velocities[i] if i < len(velocities) else None, + "pace": 1/velocities[i] if i < len(velocities) and velocities[i] != 0 else None, # Add other relevant fields based on your requirements } @@ -445,7 +461,7 @@ def process_activity(activity, user_id, stravaClient): city=city, town=town, country=country, - #created_at=func.now(), # Use func.now() to set 'created_at' to the current timestamp + # created_at=func.now(), # Use func.now() to set 'created_at' to the current timestamp created_at=datetime.utcnow(), waypoints=waypoints, elevation_gain=elevation_gain, @@ -456,451 +472,4 @@ def process_activity(activity, user_id, stravaClient): strava_activity_id=activity.id, ) - return newActivity - - -def store_strava_activities_stravaLib(user_id, strava_token, start_date): - stravaClient = Client(access_token=strava_token) - #start_date = (datetime.utcnow() - timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ") - - for activity in stravaClient.get_activities(after=start_date): - with get_db_session() as db_session: - # Use SQLAlchemy to query the gear record by ID - activity_record = ( - db_session.query(Activity) - .filter(Activity.strava_activity_id == activity.id) - .first() - ) - - if activity_record: - # Skip to the next iteration - continue - - start_date_parsed = activity.start_date - # Ensure activity.elapsed_time is a numerical value - elapsed_time_seconds = ( - activity.elapsed_time.total_seconds() - if isinstance(activity.elapsed_time, timedelta) - else activity.elapsed_time - ) - end_date_parsed = start_date_parsed + timedelta( - seconds=elapsed_time_seconds - ) - - latitude = 0 - longitude = 0 - - if hasattr(activity, "start_latlng") and activity.start_latlng is not None: - latitude = activity.start_latlng.lat - longitude = activity.start_latlng.lon - - city = None - town = None - country = None - if latitude != 0 and longitude != 0: - url = f"https://geocode.maps.co/reverse?lat={latitude}&lon={longitude}" - try: - # Make a GET request - response = requests.get(url) - - # Check if the request was successful (status code 200) - if response.status_code == 200: - # Parse the JSON response - data = response.json() - - # Extract the town and country from the address components - city = data.get("address", {}).get("city", None) - town = data.get("address", {}).get("town", None) - country = data.get("address", {}).get("country", None) - else: - print(f"Error location: {response.status_code}") - print(f"Error location: {url}") - except Exception as e: - print(f"An error occurred: {e}") - - # List to store constructed waypoints - waypoints = [] - - # Initialize variables for elevation gain and loss - elevation_gain = 0 - elevation_loss = 0 - previous_elevation = None - - # Get streams for the activity - streams = stravaClient.get_activity_streams( - activity.id, - types=[ - "latlng", - "altitude", - "time", - "heartrate", - "cadence", - "watts", - "velocity_smooth", - ], - ) - - # Extract data from streams - latitudes = streams["latlng"].data if "latlng" in streams else [] - longitudes = streams["latlng"].data if "latlng" in streams else [] - elevations = streams["altitude"].data if "altitude" in streams else [] - times = streams["time"].data if "time" in streams else [] - heart_rates = streams["heartrate"].data if "heartrate" in streams else [] - cadences = streams["cadence"].data if "cadence" in streams else [] - powers = streams["watts"].data if "watts" in streams else [] - velocities = ( - streams["velocity_smooth"].data if "velocity_smooth" in streams else [] - ) - - for i in range(len(heart_rates)): - waypoint = { - "lat": latitudes[i] if i < len(latitudes) else None, - "lon": longitudes[i] if i < len(longitudes) else None, - "ele": elevations[i] if i < len(elevations) else None, - "time": times[i] if i < len(times) else None, - "hr": heart_rates[i] if i < len(heart_rates) else None, - "cad": cadences[i] if i < len(cadences) else None, - "power": powers[i] if i < len(powers) else None, - "vel": velocities[i] if i < len(velocities) else None, - # Add other relevant fields based on your requirements - } - - # Calculate elevation gain and loss on-the-fly - current_elevation = elevations[i] if i < len(elevations) else None - - if current_elevation is not None: - if previous_elevation is not None: - elevation_change = current_elevation - previous_elevation - - if elevation_change > 0: - elevation_gain += elevation_change - else: - elevation_loss += abs(elevation_change) - - previous_elevation = current_elevation - - # Append the constructed waypoint to the waypoints list - waypoints.append(waypoint) - - average_speed = 0 - if activity.average_speed is not None: - average_speed = ( - float(activity.average_speed.magnitude) - if isinstance(activity.average_speed, Quantity) - else activity.average_speed - ) - - average_pace = 1 / average_speed if average_speed != 0 else 0 - - average_watts = 0 - if activity.average_watts is not None: - average_watts = activity.average_watts - - auxType = 10 # Default value - type_mapping = { - "running": 1, - "Run": 1, - "trail running": 2, - "TrailRun": 2, - "VirtualRun": 3, - "cycling": 4, - "Ride": 4, - "GravelRide": 5, - "EBikeRide": 6, - "EMountainBikeRide": 6, - "VirtualRide": 7, - "virtual_ride": 7, - "MountainBikeRide": 8, - "swimming": 9, - "Swim": 9, - "open_water_swimming": 9, - "Workout": 10, - } - auxType = type_mapping.get(activity.sport_type, 10) - - # Create a new Activity record - activity = Activity( - user_id=user_id, - name=activity.name, - distance=round(float(activity.distance)) - if isinstance(activity.distance, Quantity) - else round(activity.distance), - activity_type=auxType, - start_time=start_date_parsed, - end_time=end_date_parsed, - city=city, - town=town, - country=country, - created_at=func.now(), # Use func.now() to set 'created_at' to the current timestamp - waypoints=waypoints, - elevation_gain=elevation_gain, - elevation_loss=elevation_loss, - pace=average_pace, - average_speed=average_speed, - average_power=average_watts, - strava_activity_id=activity.id, - ) - - try: - # Store the Activity record in the database - with get_db_session() as db_session: - db_session.add(activity) - db_session.commit() - db_session.refresh( - activity - ) # This will ensure that the activity object is updated with the ID from the database - return {"message": "Activities retrieved"} - except Exception as err: - print(err) - logger.error(err) - - -def store_strava_activities(strava_activities, user_id, strava_token): - from . import activitiesController - - try: - params = { - "access_token": strava_token, - } - - # start_date = (datetime.utcnow() - timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%SZ') - # strava_activities = get_strava_activities(start_date) - if strava_activities: - for activity in strava_activities: - try: - with get_db_session() as db_session: - # Use SQLAlchemy to query the gear record by ID - activity_record = ( - db_session.query(Activity) - .filter(Activity.strava_activity_id == activity.get("id")) - .first() - ) - - if activity_record: - # Skip to the next iteration - continue - - start_date_parsed = activitiesController.parse_timestamp( - activity.get("start_date") - ) - end_date_parsed = start_date_parsed + timedelta( - seconds=activity.get("elapsed_time") - ) - - latitude, longitude = activity.get("start_latlng", [0, 0]) - city = None - town = None - country = None - if latitude != 0 and longitude != 0: - url = f"https://geocode.maps.co/reverse?lat={latitude}&lon={longitude}" - try: - # Make a GET request - response = requests.get(url) - - # Check if the request was successful (status code 200) - if response.status_code == 200: - # Parse the JSON response - data = response.json() - - # Extract the town and country from the address components - city = data.get("address", {}).get("city", None) - town = data.get("address", {}).get("town", None) - country = data.get("address", {}).get("country", None) - else: - print(f"Error location: {response.status_code}") - print(f"Error location: {url}") - except Exception as e: - print(f"An error occurred: {e}") - - activity_id = activity.get("id") - strava_api_url_streams = f"https://www.strava.com/api/v3/activities/{activity_id}/streams" - - # List to store constructed waypoints - waypoints = [] - - # Initialize variables for elevation gain and loss - elevation_gain = 0 - elevation_loss = 0 - - try: - # Make a GET request to retrieve activity streams - response = requests.get(strava_api_url_streams, params=params) - - # Check if the request was successful (status code 200) - if response.status_code == 200: - # Parse the JSON response - activity_streams = response.json() - - # Extract relevant streams data - latlng_stream = activity_streams.get("latlng", {}).get( - "data", [] - ) - time_stream = activity_streams.get("time", {}).get( - "data", [] - ) - elevation_stream = activity_streams.get("altitude", {}).get( - "data", [] - ) - heart_rate_stream = activity_streams.get( - "heartrate", {} - ).get("data", []) - cadence_stream = activity_streams.get("cadence", {}).get( - "data", [] - ) - power_stream = activity_streams.get("watts", {}).get( - "data", [] - ) - velocity_stream = activity_streams.get( - "velocity_smooth", {} - ).get("data", []) - - # Ensure all streams have the same length (adjust as needed) - stream_length = min( - len(latlng_stream), - len(time_stream), - len(elevation_stream), - len(heart_rate_stream), - len(cadence_stream), - len(power_stream), - len(velocity_stream), - ) - - # Iterate over the streams and construct waypoints - for i in range(stream_length): - latitude, longitude = ( - latlng_stream[i] - if i < len(latlng_stream) - else (0, 0) - ) - time = time_stream[i] if i < len(time_stream) else "" - elevation = ( - elevation_stream[i] - if i < len(elevation_stream) - else 0 - ) - heart_rate = ( - heart_rate_stream[i] - if i < len(heart_rate_stream) - else 0 - ) - cadence = ( - cadence_stream[i] if i < len(cadence_stream) else 0 - ) - power = power_stream[i] if i < len(power_stream) else 0 - velocity = ( - velocity_stream[i] - if i < len(velocity_stream) - else 0 - ) - - elevation_current = ( - elevation_stream[i] - if i < len(elevation_stream) - else 0 - ) - elevation_previous = ( - elevation_stream[i - 1] - if i - 1 < len(elevation_stream) - else 0 - ) - - # Calculate the difference in elevation - elevation_difference = ( - elevation_current - elevation_previous - ) - - # Update elevation gain and loss based on the difference - if elevation_difference > 0: - elevation_gain += elevation_difference - elif elevation_difference < 0: - elevation_loss += abs(elevation_difference) - - # Construct the waypoint dictionary - waypoint = { - "lat": latitude, - "lon": longitude, - "ele": elevation, - "time": time, - "hr": heart_rate, - "cad": cadence, - "power": power, - "vel": velocity, - # Add other relevant fields based on your requirements - } - - # Append the constructed waypoint to the waypoints list - waypoints.append(waypoint) - - else: - print(f"Error streams: {response.status_code}") - except Exception as e: - print(f"An error occurred: {e}") - - average_pace = ( - 1 / activity.get("average_speed", 0) - if activity.get("average_speed", 0) != 0 - else 0 - ) - - # activitiesController.create_activity([activity.get("distance", 0), activity.get("name", "Unnamed activity"), activity.get("type", "Workout"), start_date_parsed, end_date_parsed, city, town, country, waypoints, elevation_gain, elevation_loss, average_pace, activity.get("average_speed", 0), activity.get("average_watts", 0), activity.get("id")],token) - - auxType = 10 # Default value - type_mapping = { - "running": 1, - "Run": 1, - "trail running": 2, - "TrailRun": 2, - "VirtualRun": 3, - "cycling": 4, - "Ride": 4, - "GravelRide": 5, - "EBikeRide": 6, - "EMountainBikeRide": 6, - "VirtualRide": 7, - "virtual_ride": 7, - "MountainBikeRide": 8, - "swimming": 9, - "open_water_swimming": 9, - "Workout": 10, - } - auxType = type_mapping.get(activity.get("sport_type", "Workout"), 10) - - aux = activity.get("sport_type") - logger.info(f"No Strava activities returned {aux}") - - # Create a new Activity record - activity = Activity( - user_id=user_id, - name=activity.get("name", "Unnamed activity"), - distance=round(activity.get("distance", 0)), - activity_type=auxType, - start_time=start_date_parsed, - end_time=end_date_parsed, - city=city, - town=town, - country=country, - created_at=func.now(), # Use func.now() to set 'created_at' to the current timestamp - waypoints=waypoints, - elevation_gain=elevation_gain, - elevation_loss=elevation_loss, - pace=average_pace, - average_speed=activity.get("average_speed", 0), - average_power=activity.get("average_watts", 0), - strava_activity_id=activity.get("id"), - ) - - # Store the Activity record in the database - with get_db_session() as db_session: - db_session.add(activity) - db_session.commit() - db_session.refresh( - activity - ) # This will ensure that the activity object is updated with the ID from the database - return {"message": "Activities retrieved"} - except Exception as err: - print(err) - logger.error(err) - else: - logger.info("No Strava activities returned") - except JWTError: - raise HTTPException(status_code=401, detail="Unauthorized") + return newActivity \ No newline at end of file