def DownloadActivity(self, svcRecord, activity): if activity.ServiceData["Manual"]: # I should really add a param to DownloadActivity for this value as opposed to constantly doing this # We've got as much information as we're going to get - we need to copy it into a Lap though. activity.Laps = [Lap(startTime=activity.StartTime, endTime=activity.EndTime, stats=activity.Stats)] return activity activityID = activity.ServiceData["ActivityID"] streamdata = requests.get("https://www.strava.com/api/v3/activities/" + str(activityID) + "/streams/time,altitude,heartrate,cadence,watts,temp,moving,latlng,distance,velocity_smooth", headers=self._apiHeaders(svcRecord)) if streamdata.status_code == 401: raise APIException("No authorization to download activity", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True)) try: streamdata = streamdata.json() except: raise APIException("Stream data returned is not JSON") if "message" in streamdata and streamdata["message"] == "Record Not Found": raise APIException("Could not find activity") ridedata = {} for stream in streamdata: ridedata[stream["type"]] = stream["data"] if "error" in ridedata: raise APIException("Strava error " + ridedata["error"]) activity.Laps = [] res = requests.get("https://www.strava.com/api/v3/activities/{}".format(activityID), headers=self._apiHeaders(svcRecord)) activityDetails = res.json() activity.Notes = activityDetails["description"] lapsdata = activityDetails["laps"] for lapdata in lapsdata: lapWaypoints, lapStats = self._process_lap_waypoints(activity, ridedata, lapdata) lapStart = pytz.utc.localize(datetime.strptime(lapdata["start_date"], "%Y-%m-%dT%H:%M:%SZ")) lapEnd = lapStart + timedelta(0, lapdata["elapsed_time"]) lap = Lap(startTime=lapStart, endTime=lapEnd) lap.Waypoints = lapWaypoints # In single-lap case lap stats needs to match global stats lap.Stats = activity.Stats if len(lapsdata) == 1 else lapStats activity.Laps.append(lap) return activity
def DownloadActivity(self, serviceRecord, activity): session = self._get_session(serviceRecord) act_id = activity.ServiceData["ID"] activityDetails = session.get("https://api.nike.com/me/sport/activities/%s" % act_id, params=self._with_auth(session)) activityDetails = activityDetails.json() streams = {metric["metricType"].lower(): self._nikeStream(metric) for metric in activityDetails["metrics"]} activity.GPS = activityDetails["isGpsActivity"] if activity.GPS: activityGps = session.get("https://api.nike.com/me/sport/activities/%s/gps" % act_id, params=self._with_auth(session)) activityGps = activityGps.json() streams["gps"] = self._nikeStream(activityGps, "waypoints") activity.Stats.Elevation.update(ActivityStatistic(ActivityStatisticUnit.Meters, gain=float(activityGps["elevationGain"]), loss=float(activityGps["elevationLoss"]), max=float(activityGps["elevationMax"]), min=float(activityGps["elevationMin"]))) lap = Lap(startTime=activity.StartTime, endTime=activity.EndTime) lap.Stats = activity.Stats activity.Laps = [lap] # I thought I wrote StreamSampler to be generator-friendly - nope. streams = {k: list(v) for k,v in streams.items()} # The docs are unclear on which of these are actually stream metrics, oh well def stream_waypoint(offset, speed=None, distance=None, heartrate=None, calories=None, steps=None, watts=None, gps=None, **kwargs): wp = Waypoint() wp.Timestamp = activity.StartTime + timedelta(seconds=offset) wp.Speed = float(speed) if speed else None wp.Distance = float(distance) / 1000 if distance else None wp.HR = float(heartrate) if heartrate else None wp.Calories = float(calories) if calories else None wp.Power = float(watts) if watts else None if gps: wp.Location = Location(lat=float(gps["latitude"]), lon=float(gps["longitude"]), alt=float(gps["elevation"])) lap.Waypoints.append(wp) StreamSampler.SampleWithCallback(stream_waypoint, streams) activity.Stationary = len(lap.Waypoints) == 0 return activity
def DownloadActivity(self, serviceRecord, activity): session = self._get_session(serviceRecord) act_id = activity.ServiceData["ID"] activityDetails = session.get( "https://api.nike.com/me/sport/activities/%s" % act_id, params=self._with_auth(session)) activityDetails = activityDetails.json() streams = { metric["metricType"].lower(): self._nikeStream(metric) for metric in activityDetails["metrics"] } activity.GPS = activityDetails["isGpsActivity"] if activity.GPS: activityGps = session.get( "https://api.nike.com/me/sport/activities/%s/gps" % act_id, params=self._with_auth(session)) activityGps = activityGps.json() streams["gps"] = self._nikeStream(activityGps, "waypoints") activity.Stats.Elevation.update( ActivityStatistic(ActivityStatisticUnit.Meters, gain=float(activityGps["elevationGain"]), loss=float(activityGps["elevationLoss"]), max=float(activityGps["elevationMax"]), min=float(activityGps["elevationMin"]))) lap = Lap(startTime=activity.StartTime, endTime=activity.EndTime) lap.Stats = activity.Stats activity.Laps = [lap] # I thought I wrote StreamSampler to be generator-friendly - nope. streams = {k: list(v) for k, v in streams.items()} # The docs are unclear on which of these are actually stream metrics, oh well def stream_waypoint(offset, speed=None, distance=None, heartrate=None, calories=None, steps=None, watts=None, gps=None, **kwargs): wp = Waypoint() wp.Timestamp = activity.StartTime + timedelta(seconds=offset) wp.Speed = float(speed) if speed else None wp.Distance = float(distance) / 1000 if distance else None wp.HR = float(heartrate) if heartrate else None wp.Calories = float(calories) if calories else None wp.Power = float(watts) if watts else None if gps: wp.Location = Location(lat=float(gps["latitude"]), lon=float(gps["longitude"]), alt=float(gps["elevation"])) lap.Waypoints.append(wp) StreamSampler.SampleWithCallback(stream_waypoint, streams) activity.Stationary = len(lap.Waypoints) == 0 return activity
def _downloadActivity(self, serviceRecord, activity, returnFirstLocation=False): activityURI = activity.ServiceData["ActivityURI"] headers = self._getAuthHeaders(serviceRecord) activityData = requests.get(activityURI, headers=headers) activityData = activityData.json() if "clock_duration" in activityData: activity.EndTime = activity.StartTime + timedelta( seconds=float(activityData["clock_duration"])) activity.Private = "sharing" in activityData and activityData[ "sharing"] != "public" activity.GPS = False # Gets set back if there is GPS data if "notes" in activityData: activity.Notes = activityData["notes"] activity.Stats.Energy = ActivityStatistic( ActivityStatisticUnit.Kilojoules, value=float(activityData["calories"])) activity.Stats.Elevation = ActivityStatistic( ActivityStatisticUnit.Meters, gain=float(activityData["elevation_gain"]) if "elevation_gain" in activityData else None, loss=float(activityData["elevation_loss"]) if "elevation_loss" in activityData else None) activity.Stats.HR = ActivityStatistic( ActivityStatisticUnit.BeatsPerMinute, avg=activityData["avg_heartrate"] if "avg_heartrate" in activityData else None, max=activityData["max_heartrate"] if "max_heartrate" in activityData else None) activity.Stats.Cadence = ActivityStatistic( ActivityStatisticUnit.RevolutionsPerMinute, avg=activityData["avg_cadence"] if "avg_cadence" in activityData else None, max=activityData["max_cadence"] if "max_cadence" in activityData else None) activity.Stats.Power = ActivityStatistic( ActivityStatisticUnit.Watts, avg=activityData["avg_power"] if "avg_power" in activityData else None, max=activityData["max_power"] if "max_power" in activityData else None) laps_info = [] laps_starts = [] if "laps" in activityData: laps_info = activityData["laps"] for lap in activityData["laps"]: laps_starts.append(dateutil.parser.parse(lap["start_time"])) lap = None for lapinfo in laps_info: lap = Lap() activity.Laps.append(lap) lap.StartTime = dateutil.parser.parse(lapinfo["start_time"]) lap.EndTime = lap.StartTime + timedelta( seconds=lapinfo["clock_duration"]) if "type" in lapinfo: lap.Intensity = LapIntensity.Active if lapinfo[ "type"] == "ACTIVE" else LapIntensity.Rest if "distance" in lapinfo: lap.Stats.Distance = ActivityStatistic( ActivityStatisticUnit.Meters, value=float(lapinfo["distance"])) if "duration" in lapinfo: lap.Stats.TimerTime = ActivityStatistic( ActivityStatisticUnit.Seconds, value=lapinfo["duration"]) if "calories" in lapinfo: lap.Stats.Energy = ActivityStatistic( ActivityStatisticUnit.Kilojoules, value=lapinfo["calories"]) if "elevation_gain" in lapinfo: lap.Stats.Elevation.update( ActivityStatistic(ActivityStatisticUnit.Meters, gain=float(lapinfo["elevation_gain"]))) if "elevation_loss" in lapinfo: lap.Stats.Elevation.update( ActivityStatistic(ActivityStatisticUnit.Meters, loss=float(lapinfo["elevation_loss"]))) if "max_speed" in lapinfo: lap.Stats.Speed.update( ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, max=float(lapinfo["max_speed"]))) if "max_speed" in lapinfo: lap.Stats.Speed.update( ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, max=float(lapinfo["max_speed"]))) if "avg_speed" in lapinfo: lap.Stats.Speed.update( ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, avg=float(lapinfo["avg_speed"]))) if "max_heartrate" in lapinfo: lap.Stats.HR.update( ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, max=float(lapinfo["max_heartrate"]))) if "avg_heartrate" in lapinfo: lap.Stats.HR.update( ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, avg=float(lapinfo["avg_heartrate"]))) if lap is None: # No explicit laps => make one that encompasses the entire activity lap = Lap() activity.Laps.append(lap) lap.Stats = activity.Stats lap.StartTime = activity.StartTime lap.EndTime = activity.EndTime elif len(activity.Laps) == 1: activity.Stats.update( activity.Laps[0].Stats ) # Lap stats have a bit more info generally. activity.Laps[0].Stats = activity.Stats timerStops = [] if "timer_stops" in activityData: for stop in activityData["timer_stops"]: timerStops.append([ dateutil.parser.parse(stop[0]), dateutil.parser.parse(stop[1]) ]) def isInTimerStop(timestamp): for stop in timerStops: if timestamp >= stop[0] and timestamp < stop[1]: return True if timestamp >= stop[1]: return False return False # Collate the individual streams into our waypoints. # Global sample rate is variable - will pick the next nearest stream datapoint. # Resampling happens on a lookbehind basis - new values will only appear their timestamp has been reached/passed wasInPause = False currentLapIdx = 0 lap = activity.Laps[currentLapIdx] streams = [] for stream in [ "location", "elevation", "heartrate", "power", "cadence", "distance" ]: if stream in activityData: streams.append(stream) stream_indices = dict([(stream, -1) for stream in streams ]) # -1 meaning the stream has yet to start stream_lengths = dict([(stream, len(activityData[stream]) / 2) for stream in streams]) # Data comes as "stream":[timestamp,value,timestamp,value,...] stream_values = {} for stream in streams: values = [] for x in range(0, int(len(activityData[stream]) / 2)): values.append((activityData[stream][x * 2], activityData[stream][x * 2 + 1])) stream_values[stream] = values currentOffset = 0 def streamVal(stream): nonlocal stream_values, stream_indices return stream_values[stream][stream_indices[stream]][1] def hasStreamData(stream): nonlocal stream_indices, streams return stream in streams and stream_indices[stream] >= 0 while True: advance_stream = None advance_offset = None for stream in streams: if stream_indices[stream] + 1 == stream_lengths[stream]: continue # We're at the end - can't advance if advance_offset is None or stream_values[stream][ stream_indices[stream] + 1][0] - currentOffset < advance_offset: advance_offset = stream_values[stream][ stream_indices[stream] + 1][0] - currentOffset advance_stream = stream if not advance_stream: break # We've hit the end of every stream, stop # Advance streams sharing the current timestamp for stream in streams: if stream == advance_stream: continue # For clarity, we increment this later if stream_indices[stream] + 1 == stream_lengths[stream]: continue # We're at the end - can't advance if stream_values[stream][ stream_indices[stream] + 1][0] == stream_values[advance_stream][ stream_indices[advance_stream] + 1][0]: stream_indices[stream] += 1 stream_indices[ advance_stream] += 1 # Advance the key stream for this waypoint currentOffset = stream_values[advance_stream][stream_indices[ advance_stream]][0] # Update the current time offset waypoint = Waypoint(activity.StartTime + timedelta(seconds=currentOffset)) if hasStreamData("location"): waypoint.Location = Location( streamVal("location")[0], streamVal("location")[1], None) activity.GPS = True if returnFirstLocation: return waypoint.Location if hasStreamData("elevation"): if not waypoint.Location: waypoint.Location = Location(None, None, None) waypoint.Location.Altitude = streamVal("elevation") if hasStreamData("heartrate"): waypoint.HR = streamVal("heartrate") if hasStreamData("power"): waypoint.Power = streamVal("power") if hasStreamData("cadence"): waypoint.Cadence = streamVal("cadence") if hasStreamData("distance"): waypoint.Distance = streamVal("distance") inPause = isInTimerStop(waypoint.Timestamp) waypoint.Type = WaypointType.Regular if not inPause else WaypointType.Pause if wasInPause and not inPause: waypoint.Type = WaypointType.Resume wasInPause = inPause # We only care if it's possible to start a new lap, i.e. there are more left if currentLapIdx + 1 < len(laps_starts): if laps_starts[currentLapIdx + 1] < waypoint.Timestamp: # A new lap has started currentLapIdx += 1 lap = activity.Laps[currentLapIdx] lap.Waypoints.append(waypoint) if returnFirstLocation: return None # I guess there were no waypoints? if activity.CountTotalWaypoints(): activity.GetFlatWaypoints()[0].Type = WaypointType.Start activity.GetFlatWaypoints()[-1].Type = WaypointType.End activity.Stationary = False else: activity.Stationary = True return activity
def _downloadActivity(self, serviceRecord, activity, returnFirstLocation=False): activityURI = activity.ServiceData["ActivityURI"] headers = self._getAuthHeaders(serviceRecord) activityData = requests.get(activityURI, headers=headers) activityData = activityData.json() if "clock_duration" in activityData: activity.EndTime = activity.StartTime + timedelta(seconds=float(activityData["clock_duration"])) activity.Private = "sharing" in activityData and activityData["sharing"] != "public" activity.GPS = False # Gets set back if there is GPS data if "notes" in activityData: activity.Notes = activityData["notes"] activity.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilojoules, value=float(activityData["calories"])) activity.Stats.Elevation = ActivityStatistic(ActivityStatisticUnit.Meters, gain=float(activityData["elevation_gain"]) if "elevation_gain" in activityData else None, loss=float(activityData["elevation_loss"]) if "elevation_loss" in activityData else None) activity.Stats.HR = ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, avg=activityData["avg_heartrate"] if "avg_heartrate" in activityData else None, max=activityData["max_heartrate"] if "max_heartrate" in activityData else None) activity.Stats.Cadence = ActivityStatistic(ActivityStatisticUnit.RevolutionsPerMinute, avg=activityData["avg_cadence"] if "avg_cadence" in activityData else None, max=activityData["max_cadence"] if "max_cadence" in activityData else None) activity.Stats.Power = ActivityStatistic(ActivityStatisticUnit.Watts, avg=activityData["avg_power"] if "avg_power" in activityData else None, max=activityData["max_power"] if "max_power" in activityData else None) laps_info = [] laps_starts = [] if "laps" in activityData: laps_info = activityData["laps"] for lap in activityData["laps"]: laps_starts.append(dateutil.parser.parse(lap["start_time"])) lap = None for lapinfo in laps_info: lap = Lap() activity.Laps.append(lap) lap.StartTime = dateutil.parser.parse(lapinfo["start_time"]) lap.EndTime = lap.StartTime + timedelta(seconds=lapinfo["clock_duration"]) if "type" in lapinfo: lap.Intensity = LapIntensity.Active if lapinfo["type"] == "ACTIVE" else LapIntensity.Rest if "distance" in lapinfo: lap.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=float(lapinfo["distance"])) if "duration" in lapinfo: lap.Stats.TimerTime = ActivityStatistic(ActivityStatisticUnit.Seconds, value=lapinfo["duration"]) if "calories" in lapinfo: lap.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilojoules, value=lapinfo["calories"]) if "elevation_gain" in lapinfo: lap.Stats.Elevation.update(ActivityStatistic(ActivityStatisticUnit.Meters, gain=float(lapinfo["elevation_gain"]))) if "elevation_loss" in lapinfo: lap.Stats.Elevation.update(ActivityStatistic(ActivityStatisticUnit.Meters, loss=float(lapinfo["elevation_loss"]))) if "max_speed" in lapinfo: lap.Stats.Speed.update(ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, max=float(lapinfo["max_speed"]))) if "max_speed" in lapinfo: lap.Stats.Speed.update(ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, max=float(lapinfo["max_speed"]))) if "avg_speed" in lapinfo: lap.Stats.Speed.update(ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, avg=float(lapinfo["avg_speed"]))) if "max_heartrate" in lapinfo: lap.Stats.HR.update(ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, max=float(lapinfo["max_heartrate"]))) if "avg_heartrate" in lapinfo: lap.Stats.HR.update(ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, avg=float(lapinfo["avg_heartrate"]))) if lap is None: # No explicit laps => make one that encompasses the entire activity lap = Lap() activity.Laps.append(lap) lap.Stats = activity.Stats lap.StartTime = activity.StartTime lap.EndTime = activity.EndTime elif len(activity.Laps) == 1: activity.Stats.update(activity.Laps[0].Stats) # Lap stats have a bit more info generally. activity.Laps[0].Stats = activity.Stats timerStops = [] if "timer_stops" in activityData: for stop in activityData["timer_stops"]: timerStops.append([dateutil.parser.parse(stop[0]), dateutil.parser.parse(stop[1])]) def isInTimerStop(timestamp): for stop in timerStops: if timestamp >= stop[0] and timestamp < stop[1]: return True if timestamp >= stop[1]: return False return False # Collate the individual streams into our waypoints. # Global sample rate is variable - will pick the next nearest stream datapoint. # Resampling happens on a lookbehind basis - new values will only appear their timestamp has been reached/passed wasInPause = False currentLapIdx = 0 lap = activity.Laps[currentLapIdx] streams = [] for stream in ["location", "elevation", "heartrate", "power", "cadence", "distance"]: if stream in activityData: streams.append(stream) stream_indices = dict([(stream, -1) for stream in streams]) # -1 meaning the stream has yet to start stream_lengths = dict([(stream, len(activityData[stream])/2) for stream in streams]) # Data comes as "stream":[timestamp,value,timestamp,value,...] stream_values = {} for stream in streams: values = [] for x in range(0,int(len(activityData[stream])/2)): values.append((activityData[stream][x * 2], activityData[stream][x * 2 + 1])) stream_values[stream] = values currentOffset = 0 def streamVal(stream): nonlocal stream_values, stream_indices return stream_values[stream][stream_indices[stream]][1] def hasStreamData(stream): nonlocal stream_indices, streams return stream in streams and stream_indices[stream] >= 0 while True: advance_stream = None advance_offset = None for stream in streams: if stream_indices[stream] + 1 == stream_lengths[stream]: continue # We're at the end - can't advance if advance_offset is None or stream_values[stream][stream_indices[stream] + 1][0] - currentOffset < advance_offset: advance_offset = stream_values[stream][stream_indices[stream] + 1][0] - currentOffset advance_stream = stream if not advance_stream: break # We've hit the end of every stream, stop # Advance streams sharing the current timestamp for stream in streams: if stream == advance_stream: continue # For clarity, we increment this later if stream_indices[stream] + 1 == stream_lengths[stream]: continue # We're at the end - can't advance if stream_values[stream][stream_indices[stream] + 1][0] == stream_values[advance_stream][stream_indices[advance_stream] + 1][0]: stream_indices[stream] += 1 stream_indices[advance_stream] += 1 # Advance the key stream for this waypoint currentOffset = stream_values[advance_stream][stream_indices[advance_stream]][0] # Update the current time offset waypoint = Waypoint(activity.StartTime + timedelta(seconds=currentOffset)) if hasStreamData("location"): waypoint.Location = Location(streamVal("location")[0], streamVal("location")[1], None) activity.GPS = True if returnFirstLocation: return waypoint.Location if hasStreamData("elevation"): if not waypoint.Location: waypoint.Location = Location(None, None, None) waypoint.Location.Altitude = streamVal("elevation") if hasStreamData("heartrate"): waypoint.HR = streamVal("heartrate") if hasStreamData("power"): waypoint.Power = streamVal("power") if hasStreamData("cadence"): waypoint.Cadence = streamVal("cadence") if hasStreamData("distance"): waypoint.Distance = streamVal("distance") inPause = isInTimerStop(waypoint.Timestamp) waypoint.Type = WaypointType.Regular if not inPause else WaypointType.Pause if wasInPause and not inPause: waypoint.Type = WaypointType.Resume wasInPause = inPause # We only care if it's possible to start a new lap, i.e. there are more left if currentLapIdx + 1 < len(laps_starts): if laps_starts[currentLapIdx + 1] < waypoint.Timestamp: # A new lap has started currentLapIdx += 1 lap = activity.Laps[currentLapIdx] lap.Waypoints.append(waypoint) if returnFirstLocation: return None # I guess there were no waypoints? if activity.CountTotalWaypoints(): activity.GetFlatWaypoints()[0].Type = WaypointType.Start activity.GetFlatWaypoints()[-1].Type = WaypointType.End activity.Stationary = False else: activity.Stationary = True return activity
def _downloadActivity(self, serviceRecord, activity, returnFirstLocation=False): activityURI = activity.ServiceData["ActivityURI"] cookies = self._get_cookies(record=serviceRecord) activityData = requests.get(activityURI, cookies=cookies) activityData = activityData.json() if "clock_duration" in activityData: activity.EndTime = activity.StartTime + timedelta(seconds=float(activityData["clock_duration"])) activity.Private = "sharing" in activityData and activityData["sharing"] != "public" if "notes" in activityData: activity.Notes = activityData["notes"] activity.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilojoules, value=float(activityData["calories"])) activity.Stats.Elevation = ActivityStatistic(ActivityStatisticUnit.Meters, gain=float(activityData["elevation_gain"]) if "elevation_gain" in activityData else None, loss=float(activityData["elevation_loss"]) if "elevation_loss" in activityData else None) activity.Stats.HR = ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, avg=activityData["avg_heartrate"] if "avg_heartrate" in activityData else None, max=activityData["max_heartrate"] if "max_heartrate" in activityData else None) activity.Stats.Cadence = ActivityStatistic(ActivityStatisticUnit.RevolutionsPerMinute, avg=activityData["avg_cadence"] if "avg_cadence" in activityData else None, max=activityData["max_cadence"] if "max_cadence" in activityData else None) activity.Stats.Power = ActivityStatistic(ActivityStatisticUnit.Watts, avg=activityData["avg_power"] if "avg_power" in activityData else None, max=activityData["max_power"] if "max_power" in activityData else None) laps_info = [] laps_starts = [] if "laps" in activityData: laps_info = activityData["laps"] for lap in activityData["laps"]: laps_starts.append(dateutil.parser.parse(lap["start_time"])) lap = None for lapinfo in laps_info: lap = Lap() activity.Laps.append(lap) lap.StartTime = dateutil.parser.parse(lapinfo["start_time"]) lap.EndTime = lap.StartTime + timedelta(seconds=lapinfo["clock_duration"]) if "type" in lapinfo: lap.Intensity = LapIntensity.Active if lapinfo["type"] == "ACTIVE" else LapIntensity.Rest if "distance" in lapinfo: lap.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Meters, value=float(lapinfo["distance"])) if "duration" in lapinfo: lap.Stats.MovingTime = ActivityStatistic(ActivityStatisticUnit.Time, value=timedelta(seconds=lapinfo["duration"])) if "calories" in lapinfo: lap.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilojoules, value=lapinfo["calories"]) if "elevation_gain" in lapinfo: lap.Stats.Elevation.update(ActivityStatistic(ActivityStatisticUnit.Meters, gain=float(lapinfo["elevation_gain"]))) if "elevation_loss" in lapinfo: lap.Stats.Elevation.update(ActivityStatistic(ActivityStatisticUnit.Meters, loss=float(lapinfo["elevation_loss"]))) if "max_speed" in lapinfo: lap.Stats.Speed.update(ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, max=float(lapinfo["max_speed"]))) if "max_speed" in lapinfo: lap.Stats.Speed.update(ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, max=float(lapinfo["max_speed"]))) if "avg_speed" in lapinfo: lap.Stats.Speed.update(ActivityStatistic(ActivityStatisticUnit.MetersPerSecond, avg=float(lapinfo["avg_speed"]))) if "max_heartrate" in lapinfo: lap.Stats.HR.update(ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, max=float(lapinfo["max_heartrate"]))) if "avg_heartrate" in lapinfo: lap.Stats.HR.update(ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, avg=float(lapinfo["avg_heartrate"]))) if lap is None: # No explicit laps => make one that encompasses the entire activity lap = Lap() activity.Laps.append(lap) lap.Stats = activity.Stats lap.StartTime = activity.StartTime lap.EndTime = activity.EndTime if "location" not in activityData: activity.Stationary = True else: activity.Stationary = False timerStops = [] if "timer_stops" in activityData: for stop in activityData["timer_stops"]: timerStops.append([dateutil.parser.parse(stop[0]), dateutil.parser.parse(stop[1])]) def isInTimerStop(timestamp): for stop in timerStops: if timestamp >= stop[0] and timestamp < stop[1]: return True if timestamp >= stop[1]: return False return False # Collate the individual streams into our waypoints. # Everything is resampled by nearest-neighbour to the rate of the location stream. parallel_indices = {} parallel_stream_lengths = {} for secondary_stream in ["elevation", "heartrate", "power", "cadence", "distance"]: if secondary_stream in activityData: parallel_indices[secondary_stream] = 0 parallel_stream_lengths[secondary_stream] = len(activityData[secondary_stream]) wasInPause = False currentLapIdx = 0 lap = activity.Laps[currentLapIdx] for idx in range(0, len(activityData["location"]), 2): # Pick the nearest indices in the parallel streams for parallel_stream, parallel_index in parallel_indices.items(): if parallel_index + 2 == parallel_stream_lengths[parallel_stream]: continue # We're at the end of this stream # Is the next datapoint a better choice than the current? if abs(activityData["location"][idx] - activityData[parallel_stream][parallel_index + 2]) < abs(activityData["location"][idx] - activityData[parallel_stream][parallel_index]): parallel_indices[parallel_stream] += 2 waypoint = Waypoint(activity.StartTime + timedelta(0, activityData["location"][idx])) waypoint.Location = Location(activityData["location"][idx+1][0], activityData["location"][idx+1][1], None) if "elevation" in parallel_indices: waypoint.Location.Altitude = activityData["elevation"][parallel_indices["elevation"]+1] if returnFirstLocation: return waypoint.Location if "heartrate" in parallel_indices: waypoint.HR = activityData["heartrate"][parallel_indices["heartrate"]+1] if "power" in parallel_indices: waypoint.Power = activityData["power"][parallel_indices["power"]+1] if "cadence" in parallel_indices: waypoint.Cadence = activityData["cadence"][parallel_indices["cadence"]+1] if "distance" in parallel_indices: waypoint.Distance = activityData["distance"][parallel_indices["distance"]+1] inPause = isInTimerStop(waypoint.Timestamp) waypoint.Type = WaypointType.Regular if not inPause else WaypointType.Pause if wasInPause and not inPause: waypoint.Type = WaypointType.Resume wasInPause = inPause # We only care if it's possible to start a new lap, i.e. there are more left if currentLapIdx + 1 < len(laps_starts): if laps_starts[currentLapIdx + 1] < waypoint.Timestamp: # A new lap has started currentLapIdx += 1 lap = activity.Laps[currentLapIdx] lap.Waypoints.append(waypoint) if returnFirstLocation: return None # I guess there were no waypoints? if activity.CountTotalWaypoints(): activity.Laps[0].Waypoints[0].Type = WaypointType.Start activity.Laps[-1].Waypoints[-1].Type = WaypointType.End return activity
def _populateActivityFromTrackData(self, activity, recordText, minimumWaypoints=False): lap = Lap() activity.Laps = [lap] ### 1ST RECORD ### # userID; # timestamp - create date?; # type? W=1st # User name; # activity name; # activity type; # another timestamp - start time of event?; # duration.00; # distance (km); # kcal; #; # max alt; # min alt; # max HR; # avg HR; ### TRACK RECORDS ### # timestamp; # type (2=start, 3=end, 0=pause, 1=resume); # latitude; # longitude; #; #; # alt; # hr; wptsWithLocation = False wptsWithNonZeroAltitude = False rows = recordText.split("\n") for row in rows: if row == "OK" or len(row) == 0: continue split = row.split(";") if split[2] == "W": # init record lap.Stats.MovingTime = ActivityStatistic(ActivityStatisticUnit.Time, value=timedelta(seconds=float(split[7])) if split[7] != "" else None) lap.Stats.Distance = ActivityStatistic(ActivityStatisticUnit.Kilometers, value=float(split[8]) if split[8] != "" else None) lap.Stats.HR = ActivityStatistic(ActivityStatisticUnit.BeatsPerMinute, avg=float(split[14]) if split[14] != "" else None, max=float(split[13]) if split[13] != "" else None) lap.Stats.Elevation = ActivityStatistic(ActivityStatisticUnit.Meters, min=float(split[12]) if split[12] != "" else None, max=float(split[11]) if split[11] != "" else None) lap.Stats.Energy = ActivityStatistic(ActivityStatisticUnit.Kilocalories, value=float(split[12]) if split[12] != "" else None) activity.Stats.update(lap.Stats) lap.Stats = activity.Stats activity.Name = split[4] else: wp = Waypoint() if split[1] == "2": wp.Type = WaypointType.Start elif split[1] == "3": wp.Type = WaypointType.End elif split[1] == "0": wp.Type = WaypointType.Pause elif split[1] == "1": wp.Type = WaypointType.Resume else: wp.Type == WaypointType.Regular if split[0] == "": continue # no timestamp, for whatever reason wp.Timestamp = pytz.utc.localize(datetime.strptime(split[0], "%Y-%m-%d %H:%M:%S UTC")) # it's like this as opposed to %z so I know when they change things (it'll break) if split[2] != "": wp.Location = Location(float(split[2]), float(split[3]), None) if wp.Location.Longitude > 180 or wp.Location.Latitude > 90 or wp.Location.Longitude < -180 or wp.Location.Latitude < -90: raise APIExcludeActivity("Out of range lat/lng") if wp.Location.Latitude is not None and wp.Location.Latitude is not None: wptsWithLocation = True if split[6] != "": wp.Location.Altitude = float(split[6]) # why this is missing: who knows? if wp.Location.Altitude != 0: wptsWithNonZeroAltitude = True if split[7] != "": wp.HR = float(split[7]) lap.Waypoints.append(wp) if wptsWithLocation and minimumWaypoints: break lap.Waypoints = sorted(activity.Waypoints, key=lambda v: v.Timestamp) if wptsWithLocation: activity.EnsureTZ(recalculate=True) if not wptsWithNonZeroAltitude: # do this here so, should the activity run near sea level, altitude data won't be spotty for x in lap.Waypoints: # clear waypoints of altitude data if all of them were logged at 0m (invalid) if x.Location is not None: x.Location.Altitude = None else: lap.Waypoints = [] # practically speaking
def _populateActivityFromTrackData(self, activity, recordText, minimumWaypoints=False): lap = Lap() activity.Laps = [lap] ### 1ST RECORD ### # userID; # timestamp - create date?; # type? W=1st # User name; # activity name; # activity type; # another timestamp - start time of event?; # duration.00; # distance (km); # kcal; #; # max alt; # min alt; # max HR; # avg HR; ### TRACK RECORDS ### # timestamp; # type (2=start, 3=end, 0=pause, 1=resume); # latitude; # longitude; #; #; # alt; # hr; wptsWithLocation = False wptsWithNonZeroAltitude = False rows = recordText.split("\n") for row in rows: if row == "OK" or len(row) == 0: continue split = row.split(";") if split[2] == "W": # init record lap.Stats.TimerTime = ActivityStatistic( ActivityStatisticUnit.Time, value=timedelta( seconds=float(split[7])) if split[7] != "" else None) lap.Stats.Distance = ActivityStatistic( ActivityStatisticUnit.Kilometers, value=float(split[8]) if split[8] != "" else None) lap.Stats.HR = ActivityStatistic( ActivityStatisticUnit.BeatsPerMinute, avg=float(split[14]) if split[14] != "" else None, max=float(split[13]) if split[13] != "" else None) lap.Stats.Elevation = ActivityStatistic( ActivityStatisticUnit.Meters, min=float(split[12]) if split[12] != "" else None, max=float(split[11]) if split[11] != "" else None) lap.Stats.Energy = ActivityStatistic( ActivityStatisticUnit.Kilocalories, value=float(split[12]) if split[12] != "" else None) activity.Stats.update(lap.Stats) lap.Stats = activity.Stats activity.Name = split[4] else: wp = Waypoint() if split[1] == "2": wp.Type = WaypointType.Start elif split[1] == "3": wp.Type = WaypointType.End elif split[1] == "0": wp.Type = WaypointType.Pause elif split[1] == "1": wp.Type = WaypointType.Resume else: wp.Type == WaypointType.Regular if split[0] == "": continue # no timestamp, for whatever reason wp.Timestamp = pytz.utc.localize( datetime.strptime(split[0], "%Y-%m-%d %H:%M:%S UTC") ) # it's like this as opposed to %z so I know when they change things (it'll break) if split[2] != "": wp.Location = Location(float(split[2]), float(split[3]), None) if wp.Location.Longitude > 180 or wp.Location.Latitude > 90 or wp.Location.Longitude < -180 or wp.Location.Latitude < -90: raise APIExcludeActivity("Out of range lat/lng") if wp.Location.Latitude is not None and wp.Location.Latitude is not None: wptsWithLocation = True if split[6] != "": wp.Location.Altitude = float( split[6]) # why this is missing: who knows? if wp.Location.Altitude != 0: wptsWithNonZeroAltitude = True if split[7] != "": wp.HR = float(split[7]) lap.Waypoints.append(wp) if wptsWithLocation and minimumWaypoints: break lap.Waypoints = sorted(activity.Waypoints, key=lambda v: v.Timestamp) if wptsWithLocation: if not wptsWithNonZeroAltitude: # do this here so, should the activity run near sea level, altitude data won't be spotty for x in lap.Waypoints: # clear waypoints of altitude data if all of them were logged at 0m (invalid) if x.Location is not None: x.Location.Altitude = None else: lap.Waypoints = [] # practically speaking