Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
    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
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
0
    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