Beispiel #1
0
 def _generate_per_lap_graphs(self):
     '''Build lap based graphs...'''
     logging.debug(">>")
     if self.laps is None:
         logging.debug("No laps to generate graphs from")
         logging.debug("<<")
         return
     #Lap columns
     self._lap_distance = GraphData()
     self._lap_distance.set_color('#CCFF00', '#CCFF00')
     self._lap_distance.graphType = "vspan"
     self._lap_time = GraphData()
     self._lap_time.set_color('#CCFF00', '#CCFF00')
     self._lap_time.graphType = "vspan"
     #Pace
     title = _("Pace by Lap")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Pace'), self.uc.unit_pace)
     self.distance_data['pace_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
     self.distance_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
     self.distance_data['pace_lap'].graphType = "bar"
     xlabel=_("Time (seconds)")
     self.time_data['pace_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
     self.time_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
     self.time_data['pace_lap'].graphType = "bar"
     #Speed
     title = _("Speed by Lap")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Speed'), self.uc.unit_speed)
     self.distance_data['speed_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
     self.distance_data['speed_lap'].set_color('#336633', '#336633')
     self.distance_data['speed_lap'].graphType = "bar"
     xlabel = _("Time (seconds)")
     self.time_data['speed_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
     self.time_data['speed_lap'].set_color('#336633', '#336633')
     self.time_data['speed_lap'].graphType = "bar"
     for lap in self.laps:
         time = float(lap['elapsed_time']) # time in sql is a unicode string
         dist = lap['distance']/1000 #distance in km
         try:
             pace = time/(60*dist) #min/km
         except ZeroDivisionError:
             pace = 0.0
         try:
             avg_speed = dist/(time/3600) # km/hr
         except:
             avg_speed = 0.0
         if self.pace_limit is not None and pace > self.pace_limit:
             logging.debug("Pace (%s) exceeds limit (%s). Setting to 0", pace, self.pace_limit)
             pace = 0.0
         logging.debug("Time: %f, Dist: %f, Pace: %f, Speed: %f", time, dist, pace, avg_speed)
         self._lap_time.addBars(x=time, y=10)
         self._lap_distance.addBars(x=self.uc.distance(dist), y=10)
         self.distance_data['pace_lap'].addBars(x=self.uc.distance(dist), y=pacekm2miles(pace))
         self.time_data['pace_lap'].addBars(x=time, y=self.uc.speed(pace))
         self.distance_data['speed_lap'].addBars(x=self.uc.distance(dist), y=self.uc.speed(avg_speed))
         self.time_data['speed_lap'].addBars(x=time, y=self.uc.speed(avg_speed))
     logging.debug("<<")
 def get_athlete_data(self):
     logging.debug('>>')
     graphdata = {}
     graphdata['weight'] = GraphData(title="Weight",
                                     xlabel="Date",
                                     ylabel="Weight (%s)" %
                                     (self.uc.unit_weight))
     graphdata['weight'].set_color('#3300FF', '#3300FF')
     #graphdata['weight'].graphType = 'date'
     graphdata['bodyfat'] = GraphData(title="Body Fat",
                                      xlabel="Date",
                                      ylabel="Body Fat (%s)" %
                                      (self.uc.unit_weight))
     graphdata['bodyfat'].set_color('#FF6600', '#FF6600')
     #graphdata['bf'].graphType = 'date'
     graphdata['restinghr'] = GraphData(title="Resting Heartrate",
                                        xlabel="Date",
                                        ylabel="Resting Heartrate (bpm)")
     graphdata['restinghr'].set_color('#660000', '#660000')
     graphdata['restinghr'].show_on_y2 = True
     graphdata['maxhr'] = GraphData(title="Maximum Heartrate",
                                    xlabel="Date",
                                    ylabel="Maximum Heartrate (bpm)")
     graphdata['maxhr'].set_color('#33CC99', '#33CC99')
     graphdata['maxhr'].show_on_y2 = True
     for row in self.data:
         if not 'date' in row or not row['date']:
             continue
         else:
             date = row['date']
         if 'weight' in row and row['weight']:
             weight = float(row['weight'])
         else:
             weight = ""
         if 'bodyfat' in row and row['bodyfat'] and weight:
             bf = float(row['bodyfat']) / 100 * weight
         else:
             bf = ""
         graphdata['weight'].addPoints(x=date, y=weight)
         graphdata['bodyfat'].addPoints(x=date, y=bf)
         graphdata['restinghr'].addPoints(x=date, y=row['restinghr'])
         graphdata['maxhr'].addPoints(x=date, y=row['maxhr'])
     #Remove empty data
     for item in list(graphdata.keys()):
         if len(graphdata[item]) == 0:
             logging.debug("No values for %s. Removing...." % item)
             del graphdata[item]
     return graphdata
Beispiel #3
0
    def get_athlete_data(self):
        logging.debug('>>')
        graphdata = {}
        graphdata['weight'] = GraphData(title="Weight", xlabel="Date", ylabel="Weight (%s)" % (self.weight_unit))
        graphdata['weight'].set_color('#3300FF', '#3300FF')
        #graphdata['weight'].graphType = 'date'
        graphdata['bodyfat'] = GraphData(title="Body Fat", xlabel="Date", ylabel="Body Fat (%s)" % (self.weight_unit))
        graphdata['bodyfat'].set_color('#FF6600', '#FF6600')
        #graphdata['bf'].graphType = 'date'
        graphdata['restinghr'] = GraphData(title="Resting Heartrate", xlabel="Date", ylabel="Resting Heartrate (bpm)")
        graphdata['restinghr'].set_color('#660000', '#660000')
        graphdata['restinghr'].show_on_y2 = True
        graphdata['maxhr'] = GraphData(title="Maximum Heartrate", xlabel="Date", ylabel="Maximum Heartrate (bpm)")
        graphdata['maxhr'].set_color('#33CC99', '#33CC99')
        graphdata['maxhr'].show_on_y2 = True
        for row in self.data:
            if not 'date' in row or not row['date']:
                continue
            else:
                date = row['date']
            if 'weight' in row and row['weight']:
                weight = float(row['weight'])
            else:
                weight = ""
            if 'bodyfat' in row and row['bodyfat'] and weight:
                bf = float(row['bodyfat']) / 100 * weight
            else:
                bf = ""
            graphdata['weight'].addPoints(x=date, y=weight)
            graphdata['bodyfat'].addPoints(x=date, y=bf)
            graphdata['restinghr'].addPoints(x=date, y=row['restinghr'])
            graphdata['maxhr'].addPoints(x=date, y=row['maxhr'])
        #Remove empty data
        for item in graphdata.keys():
			if len(graphdata[item]) == 0:
				logging.debug( "No values for %s. Removing...." % item )
				del graphdata[item]
        return graphdata
Beispiel #4
0
 def _init_graph_data(self):
     logging.debug(">>")
     if self.tracklist is None:
         logging.debug("No tracklist in activity")
         logging.debug("<<")
         return
     #Profile
     title = _("Elevation")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Elevation'), self.uc.unit_height)
     self._distance_data['elevation'] = GraphData(title=title,
                                                  xlabel=xlabel,
                                                  ylabel=ylabel)
     self._distance_data['elevation'].set_color('#ff0000', '#ff0000')
     self._distance_data[
         'elevation'].show_on_y1 = True  #Make graph show elevation by default
     xlabel = _("Time (seconds)")
     self._time_data['elevation'] = GraphData(title=title,
                                              xlabel=xlabel,
                                              ylabel=ylabel)
     self._time_data['elevation'].set_color('#ff0000', '#ff0000')
     self._time_data[
         'elevation'].show_on_y1 = True  #Make graph show elevation by default
     #Corrected Elevation...
     title = _("Corrected Elevation")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Corrected Elevation'), self.uc.unit_height)
     self._distance_data['cor_elevation'] = GraphData(title=title,
                                                      xlabel=xlabel,
                                                      ylabel=ylabel)
     self._distance_data['cor_elevation'].set_color('#993333', '#993333')
     xlabel = _("Time (seconds)")
     self._time_data['cor_elevation'] = GraphData(title=title,
                                                  xlabel=xlabel,
                                                  ylabel=ylabel)
     self._time_data['cor_elevation'].set_color('#993333', '#993333')
     #Speed
     title = _("Speed")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Speed'), self.uc.unit_speed)
     self._distance_data['speed'] = GraphData(title=title,
                                              xlabel=xlabel,
                                              ylabel=ylabel)
     self._distance_data['speed'].set_color('#000000', '#000000')
     xlabel = _("Time (seconds)")
     self._time_data['speed'] = GraphData(title=title,
                                          xlabel=xlabel,
                                          ylabel=ylabel)
     self._time_data['speed'].set_color('#000000', '#000000')
     #Pace
     title = _("Pace")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Pace'), self.uc.unit_pace)
     self._distance_data['pace'] = GraphData(title=title,
                                             xlabel=xlabel,
                                             ylabel=ylabel)
     self._distance_data['pace'].set_color('#0000ff', '#0000ff')
     xlabel = _("Time (seconds)")
     self._time_data['pace'] = GraphData(title=title,
                                         xlabel=xlabel,
                                         ylabel=ylabel)
     self._time_data['pace'].set_color('#0000ff', '#0000ff')
     #Heartrate
     title = _("Heart Rate")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
     self._distance_data['hr'] = GraphData(title=title,
                                           xlabel=xlabel,
                                           ylabel=ylabel)
     self._distance_data['hr'].set_color('#00ff00', '#00ff00')
     xlabel = _("Time (seconds)")
     self._time_data['hr'] = GraphData(title=title,
                                       xlabel=xlabel,
                                       ylabel=ylabel)
     self._time_data['hr'].set_color('#00ff00', '#00ff00')
     #Heartrate as %
     maxhr = self.profile.getMaxHR()
     title = _("Heart Rate (% of max)")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Heart Rate'), _('%'))
     self._distance_data['hr_p'] = GraphData(title=title,
                                             xlabel=xlabel,
                                             ylabel=ylabel)
     self._distance_data['hr_p'].set_color('#00ff00', '#00ff00')
     xlabel = _("Time (seconds)")
     self._time_data['hr_p'] = GraphData(title=title,
                                         xlabel=xlabel,
                                         ylabel=ylabel)
     self._time_data['hr_p'].set_color('#00ff00', '#00ff00')
     #Cadence
     title = _("Cadence")
     xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
     ylabel = "%s (%s)" % (_('Cadence'), _('rpm'))
     self._distance_data['cadence'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
     self._distance_data['cadence'].set_color('#cc00ff', '#cc00ff')
     xlabel = _("Time (seconds)")
     self._time_data['cadence'] = GraphData(title=title,
                                            xlabel=xlabel,
                                            ylabel=ylabel)
     self._time_data['cadence'].set_color('#cc00ff', '#cc00ff')
     for track in self.tracklist:
         try:
             pace = 60 / track['velocity']
             if self.pace_limit is not None and pace > self.pace_limit:
                 logging.debug(
                     "Pace (%s) exceeds limit (%s). Setting to 0" %
                     (str(pace), str(self.pace_limit)))
                 pace = 0  #TODO this should be None when we move to newgraph...
         except Exception as e:
             #print type(e), e
             pace = 0
         try:
             hr_p = float(track['hr']) / maxhr * 100
         except:
             hr_p = 0
         self._distance_data['elevation'].addPoints(
             x=self.uc.distance(track['elapsed_distance']),
             y=self.uc.height(track['ele']))
         self._distance_data['cor_elevation'].addPoints(
             x=self.uc.distance(track['elapsed_distance']),
             y=self.uc.height(track['correctedElevation']))
         self._distance_data['speed'].addPoints(
             x=self.uc.distance(track['elapsed_distance']),
             y=self.uc.speed(track['velocity']))
         self._distance_data['pace'].addPoints(x=self.uc.distance(
             track['elapsed_distance']),
                                               y=self.uc.distance(pace))
         self._distance_data['hr'].addPoints(x=self.uc.distance(
             track['elapsed_distance']),
                                             y=track['hr'])
         self._distance_data['hr_p'].addPoints(x=self.uc.distance(
             track['elapsed_distance']),
                                               y=hr_p)
         self._distance_data['cadence'].addPoints(x=self.uc.distance(
             track['elapsed_distance']),
                                                  y=track['cadence'])
         self._time_data['elevation'].addPoints(x=track['time_elapsed'],
                                                y=self.uc.height(
                                                    track['ele']))
         self._time_data['cor_elevation'].addPoints(
             x=track['time_elapsed'],
             y=self.uc.height(track['correctedElevation']))
         self._time_data['speed'].addPoints(x=track['time_elapsed'],
                                            y=self.uc.speed(
                                                track['velocity']))
         self._time_data['pace'].addPoints(x=track['time_elapsed'],
                                           y=self.uc.distance(pace))
         self._time_data['hr'].addPoints(x=track['time_elapsed'],
                                         y=track['hr'])
         self._time_data['hr_p'].addPoints(x=track['time_elapsed'], y=hr_p)
         self._time_data['cadence'].addPoints(x=track['time_elapsed'],
                                              y=track['cadence'])
     #Remove data with no values
     for item in self._distance_data.keys():
         if len(self._distance_data[item]) == 0:
             logging.debug("No values for %s. Removing...." % item)
             del self._distance_data[item]
     for item in self._time_data.keys():
         if len(self._time_data[item]) == 0:
             logging.debug("No values for %s. Removing...." % item)
             del self._time_data[item]
     logging.debug("<<")
     #Add Heartrate zones graphs
     if 'hr' in self._distance_data:
         zones = self.profile.getZones()
         title = _("Heart Rate zone")
         xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
         ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
         self._distance_data['hr_z'] = GraphData(title=title,
                                                 xlabel=xlabel,
                                                 ylabel=ylabel)
         self._distance_data['hr_z'].graphType = "hspan"
         self._distance_data['hr_z'].set_color(None, None)
         xlabel = _("Time (seconds)")
         self._time_data['hr_z'] = GraphData(title=title,
                                             xlabel=xlabel,
                                             ylabel=ylabel)
         self._time_data['hr_z'].set_color(None, None)
         for zone in zones:
             self._distance_data['hr_z'].addPoints(x=zone[0],
                                                   y=zone[1],
                                                   label=zone[3],
                                                   color=zone[2])
             self._time_data['hr_z'].addPoints(x=zone[0],
                                               y=zone[1],
                                               label=zone[3],
                                               color=zone[2])
Beispiel #5
0
class Activity(DeclarativeBase):
    '''
    Class that knows everything about a particular activity

    All values are stored in the class (and DB) in metric and are converted as needed

    tracks                  - (list) tracklist from gpx
    tracklist               - (list of dict) trackpoint data from gpx
    laps                    - (list of dict) lap list
    us_system               - (bool) True: imperial measurement False: metric measurement
    distance_data   - (dict of graphdata classes) contains the graph data with x axis distance
    time_data               - (dict of graphdata classes) contains the graph data with x axis time
    gpx_file                - (string) gpx file name
    gpx                             - (Gpx class) actual gpx instance
    sport_name              - (string) sport name
    sport_id                - (string) id for sport in sports table
    title                   - (string) title of activity
    date                    - (string) date of activity
    time                    - (int) activity duration in seconds
    time_tuple              - (tuple) activity duration as hours, min, secs tuple
    beats                   - (int) average heartrate for activity
    maxbeats                - (int) maximum heartrate for activity
    comments                - (string) activity comments
    calories                - (int) calories of activity
    id                      - (int) id for activity in records table
    date_time_local - (string) date and time of activity in local timezone
    date_time_utc   - (string) date and time of activity in UTC timezone
    date_time               - (datetime) date and time of activity in local timezone
    starttime               - (string)
    distance                - (float) activity distance
    average                 - (float) average speed of activity
    upositive               - (float) height climbed during activity
    unegative               - (float) height decended during activity
    maxspeed                - (float) maximum speed obtained during activity
    maxpace                 - (float) maxium pace obtained during activity
    pace                    - (float) average pace for activity
    has_data                - (bool) true if instance has data populated
    x_axis                  - (string) distance or time, determines what will be graphed on x axis
    x_limits                - (tuple of float) start, end limits of x axis (as determined by matplotlib)
    y1_limits               - (tuple of float) start, end limits of y1 axis (as determined by matplotlib)
    y2_limits               - (tuple of float) start, end limits of y2 axis (as determined by matplotlib)
    x_limits_u              - (tuple of float) start, end limits of x axis (as requested by user)
    y1_limits_u             - (tuple of float) start, end limits of y1 axis (as requested by user)
    y2_limits_u             - (tuple of float) start, end limits of y2 axis (as requested by user)
    show_laps               - (bool) display laps on graphs
    lap_distance    - (graphdata)
    lap_time                - (graphdata)
    pace_limit              - (int) maximum pace that is valid for this activity
    '''
    __tablename__ = 'records'
    average = Column(Float)
    beats = Column(Float)
    calories = Column(ForcedInteger)
    comments = Column(UnicodeText)
    date = Column(Date)
    date_time_local = Column(String(length=40))
    date_time_utc = Column(String(length=40))
    distance = Column(Float)
    duration = Column(ForcedInteger)
    gpslog = deferred(Column(String(length=200)))
    id = Column("id_record", Integer, primary_key=True)
    maxbeats = Column(Float)
    maxpace = Column(Float)
    maxspeed = Column(Float)
    pace = Column(Float)
    sport_id = Column("sport",
                      Integer,
                      ForeignKey('sports.id_sports'),
                      index=True,
                      nullable=False)
    title = Column(Unicode(length=200))
    unegative = Column(Float)
    upositive = Column(Float)

    #relation definitions
    sport = relationship("Sport",
                         backref=backref("activities",
                                         order_by=date,
                                         cascade='all, delete-orphan'))
    equipment = relationship("Equipment",
                             secondary=record_to_equipment,
                             backref=backref("activities", order_by=date))
    Laps = relationship('Lap',
                        backref=backref('activity'),
                        order_by='Lap.lap_number',
                        cascade='all, delete-orphan')

    def __init__(self, **kwargs):
        self._initialize()
        super(Activity, self).__init__(**kwargs)

    @reconstructor
    def _initialize(self):
        logging.debug(">>")
        self.environment = Environment()
        self.uc = uc.UC()
        self.profile = Profile()
        self.has_data = True
        self._distance_data = {}
        self._time_data = {}
        self._lap_time = None
        self._lap_distance = None
        self.time_pause = 0
        self.pace_limit = None
        self._gpx = None
        self.x_axis = "distance"
        self.x_limits = (None, None)
        self.y1_limits = (None, None)
        self.y2_limits = (None, None)
        self.x_limits_u = (None, None)
        self.y1_limits_u = (None, None)
        self.y2_limits_u = (None, None)
        self.y1_grid = False
        self.y2_grid = False
        self.x_grid = False
        self.show_laps = False
        logging.debug("<<")

    @property
    def gpx_file(self):
        if self.id:
            filename = "%s/%s.gpx" % (self.environment.gpx_dir, self.id)
            #It is OK to not have a GPX file for an activity - this just limits us to information in the DB
            if os.path.isfile(filename):
                return filename
        logging.debug("No GPX file found for record id: %s", self.id)
        return None

    @property
    def tracks(self):
        if self.gpx:
            return self.gpx.getTrackList()
        else:
            return None

    @property
    def tracklist(self):
        if self.gpx:
            return self.gpx.trkpoints
        else:
            return None

    @property
    def distance_data(self):
        if not self._distance_data:
            self._init_graph_data()
        return self._distance_data

    @property
    def time_data(self):
        if not self._time_data:
            self._init_graph_data()
        return self._time_data

    @property
    def lap_time(self):
        if not self._lap_time:
            self._generate_per_lap_graphs()
        return self._lap_time

    @property
    def lap_distance(self):
        if not self._lap_distance:
            self._generate_per_lap_graphs()
        return self._lap_distance

    @property
    def time_tuple(self):
        return second2time(self.duration)

    @property
    def date_time(self):
        if self.date_time_local:  #Have a local time stored in DB
            return dateutil.parser.parse(self.date_time_local)
        else:  #No local time in DB
            #datetime with localtime offset (using value from OS)
            return dateutil.parser.parse(self.date_time_utc).astimezone(
                tzlocal())

    @property
    def starttime(self):
        return self.date_time.strftime("%X")

    @property
    def laps(self):
        logging.warning("Deprecated property Activity.laps called")
        ret = []
        for lap in self.Laps:
            d = dict(lap.__dict__)
            d.pop('_sa_instance_state', None)
            ret.append(d)
        return ret

    def __str__(self):
        return '''
tracks (%s)
        tracklist (%s)
        laps (%s)
        us_system (%s)
        distance_data (%s)
        time_data (%s)
        gpx_file (%s)
        gpx (%s)
        sport_name (%s)
        sport_id (%s)
        title (%s)
        date (%s)
        time (%s)
        time_tuple (%s)
        beats (%s)
        maxbeats (%s)
        comments (%s)
        calories (%s)
        id (%s)
        date_time_local (%s)
        date_time_utc (%s)
        date_time (%s)
        starttime (%s)
        distance (%s)
        average (%s)
        upositive (%s)
        unegative (%s)
        maxspeed (%s)
        maxpace (%s)
        pace (%s)
        has_data (%s)
        x_axis (%s)
        x_limits (%s)
        y1_limits (%s)
        y2_limits (%s)
        x_limits_u (%s)
        y1_limits_u (%s)
        y2_limits_u (%s)
        show_laps (%s)
        lap_distance (%s)
        lap_time (%s)
        pace_limit (%s)
''' % ('self.tracks', self.tracklist, self.laps, self.uc.us,
        self.distance_data, self.time_data, self.gpx_file, self.gpx,
        self.sport_name, self.sport_id, self.title, self.date, self.duration,
        self.time_tuple, self.beats, self.maxbeats, self.comments,
        self.calories, self.id, self.date_time_local, self.date_time_utc,
        self.date_time, self.starttime, self.distance, self.average,
        self.upositive, self.unegative, self.maxspeed, self.maxpace, self.pace,
        self.has_data, self.x_axis, self.x_limits, self.y1_limits,
        self.y2_limits, self.x_limits_u, self.y1_limits_u, self.y2_limits_u,
        self.show_laps, self.lap_distance, self.lap_time, self.pace_limit)

    @property
    def gpx(self):
        '''
        Get activity information from the GPX file
        '''
        logging.debug(">>")
        if self._gpx:
            logging.debug("Return pre-created GPX")
            return self._gpx
        elif self.gpx_file:
            logging.debug("Parse GPX")
            #Parse GPX file
            #print "Activity initing GPX.. ",
            self._gpx = Gpx(
                filename=self.gpx_file)  #TODO change GPX code to do less....
            logging.info(
                "GPX Distance: %s | distance (trkpts): %s | duration: %s | duration (trkpts): %s"
                % (self.gpx.total_dist, self.gpx.total_dist_trkpts,
                   self.gpx.total_time, self.gpx.total_time_trkpts))
            time_diff = self.gpx.total_time_trkpts - self.gpx.total_time
            acceptable_lapse = 4  # number of seconds that duration calculated using lap and trkpts data can differ
            if time_diff > acceptable_lapse:
                self.time_pause = time_diff
                logging.debug("Identified non active time: %s s" %
                              self.time_pause)
            return self._gpx
        else:
            logging.debug("No GPX file found")
            return None
        logging.debug("<<")

    @property
    def time(self):
        logging.warning("Deprecated property Activity.time called")
        return self.duration

    @property
    def sport_name(self):
        logging.warning("Deprecated property Activity.sport_name called")
        return self.sport.name

    def _generate_per_lap_graphs(self):
        '''Build lap based graphs...'''
        logging.debug(">>")
        if self.laps is None:
            logging.debug("No laps to generate graphs from")
            logging.debug("<<")
            return
        #Lap columns
        self._lap_distance = GraphData()
        self._lap_distance.set_color('#CCFF00', '#CCFF00')
        self._lap_distance.graphType = "vspan"
        self._lap_time = GraphData()
        self._lap_time.set_color('#CCFF00', '#CCFF00')
        self._lap_time.graphType = "vspan"
        #Pace
        title = _("Pace by Lap")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Pace'), self.uc.unit_pace)
        self.distance_data['pace_lap'] = GraphData(title=title,
                                                   xlabel=xlabel,
                                                   ylabel=ylabel)
        self.distance_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
        self.distance_data['pace_lap'].graphType = "bar"
        xlabel = _("Time (seconds)")
        self.time_data['pace_lap'] = GraphData(title=title,
                                               xlabel=xlabel,
                                               ylabel=ylabel)
        self.time_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
        self.time_data['pace_lap'].graphType = "bar"
        #Speed
        title = _("Speed by Lap")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Speed'), self.uc.unit_speed)
        self.distance_data['speed_lap'] = GraphData(title=title,
                                                    xlabel=xlabel,
                                                    ylabel=ylabel)
        self.distance_data['speed_lap'].set_color('#336633', '#336633')
        self.distance_data['speed_lap'].graphType = "bar"
        xlabel = _("Time (seconds)")
        self.time_data['speed_lap'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
        self.time_data['speed_lap'].set_color('#336633', '#336633')
        self.time_data['speed_lap'].graphType = "bar"
        for lap in self.laps:
            time = float(lap['elapsed_time'].decode(
                'utf-8'))  # time in sql is a unicode string
            dist = lap['distance'] / 1000  #distance in km
            try:
                pace = time / (60 * dist)  #min/km
            except ZeroDivisionError:
                pace = 0.0
            try:
                avg_speed = dist / (time / 3600)  # km/hr
            except:
                avg_speed = 0.0
            if self.pace_limit is not None and pace > self.pace_limit:
                logging.debug("Pace (%s) exceeds limit (%s). Setting to 0" %
                              (str(pace), str(self.pace_limit)))
                pace = 0.0
            logging.debug("Time: %f, Dist: %f, Pace: %f, Speed: %f" %
                          (time, dist, pace, avg_speed))
            self._lap_time.addBars(x=time, y=10)
            self._lap_distance.addBars(x=self.uc.distance(dist), y=10)
            self.distance_data['pace_lap'].addBars(x=self.uc.distance(dist),
                                                   y=pacekm2miles(pace))
            self.time_data['pace_lap'].addBars(x=time, y=self.uc.speed(pace))
            self.distance_data['speed_lap'].addBars(x=self.uc.distance(dist),
                                                    y=self.uc.speed(avg_speed))
            self.time_data['speed_lap'].addBars(x=time,
                                                y=self.uc.speed(avg_speed))
        logging.debug("<<")

    def _init_graph_data(self):
        logging.debug(">>")
        if self.tracklist is None:
            logging.debug("No tracklist in activity")
            logging.debug("<<")
            return
        #Profile
        title = _("Elevation")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Elevation'), self.uc.unit_height)
        self._distance_data['elevation'] = GraphData(title=title,
                                                     xlabel=xlabel,
                                                     ylabel=ylabel)
        self._distance_data['elevation'].set_color('#ff0000', '#ff0000')
        self._distance_data[
            'elevation'].show_on_y1 = True  #Make graph show elevation by default
        xlabel = _("Time (seconds)")
        self._time_data['elevation'] = GraphData(title=title,
                                                 xlabel=xlabel,
                                                 ylabel=ylabel)
        self._time_data['elevation'].set_color('#ff0000', '#ff0000')
        self._time_data[
            'elevation'].show_on_y1 = True  #Make graph show elevation by default
        #Corrected Elevation...
        title = _("Corrected Elevation")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Corrected Elevation'), self.uc.unit_height)
        self._distance_data['cor_elevation'] = GraphData(title=title,
                                                         xlabel=xlabel,
                                                         ylabel=ylabel)
        self._distance_data['cor_elevation'].set_color('#993333', '#993333')
        xlabel = _("Time (seconds)")
        self._time_data['cor_elevation'] = GraphData(title=title,
                                                     xlabel=xlabel,
                                                     ylabel=ylabel)
        self._time_data['cor_elevation'].set_color('#993333', '#993333')
        #Speed
        title = _("Speed")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Speed'), self.uc.unit_speed)
        self._distance_data['speed'] = GraphData(title=title,
                                                 xlabel=xlabel,
                                                 ylabel=ylabel)
        self._distance_data['speed'].set_color('#000000', '#000000')
        xlabel = _("Time (seconds)")
        self._time_data['speed'] = GraphData(title=title,
                                             xlabel=xlabel,
                                             ylabel=ylabel)
        self._time_data['speed'].set_color('#000000', '#000000')
        #Pace
        title = _("Pace")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Pace'), self.uc.unit_pace)
        self._distance_data['pace'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
        self._distance_data['pace'].set_color('#0000ff', '#0000ff')
        xlabel = _("Time (seconds)")
        self._time_data['pace'] = GraphData(title=title,
                                            xlabel=xlabel,
                                            ylabel=ylabel)
        self._time_data['pace'].set_color('#0000ff', '#0000ff')
        #Heartrate
        title = _("Heart Rate")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
        self._distance_data['hr'] = GraphData(title=title,
                                              xlabel=xlabel,
                                              ylabel=ylabel)
        self._distance_data['hr'].set_color('#00ff00', '#00ff00')
        xlabel = _("Time (seconds)")
        self._time_data['hr'] = GraphData(title=title,
                                          xlabel=xlabel,
                                          ylabel=ylabel)
        self._time_data['hr'].set_color('#00ff00', '#00ff00')
        #Heartrate as %
        maxhr = self.profile.getMaxHR()
        title = _("Heart Rate (% of max)")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Heart Rate'), _('%'))
        self._distance_data['hr_p'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
        self._distance_data['hr_p'].set_color('#00ff00', '#00ff00')
        xlabel = _("Time (seconds)")
        self._time_data['hr_p'] = GraphData(title=title,
                                            xlabel=xlabel,
                                            ylabel=ylabel)
        self._time_data['hr_p'].set_color('#00ff00', '#00ff00')
        #Cadence
        title = _("Cadence")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Cadence'), _('rpm'))
        self._distance_data['cadence'] = GraphData(title=title,
                                                   xlabel=xlabel,
                                                   ylabel=ylabel)
        self._distance_data['cadence'].set_color('#cc00ff', '#cc00ff')
        xlabel = _("Time (seconds)")
        self._time_data['cadence'] = GraphData(title=title,
                                               xlabel=xlabel,
                                               ylabel=ylabel)
        self._time_data['cadence'].set_color('#cc00ff', '#cc00ff')
        for track in self.tracklist:
            try:
                pace = 60 / track['velocity']
                if self.pace_limit is not None and pace > self.pace_limit:
                    logging.debug(
                        "Pace (%s) exceeds limit (%s). Setting to 0" %
                        (str(pace), str(self.pace_limit)))
                    pace = 0  #TODO this should be None when we move to newgraph...
            except Exception as e:
                #print type(e), e
                pace = 0
            try:
                hr_p = float(track['hr']) / maxhr * 100
            except:
                hr_p = 0
            self._distance_data['elevation'].addPoints(
                x=self.uc.distance(track['elapsed_distance']),
                y=self.uc.height(track['ele']))
            self._distance_data['cor_elevation'].addPoints(
                x=self.uc.distance(track['elapsed_distance']),
                y=self.uc.height(track['correctedElevation']))
            self._distance_data['speed'].addPoints(
                x=self.uc.distance(track['elapsed_distance']),
                y=self.uc.speed(track['velocity']))
            self._distance_data['pace'].addPoints(x=self.uc.distance(
                track['elapsed_distance']),
                                                  y=self.uc.distance(pace))
            self._distance_data['hr'].addPoints(x=self.uc.distance(
                track['elapsed_distance']),
                                                y=track['hr'])
            self._distance_data['hr_p'].addPoints(x=self.uc.distance(
                track['elapsed_distance']),
                                                  y=hr_p)
            self._distance_data['cadence'].addPoints(x=self.uc.distance(
                track['elapsed_distance']),
                                                     y=track['cadence'])
            self._time_data['elevation'].addPoints(x=track['time_elapsed'],
                                                   y=self.uc.height(
                                                       track['ele']))
            self._time_data['cor_elevation'].addPoints(
                x=track['time_elapsed'],
                y=self.uc.height(track['correctedElevation']))
            self._time_data['speed'].addPoints(x=track['time_elapsed'],
                                               y=self.uc.speed(
                                                   track['velocity']))
            self._time_data['pace'].addPoints(x=track['time_elapsed'],
                                              y=self.uc.distance(pace))
            self._time_data['hr'].addPoints(x=track['time_elapsed'],
                                            y=track['hr'])
            self._time_data['hr_p'].addPoints(x=track['time_elapsed'], y=hr_p)
            self._time_data['cadence'].addPoints(x=track['time_elapsed'],
                                                 y=track['cadence'])
        #Remove data with no values
        for item in self._distance_data.keys():
            if len(self._distance_data[item]) == 0:
                logging.debug("No values for %s. Removing...." % item)
                del self._distance_data[item]
        for item in self._time_data.keys():
            if len(self._time_data[item]) == 0:
                logging.debug("No values for %s. Removing...." % item)
                del self._time_data[item]
        logging.debug("<<")
        #Add Heartrate zones graphs
        if 'hr' in self._distance_data:
            zones = self.profile.getZones()
            title = _("Heart Rate zone")
            xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
            ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
            self._distance_data['hr_z'] = GraphData(title=title,
                                                    xlabel=xlabel,
                                                    ylabel=ylabel)
            self._distance_data['hr_z'].graphType = "hspan"
            self._distance_data['hr_z'].set_color(None, None)
            xlabel = _("Time (seconds)")
            self._time_data['hr_z'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
            self._time_data['hr_z'].set_color(None, None)
            for zone in zones:
                self._distance_data['hr_z'].addPoints(x=zone[0],
                                                      y=zone[1],
                                                      label=zone[3],
                                                      color=zone[2])
                self._time_data['hr_z'].addPoints(x=zone[0],
                                                  y=zone[1],
                                                  label=zone[3],
                                                  color=zone[2])

    def _float(self, value):
        try:
            result = float(value)
        except:
            result = 0.0
        return result

    def _int(self, value):
        try:
            result = int(value)
        except:
            result = 0
        return result

    def get_value_f(self, param, format=None):
        ''' Function to return a value formated as a string
                - takes into account US/metric
                - also appends units if required
        '''
        value = self.get_value(param)
        if not value:
            #Return blank string if value is None or 0
            return ""
        if format is not None:
            result = format % value
        else:
            result = str(value)
        return result

    def get_value(self, param):
        ''' Function to get the value of various params in this activity instance
                Automatically returns values converted to imperial if needed
        '''
        if param == 'distance':
            return self.uc.distance(self.distance)
        elif param == 'average':
            return self.uc.speed(self.average)
        elif param == 'upositive':
            return self.uc.height(self.upositive)
        elif param == 'unegative':
            return self.uc.height(self.unegative)
        elif param == 'maxspeed':
            return self.uc.speed(self.maxspeed)
        elif param == 'maxpace':
            return uc.float2pace(self.uc.pace(self.maxpace))
        elif param == 'pace':
            return uc.float2pace(self.uc.pace(self.pace))
        elif param == 'calories':
            return self.calories
        elif param == 'time':
            if not self.duration:
                return ""
            _hour, _min, _sec = second2time(self.duration)
            if _hour == 0:
                return "%02d:%02d" % (_min, _sec)
            else:
                return "%0d:%02d:%02d" % (_hour, _min, _sec)
        else:
            logging.error(
                "Unable to provide value for unknown parameter (%s) for activity",
                param)
            return None
Beispiel #6
0
class Activity:
    '''
	Class that knows everything about a particular activity

	All values are stored in the class (and DB) in metric and are converted as needed

	tracks			- (list) tracklist from gpx
	tracklist		- (list of dict) trackpoint data from gpx
	laps			- (list of dict) lap list
	tree			- (ElementTree) parsed xml of gpx file
	us_system		- (bool) True: imperial measurement False: metric measurement
	distance_unit	- (string) unit to use for distance
	speed_unit		- (string) unit to use for speed
	distance_data	- (dict of graphdata classes) contains the graph data with x axis distance
	time_data		- (dict of graphdata classes) contains the graph data with x axis time
	height_unit		- (string) unit to use for height
	pace_unit		- (string) unit to use for pace
	gpx_file		- (string) gpx file name
	gpx				- (Gpx class) actual gpx instance
	sport_name		- (string) sport name
	sport_id		- (string) id for sport in sports table
	title			- (string) title of activity
	date			- (string) date of activity
	time			- (int) activity duration in seconds
	time_tuple		- (tuple) activity duration as hours, min, secs tuple
	beats			- (int) average heartrate for activity
	maxbeats 		- (int) maximum heartrate for activity
	comments		- (string) activity comments
	calories		- (int) calories of activity
	id_record		- (string) id for activity in records table
	date_time_local	- (string) date and time of activity in local timezone
	date_time_utc	- (string) date and time of activity in UTC timezone
	date_time		- (datetime) date and time of activity in local timezone
	starttime		- (string)
	distance 		- (float) activity distance
	average			- (float) average speed of activity
	upositive 		- (float) height climbed during activity
	unegative		- (float) height decended during activity
	maxspeed 		- (float) maximum speed obtained during activity
	maxpace 		- (float) maxium pace obtained during activity
	pace			- (float) average pace for activity
	has_data		- (bool) true if instance has data populated
	x_axis			- (string) distance or time, determines what will be graphed on x axis
	x_limits		- (tuple of float) start, end limits of x axis (as determined by matplotlib)
	y1_limits		- (tuple of float) start, end limits of y1 axis (as determined by matplotlib)
	y2_limits		- (tuple of float) start, end limits of y2 axis (as determined by matplotlib)
	x_limits_u		- (tuple of float) start, end limits of x axis (as requested by user)
	y1_limits_u		- (tuple of float) start, end limits of y1 axis (as requested by user)
	y2_limits_u		- (tuple of float) start, end limits of y2 axis (as requested by user)
	show_laps		- (bool) display laps on graphs
	lap_distance	- (graphdata)
	lap_time		- (graphdata)
	pace_limit		- (int) maximum pace that is valid for this activity
	'''
    def __init__(self, pytrainer_main=None, id=None):
        logging.debug(">>")
        self.id = id
        #It is an error to try to initialise with no id
        if self.id is None:
            return
        #It is an error to try to initialise with no reference to pytrainer_main
        if pytrainer_main is None:
            print(
                "Error - must initialise with a reference to the main pytrainer class"
            )
            return
        self.pytrainer_main = pytrainer_main
        self.tracks = None
        self.tracklist = None
        self.laps = None
        self.tree = None
        self.has_data = False
        self.distance_data = {}
        self.time_data = {}
        self.time_pause = 0
        self.pace_limit = None
        self.starttime = None
        self.gpx_distance = None
        #self.upositive = 0
        #self.unegative = 0
        if self.pytrainer_main.profile.getValue("pytraining",
                                                "prf_us_system") == "True":
            self.us_system = True
        else:
            self.us_system = False
        self._set_units()
        self.gpx_file = "%s/%s.gpx" % (self.pytrainer_main.profile.gpxdir, id)
        #It is OK to not have a GPX file for an activity - this just limits us to information in the DB
        if not os.path.isfile(self.gpx_file):
            self.gpx_file = None
            logging.debug("No GPX file found for record id: %s" % id)
        if self.gpx_file is not None:
            self._init_from_gpx_file()
        self._init_from_db()
        self._init_graph_data()
        self._generate_per_lap_graphs()
        self.x_axis = "distance"
        self.x_limits = (None, None)
        self.y1_limits = (None, None)
        self.y2_limits = (None, None)
        self.x_limits_u = (None, None)
        self.y1_limits_u = (None, None)
        self.y2_limits_u = (None, None)
        self.y1_grid = False
        self.y2_grid = False
        self.x_grid = False
        self.show_laps = False
        logging.debug("<<")

    def __str__(self):
        return '''
        tracks (%s)
		tracklist (%s)
		laps (%s)
		tree (%s)
		us_system (%s)
		distance_unit (%s)
		speed_unit (%s)
		distance_data (%s)
		time_data (%s)
		height_unit (%s)
		pace_unit (%s)
		gpx_file (%s)
		gpx (%s)
		sport_name (%s)
		sport_id (%s)
		title (%s)
		date (%s)
		time (%s)
		time_tuple (%s)
		beats (%s)
		maxbeats (%s)
		comments (%s)
		calories (%s)
		id_record (%s)
		date_time_local (%s)
		date_time_utc (%s)
		date_time (%s)
		starttime (%s)
		distance (%s)
		average (%s)
		upositive (%s)
		unegative (%s)
		maxspeed (%s)
		maxpace (%s)
		pace (%s)
		has_data (%s)
		x_axis (%s)
		x_limits (%s)
		y1_limits (%s)
		y2_limits (%s)
		x_limits_u (%s)
		y1_limits_u (%s)
		y2_limits_u (%s)
		show_laps (%s)
		lap_distance (%s)
		lap_time (%s)
		pace_limit (%s)
        ''' % ('self.tracks', self.tracklist, self.laps, self.tree,
               self.us_system, self.distance_unit, self.speed_unit,
               self.distance_data, self.time_data, self.height_unit,
               self.pace_unit, self.gpx_file, self.gpx, self.sport_name,
               self.sport_id, self.title, self.date, self.time,
               self.time_tuple, self.beats, self.maxbeats, self.comments,
               self.calories, self.id_record, self.date_time_local,
               self.date_time_utc, self.date_time, self.starttime,
               self.distance, self.average, self.upositive, self.unegative,
               self.maxspeed, self.maxpace, self.pace, self.has_data,
               self.x_axis, self.x_limits, self.y1_limits, self.y2_limits,
               self.x_limits_u, self.y1_limits_u, self.y2_limits_u,
               self.show_laps, self.lap_distance, self.lap_time,
               self.pace_limit)

    def _set_units(self):
        if self.us_system:
            self.distance_unit = _("miles")
            self.speed_unit = _("miles/h")
            self.pace_unit = _("min/mile")
            self.height_unit = _("feet")
        else:
            self.distance_unit = _("km")
            self.speed_unit = _("km/h")
            self.pace_unit = _("min/km")
            self.height_unit = _("m")
        self.units = {
            'distance': self.distance_unit,
            'average': self.speed_unit,
            'upositive': self.height_unit,
            'unegative': self.height_unit,
            'maxspeed': self.speed_unit,
            'pace': self.pace_unit,
            'maxpace': self.pace_unit
        }

    def _init_from_gpx_file(self):
        '''
		Get activity information from the GPX file
		'''
        logging.debug(">>")
        #Parse GPX file
        #print "Activity initing GPX.. ",
        self.gpx = Gpx(
            filename=self.gpx_file)  #TODO change GPX code to do less....
        self.tree = self.gpx.tree
        self.tracks = self.gpx.getTrackList(
        )  #TODO fix - this should removed and replaced with self.tracklist functionality
        self.tracklist = self.gpx.trkpoints
        self.gpx_distance = self.gpx.total_dist
        logging.info(
            "GPX Distance: %s | distance (trkpts): %s | duration: %s | duration (trkpts): %s"
            % (self.gpx_distance, self.gpx.total_dist_trkpts,
               self.gpx.total_time, self.gpx.total_time_trkpts))
        time_diff = self.gpx.total_time_trkpts - self.gpx.total_time
        acceptable_lapse = 4  # number of seconds that duration calculated using lap and trkpts data can differ
        if time_diff > acceptable_lapse:
            self.time_pause = time_diff
            logging.debug("Identified non active time: %s s" % self.time_pause)
        logging.debug("<<")

    def _init_from_db(self):
        '''
		Get activity information from the DB
		'''
        logging.debug(">>")
        #Get base information
        cols = ("sports.name", "id_sports", "date", "distance", "time",
                "beats", "comments", "average", "calories", "id_record",
                "title", "upositive", "unegative", "maxspeed", "maxpace",
                "pace", "maxbeats", "date_time_utc", "date_time_local",
                "sports.max_pace")
        # outer join on sport id to workaround bug where sport reference is null on records from GPX import
        db_result = self.pytrainer_main.ddbb.select(
            "records left outer join sports on records.sport=sports.id_sports",
            ", ".join(cols), "id_record=\"%s\" " % self.id)
        if len(db_result) == 1:
            row = db_result[0]
            self.sport_name = row[cols.index('sports.name')]
            if self.sport_name == None:
                self.sport_name = ""
            self.sport_id = row[cols.index('id_sports')]
            self.pace_limit = row[cols.index('sports.max_pace')]
            if self.pace_limit == 0 or self.pace_limit == "":
                self.pace_limit = None
            self.title = row[cols.index('title')]
            if self.title is None:
                self.title = ""
            self.date = row[cols.index('date')]
            self.time = self._int(row[cols.index('time')])
            self.time_tuple = Date().second2time(self.time)
            self.beats = self._int(row[cols.index('beats')])
            self.comments = row[cols.index('comments')]
            if self.comments is None:
                self.comments = ""
            self.calories = self._int(row[cols.index('calories')])
            self.id_record = row[cols.index('id_record')]
            self.maxbeats = self._int(row[cols.index('maxbeats')])
            #Sort time....
            # ... use local time if available otherwise use date_time_utc and create a local datetime...
            self.date_time_local = row[cols.index('date_time_local')]
            self.date_time_utc = row[cols.index('date_time_utc')]
            if self.date_time_local is not None:  #Have a local time stored in DB
                self.date_time = dateutil.parser.parse(self.date_time_local)
                self.starttime = self.date_time.strftime("%X")
            else:  #No local time in DB
                tmpDateTime = dateutil.parser.parse(self.date_time_utc)
                self.date_time = tmpDateTime.astimezone(tzlocal(
                ))  #datetime with localtime offset (using value from OS)
                self.starttime = self.date_time.strftime("%X")
            #Sort data that changes for the US etc
            #if self.us_system:
            #	self.distance = km2miles(self._float(row[cols.index('distance')]))
            #	self.average = km2miles(self._float(row[cols.index('average')]))
            #	self.upositive = m2feet(self._float(row[cols.index('upositive')]))
            #	self.unegative = m2feet(self._float(row[cols.index('unegative')]))
            #	self.maxspeed = km2miles(self._float(row[cols.index('maxspeed')]))
            #	self.maxpace = pacekm2miles(self._float(row[cols.index('maxpace')]))
            #	self.pace = pacekm2miles(self._float(row[cols.index('pace')]))
            #else:
            self.distance = self._float(row[cols.index('distance')])
            if not self.distance:
                self.distance = self.gpx_distance
            self.average = self._float(row[cols.index('average')])
            self.upositive = self._float(row[cols.index('upositive')])
            self.unegative = self._float(row[cols.index('unegative')])
            self.maxspeed = self._float(row[cols.index('maxspeed')])
            self.maxpace = self._float(row[cols.index('maxpace')])
            self.pace = self._float(row[cols.index('pace')])
            self.has_data = True
        else:
            raise Exception("Error - multiple results from DB for id: %s" %
                            self.id)
        #Get lap information
        laps = self.pytrainer_main.ddbb.select_dict(
            "laps", ("id_lap", "record", "elapsed_time", "distance",
                     "start_lat", "start_lon", "end_lat", "end_lon",
                     "calories", "lap_number", "intensity", "avg_hr", "max_hr",
                     "max_speed", "laptrigger", "comments"),
            "record=\"%s\"" % self.id)
        if laps is None or laps == [] or len(laps) < 1:  #No laps found
            logging.debug("No laps in DB for record %d" % self.id)
            if self.gpx_file is not None:
                laps = self._get_laps_from_gpx()
        self.laps = laps
        logging.debug("<<")

    def _generate_per_lap_graphs(self):
        '''Build lap based graphs...'''
        logging.debug(">>")
        if self.laps is None:
            logging.debug("No laps to generate graphs from")
            logging.debug("<<")
            return
        #Lap columns
        self.lap_distance = GraphData()
        self.lap_distance.set_color('#CCFF00', '#CCFF00')
        self.lap_distance.graphType = "vspan"
        self.lap_time = GraphData()
        self.lap_time.set_color('#CCFF00', '#CCFF00')
        self.lap_time.graphType = "vspan"
        #Pace
        title = _("Pace by Lap")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Pace'), self.pace_unit)
        self.distance_data['pace_lap'] = GraphData(title=title,
                                                   xlabel=xlabel,
                                                   ylabel=ylabel)
        self.distance_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
        self.distance_data['pace_lap'].graphType = "bar"
        xlabel = _("Time (seconds)")
        self.time_data['pace_lap'] = GraphData(title=title,
                                               xlabel=xlabel,
                                               ylabel=ylabel)
        self.time_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
        self.time_data['pace_lap'].graphType = "bar"
        #Speed
        title = _("Speed by Lap")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Speed'), self.speed_unit)
        self.distance_data['speed_lap'] = GraphData(title=title,
                                                    xlabel=xlabel,
                                                    ylabel=ylabel)
        self.distance_data['speed_lap'].set_color('#336633', '#336633')
        self.distance_data['speed_lap'].graphType = "bar"
        xlabel = _("Time (seconds)")
        self.time_data['speed_lap'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
        self.time_data['speed_lap'].set_color('#336633', '#336633')
        self.time_data['speed_lap'].graphType = "bar"
        for lap in self.laps:
            time = float(lap['elapsed_time'].decode(
                'utf-8'))  # time in sql is a unicode string
            dist = lap['distance'] / 1000  #distance in km
            try:
                pace = time / (60 * dist)  #min/km
            except ZeroDivisionError:
                pace = 0.0
            try:
                avg_speed = dist / (time / 3600)  # km/hr
            except:
                avg_speed = 0.0
            if self.pace_limit is not None and pace > self.pace_limit:
                logging.debug("Pace (%s) exceeds limit (%s). Setting to 0" %
                              (str(pace), str(self.pace_limit)))
                pace = 0.0
            logging.debug("Time: %f, Dist: %f, Pace: %f, Speed: %f" %
                          (time, dist, pace, avg_speed))
            self.lap_time.addBars(x=time, y=10)
            if self.us_system:
                self.lap_distance.addBars(x=km2miles(dist), y=10)
                self.distance_data['pace_lap'].addBars(x=km2miles(dist),
                                                       y=pacekm2miles(pace))
                self.time_data['pace_lap'].addBars(x=time,
                                                   y=pacekm2miles(pace))
                self.distance_data['speed_lap'].addBars(x=km2miles(dist),
                                                        y=km2miles(avg_speed))
                self.time_data['speed_lap'].addBars(x=time,
                                                    y=km2miles(avg_speed))
            else:
                self.lap_distance.addBars(x=dist, y=10)
                self.distance_data['pace_lap'].addBars(x=dist, y=pace)
                self.time_data['pace_lap'].addBars(x=time, y=pace)
                self.distance_data['speed_lap'].addBars(x=dist, y=avg_speed)
                self.time_data['speed_lap'].addBars(x=time, y=avg_speed)
        logging.debug("<<")

    def _get_laps_from_gpx(self):
        logging.debug(">>")
        laps = []
        gpxLaps = self.gpx.getLaps()
        for lap in gpxLaps:
            lap_number = gpxLaps.index(lap)
            tmp_lap = {}
            tmp_lap['record'] = self.id
            tmp_lap['lap_number'] = lap_number
            tmp_lap['elapsed_time'] = lap[0]
            tmp_lap['distance'] = lap[4]
            tmp_lap['start_lat'] = lap[5]
            tmp_lap['start_lon'] = lap[6]
            tmp_lap['end_lat'] = lap[1]
            tmp_lap['end_lon'] = lap[2]
            tmp_lap['calories'] = lap[3]
            laps.append(tmp_lap)
        if laps is not None:
            for lap in laps:
                lap_keys = ", ".join(map(str, lap.keys()))
                lap_values = lap.values()
                self.pytrainer_main.record.insertLaps(lap_keys, lap.values())
        logging.debug("<<")
        return laps

    def _init_graph_data(self):
        logging.debug(">>")
        if self.tracklist is None:
            logging.debug("No tracklist in activity")
            logging.debug("<<")
            return
        #Profile
        title = _("Elevation")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Elevation'), self.height_unit)
        self.distance_data['elevation'] = GraphData(title=title,
                                                    xlabel=xlabel,
                                                    ylabel=ylabel)
        self.distance_data['elevation'].set_color('#ff0000', '#ff0000')
        self.distance_data[
            'elevation'].show_on_y1 = True  #Make graph show elevation by default
        xlabel = _("Time (seconds)")
        self.time_data['elevation'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
        self.time_data['elevation'].set_color('#ff0000', '#ff0000')
        self.time_data[
            'elevation'].show_on_y1 = True  #Make graph show elevation by default
        #Corrected Elevation...
        title = _("Corrected Elevation")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Corrected Elevation'), self.height_unit)
        self.distance_data['cor_elevation'] = GraphData(title=title,
                                                        xlabel=xlabel,
                                                        ylabel=ylabel)
        self.distance_data['cor_elevation'].set_color('#993333', '#993333')
        xlabel = _("Time (seconds)")
        self.time_data['cor_elevation'] = GraphData(title=title,
                                                    xlabel=xlabel,
                                                    ylabel=ylabel)
        self.time_data['cor_elevation'].set_color('#993333', '#993333')
        #Speed
        title = _("Speed")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Speed'), self.speed_unit)
        self.distance_data['speed'] = GraphData(title=title,
                                                xlabel=xlabel,
                                                ylabel=ylabel)
        self.distance_data['speed'].set_color('#000000', '#000000')
        xlabel = _("Time (seconds)")
        self.time_data['speed'] = GraphData(title=title,
                                            xlabel=xlabel,
                                            ylabel=ylabel)
        self.time_data['speed'].set_color('#000000', '#000000')
        #Pace
        title = _("Pace")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Pace'), self.pace_unit)
        self.distance_data['pace'] = GraphData(title=title,
                                               xlabel=xlabel,
                                               ylabel=ylabel)
        self.distance_data['pace'].set_color('#0000ff', '#0000ff')
        xlabel = _("Time (seconds)")
        self.time_data['pace'] = GraphData(title=title,
                                           xlabel=xlabel,
                                           ylabel=ylabel)
        self.time_data['pace'].set_color('#0000ff', '#0000ff')
        #Heartrate
        title = _("Heart Rate")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
        self.distance_data['hr'] = GraphData(title=title,
                                             xlabel=xlabel,
                                             ylabel=ylabel)
        self.distance_data['hr'].set_color('#00ff00', '#00ff00')
        xlabel = _("Time (seconds)")
        self.time_data['hr'] = GraphData(title=title,
                                         xlabel=xlabel,
                                         ylabel=ylabel)
        self.time_data['hr'].set_color('#00ff00', '#00ff00')
        #Heartrate as %
        maxhr = self.pytrainer_main.profile.getMaxHR()
        title = _("Heart Rate (% of max)")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Heart Rate'), _('%'))
        self.distance_data['hr_p'] = GraphData(title=title,
                                               xlabel=xlabel,
                                               ylabel=ylabel)
        self.distance_data['hr_p'].set_color('#00ff00', '#00ff00')
        xlabel = _("Time (seconds)")
        self.time_data['hr_p'] = GraphData(title=title,
                                           xlabel=xlabel,
                                           ylabel=ylabel)
        self.time_data['hr_p'].set_color('#00ff00', '#00ff00')
        #Cadence
        title = _("Cadence")
        xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
        ylabel = "%s (%s)" % (_('Cadence'), _('rpm'))
        self.distance_data['cadence'] = GraphData(title=title,
                                                  xlabel=xlabel,
                                                  ylabel=ylabel)
        self.distance_data['cadence'].set_color('#cc00ff', '#cc00ff')
        xlabel = _("Time (seconds)")
        self.time_data['cadence'] = GraphData(title=title,
                                              xlabel=xlabel,
                                              ylabel=ylabel)
        self.time_data['cadence'].set_color('#cc00ff', '#cc00ff')
        for track in self.tracklist:
            try:
                pace = 60 / track['velocity']
                if self.pace_limit is not None and pace > self.pace_limit:
                    logging.debug(
                        "Pace (%s) exceeds limit (%s). Setting to 0" %
                        (str(pace), str(self.pace_limit)))
                    pace = 0  #TODO this should be None when we move to newgraph...
            except Exception as e:
                #print type(e), e
                pace = 0
            try:
                hr_p = float(track['hr']) / maxhr * 100
            except:
                hr_p = 0
            if self.us_system:
                self.distance_data['elevation'].addPoints(
                    x=km2miles(track['elapsed_distance']),
                    y=m2feet(track['ele']))
                self.distance_data['cor_elevation'].addPoints(
                    x=km2miles(track['elapsed_distance']),
                    y=m2feet(track['correctedElevation']))
                self.distance_data['speed'].addPoints(
                    x=km2miles(track['elapsed_distance']),
                    y=km2miles(track['velocity']))
                self.distance_data['pace'].addPoints(x=km2miles(
                    track['elapsed_distance']),
                                                     y=pacekm2miles(pace))
                self.distance_data['hr'].addPoints(x=km2miles(
                    track['elapsed_distance']),
                                                   y=track['hr'])
                self.distance_data['hr_p'].addPoints(x=km2miles(
                    track['elapsed_distance']),
                                                     y=hr_p)
                self.distance_data['cadence'].addPoints(x=km2miles(
                    track['elapsed_distance']),
                                                        y=track['cadence'])
                self.time_data['elevation'].addPoints(x=track['time_elapsed'],
                                                      y=m2feet(track['ele']))
                self.time_data['cor_elevation'].addPoints(
                    x=track['time_elapsed'],
                    y=m2feet(track['correctedElevation']))
                self.time_data['speed'].addPoints(x=track['time_elapsed'],
                                                  y=km2miles(
                                                      track['velocity']))
                self.time_data['pace'].addPoints(x=track['time_elapsed'],
                                                 y=pacekm2miles(pace))
            else:
                self.distance_data['elevation'].addPoints(
                    x=track['elapsed_distance'], y=track['ele'])
                self.distance_data['cor_elevation'].addPoints(
                    x=track['elapsed_distance'], y=track['correctedElevation'])
                self.distance_data['speed'].addPoints(
                    x=track['elapsed_distance'], y=track['velocity'])
                self.distance_data['pace'].addPoints(
                    x=track['elapsed_distance'], y=pace)
                self.distance_data['hr'].addPoints(x=track['elapsed_distance'],
                                                   y=track['hr'])
                self.distance_data['hr_p'].addPoints(
                    x=track['elapsed_distance'], y=hr_p)
                self.distance_data['cadence'].addPoints(
                    x=track['elapsed_distance'], y=track['cadence'])
                self.time_data['elevation'].addPoints(x=track['time_elapsed'],
                                                      y=track['ele'])
                self.time_data['cor_elevation'].addPoints(
                    x=track['time_elapsed'], y=track['correctedElevation'])
                self.time_data['speed'].addPoints(x=track['time_elapsed'],
                                                  y=track['velocity'])
                self.time_data['pace'].addPoints(x=track['time_elapsed'],
                                                 y=pace)
            self.time_data['hr'].addPoints(x=track['time_elapsed'],
                                           y=track['hr'])
            self.time_data['hr_p'].addPoints(x=track['time_elapsed'], y=hr_p)
            self.time_data['cadence'].addPoints(x=track['time_elapsed'],
                                                y=track['cadence'])
        #Remove data with no values
        for item in self.distance_data.keys():
            if len(self.distance_data[item]) == 0:
                logging.debug("No values for %s. Removing...." % item)
                del self.distance_data[item]
        for item in self.time_data.keys():
            if len(self.time_data[item]) == 0:
                logging.debug("No values for %s. Removing...." % item)
                del self.time_data[item]
        logging.debug("<<")
        #Add Heartrate zones graphs
        if 'hr' in self.distance_data:
            zones = self.pytrainer_main.profile.getZones()
            title = _("Heart Rate zone")
            xlabel = "%s (%s)" % (_('Distance'), self.distance_unit)
            ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
            self.distance_data['hr_z'] = GraphData(title=title,
                                                   xlabel=xlabel,
                                                   ylabel=ylabel)
            self.distance_data['hr_z'].graphType = "hspan"
            self.distance_data['hr_z'].set_color(None, None)
            xlabel = _("Time (seconds)")
            self.time_data['hr_z'] = GraphData(title=title,
                                               xlabel=xlabel,
                                               ylabel=ylabel)
            self.time_data['hr_z'].set_color(None, None)
            for zone in zones:
                self.distance_data['hr_z'].addPoints(x=zone[0],
                                                     y=zone[1],
                                                     label=zone[3],
                                                     color=zone[2])
                self.time_data['hr_z'].addPoints(x=zone[0],
                                                 y=zone[1],
                                                 label=zone[3],
                                                 color=zone[2])

    def _float(self, value):
        try:
            result = float(value)
        except:
            result = 0.0
        return result

    def _int(self, value):
        try:
            result = int(value)
        except:
            result = 0
        return result

    def get_value_f(self, param, format=None, with_units=False):
        ''' Function to return a value formated as a string
			- takes into account US/metric
			- also appends units if required
		'''
        value = self.get_value(param)
        if not value:
            #Return blank string if value is None or 0
            return ""
        if format is not None:
            result = format % value
        else:
            result = str(value)
        if with_units:
            if param in self.units:
                result += self.units[param]
        #print "activity: 509", result
        return result

    def get_value(self, param):
        ''' Function to get the value of various params in this activity instance
			Automatically returns values converted to imperial if needed
		'''
        if param == 'distance':
            if self.us_system:
                return km2miles(self.distance)
            else:
                return self.distance
        elif param == 'average':
            if self.us_system:
                return km2miles(self.average)
            else:
                return self.average
        elif param == 'upositive':
            if self.us_system:
                return m2feet(self.upositive)
            else:
                return self.upositive
        elif param == 'unegative':
            if self.us_system:
                return m2feet(self.unegative)
            else:
                return self.unegative
        elif param == 'maxspeed':
            if self.us_system:
                return km2miles(self.maxspeed)
            else:
                return self.maxspeed
        elif param == 'maxpace':
            if self.us_system:
                return self.pace_from_float(pacekm2miles(self.maxpace))
            else:
                return self.pace_from_float(self.maxpace)
        elif param == 'pace':
            if self.us_system:
                return self.pace_from_float(pacekm2miles(self.pace))
            else:
                return self.pace_from_float(self.pace)
        elif param == 'calories':
            return self.calories
        elif param == 'time':
            if not self.time:
                return ""
            _hour, _min, _sec = self.pytrainer_main.date.second2time(self.time)
            if _hour == 0:
                return "%02d:%02d" % (_min, _sec)
            else:
                return "%0d:%02d:%02d" % (_hour, _min, _sec)
        else:
            print "Unable to provide value for unknown parameter (%s) for activity" % param
            return None

    def set_value(self, param, value):
        ''' Function to set the value of various params in this activity instance
			Automatically converts from imperial if using them
		'''
        _value = _float(value)
        if param == 'distance':
            if self.us_system:
                self.distance = miles2mk(_value)
            else:
                self.distance = _value
        elif param == 'average':
            if self.us_system:
                self.average = miles2mk(_value)
            else:
                self.average = _value
        elif param == 'upositive':
            if self.us_system:
                self.upositive = feet2m(_value)
            else:
                self.upositive = _value
        elif param == 'unegative':
            if self.us_system:
                self.unegative = feet2m(_value)
            else:
                self.unegative = _value
        elif param == 'maxspeed':
            if self.us_system:
                self.maxspeed = miles2mk(_value)
            else:
                self.maxspeed = _value
        elif param == 'maxpace':
            if self.us_system:
                _maxpace = pacemiles2mk(_value)
            else:
                _maxpace = _value
            self.maxpace = self.pace_to_float(_maxpace)
        elif param == 'pace':
            if self.us_system:
                _pace = pacemiles2mk(_value)
            else:
                _pace = _value
            self.pace = self.pace_to_float(_pace)
        else:
            print "Unable to set value (%s) for unknown parameter (%s) for activity" % (
                str(value), param)

    def pace_to_float(self, value):
        '''Take a mm:ss or mm.ss and return float'''
        value = value.replace(':', '.')
        try:
            value = float(value)
        except ValueError:
            value = None
        return value

    def pace_from_float(self, value):
        '''Helper to generate mm:ss from float representation mm.ss (or mm,ss?)'''
        #Check that value supplied is a float
        if not value:
            return ""
        try:
            _value = "%0.2f" % float(value)
        except ValueError:
            _value = str(value)
        return _value.replace('.', ':')
Beispiel #7
0
class Activity:
	'''
	Class that knows everything about a particular activity

	All values are stored in the class (and DB) in metric and are converted as needed

	tracks			- (list) tracklist from gpx
	tracklist		- (list of dict) trackpoint data from gpx
	laps			- (list of dict) lap list
	tree			- (ElementTree) parsed xml of gpx file
	us_system		- (bool) True: imperial measurement False: metric measurement
	distance_unit	- (string) unit to use for distance
	speed_unit		- (string) unit to use for speed
	distance_data	- (dict of graphdata classes) contains the graph data with x axis distance
	time_data		- (dict of graphdata classes) contains the graph data with x axis time
	height_unit		- (string) unit to use for height
	pace_unit		- (string) unit to use for pace
	gpx_file		- (string) gpx file name
	gpx				- (Gpx class) actual gpx instance
	sport_name		- (string) sport name
	sport_id		- (string) id for sport in sports table
	title			- (string) title of activity
	date			- (string) date of activity
	time			- (int) activity duration in seconds
	time_tuple		- (tuple) activity duration as hours, min, secs tuple
	beats			- (int) average heartrate for activity
	maxbeats 		- (int) maximum heartrate for activity
	comments		- (string) activity comments
	calories		- (int) calories of activity
	id_record		- (string) id for activity in records table
	date_time_local	- (string) date and time of activity in local timezone
	date_time_utc	- (string) date and time of activity in UTC timezone
	date_time		- (datetime) date and time of activity in local timezone
	starttime		- (string)
	distance 		- (float) activity distance
	average			- (float) average speed of activity
	upositive 		- (float) height climbed during activity
	unegative		- (float) height decended during activity
	maxspeed 		- (float) maximum speed obtained during activity
	maxpace 		- (float) maxium pace obtained during activity
	pace			- (float) average pace for activity
	has_data		- (bool) true if instance has data populated
	x_axis			- (string) distance or time, determines what will be graphed on x axis
	x_limits		- (tuple of float) start, end limits of x axis (as determined by matplotlib)
	y1_limits		- (tuple of float) start, end limits of y1 axis (as determined by matplotlib)
	y2_limits		- (tuple of float) start, end limits of y2 axis (as determined by matplotlib)
	x_limits_u		- (tuple of float) start, end limits of x axis (as requested by user)
	y1_limits_u		- (tuple of float) start, end limits of y1 axis (as requested by user)
	y2_limits_u		- (tuple of float) start, end limits of y2 axis (as requested by user)
	show_laps		- (bool) display laps on graphs
	lap_distance	- (graphdata)
	lap_time		- (graphdata)
	pace_limit		- (int) maximum pace that is valid for this activity
	'''
	def __init__(self, pytrainer_main = None, id = None):
		logging.debug(">>")
		self.id = id
		#It is an error to try to initialise with no id
		if self.id is None:
			return
		#It is an error to try to initialise with no reference to pytrainer_main
		if pytrainer_main is None:
			print("Error - must initialise with a reference to the main pytrainer class")
			return
		self.pytrainer_main = pytrainer_main
		self.tracks = None
		self.tracklist = None
		self.laps = None
		self.tree = None
		self.has_data = False
		self.distance_data = {}
		self.time_data = {}
		self.time_pause = 0
		self.pace_limit = None
		self.starttime = None
		self.gpx_distance = None
		#self.upositive = 0
		#self.unegative = 0
		if self.pytrainer_main.profile.getValue("pytraining","prf_us_system") == "True":
			self.us_system = True
		else:
			self.us_system = False
		self._set_units()
		self.gpx_file = "%s/%s.gpx" % (self.pytrainer_main.profile.gpxdir, id)
		#It is OK to not have a GPX file for an activity - this just limits us to information in the DB
		if not os.path.isfile(self.gpx_file):
			self.gpx_file = None
			logging.debug("No GPX file found for record id: %s" % id)
		if self.gpx_file is not None:
			self._init_from_gpx_file()
		self._init_from_db()
		self._init_graph_data()
		self._generate_per_lap_graphs()
		self.x_axis = "distance"
		self.x_limits = (None, None)
		self.y1_limits = (None, None)
		self.y2_limits = (None, None)
		self.x_limits_u = (None, None)
		self.y1_limits_u = (None, None)
		self.y2_limits_u = (None, None)
		self.y1_grid = False
		self.y2_grid = False
		self.x_grid = False
		self.show_laps = False
		logging.debug("<<")

	def __str__(self):
		return '''
        tracks (%s)
		tracklist (%s)
		laps (%s)
		tree (%s)
		us_system (%s)
		distance_unit (%s)
		speed_unit (%s)
		distance_data (%s)
		time_data (%s)
		height_unit (%s)
		pace_unit (%s)
		gpx_file (%s)
		gpx (%s)
		sport_name (%s)
		sport_id (%s)
		title (%s)
		date (%s)
		time (%s)
		time_tuple (%s)
		beats (%s)
		maxbeats (%s)
		comments (%s)
		calories (%s)
		id_record (%s)
		date_time_local (%s)
		date_time_utc (%s)
		date_time (%s)
		starttime (%s)
		distance (%s)
		average (%s)
		upositive (%s)
		unegative (%s)
		maxspeed (%s)
		maxpace (%s)
		pace (%s)
		has_data (%s)
		x_axis (%s)
		x_limits (%s)
		y1_limits (%s)
		y2_limits (%s)
		x_limits_u (%s)
		y1_limits_u (%s)
		y2_limits_u (%s)
		show_laps (%s)
		lap_distance (%s)
		lap_time (%s)
		pace_limit (%s)
        ''' % ('self.tracks', self.tracklist, self.laps, self.tree, self.us_system,
			self.distance_unit, self.speed_unit, self.distance_data, self.time_data,
			self.height_unit, self.pace_unit, self.gpx_file, self.gpx, self.sport_name,
			self.sport_id, self.title, self.date, self.time, self.time_tuple, self.beats,
			self.maxbeats, self.comments, self.calories, self.id_record, self.date_time_local,
			self.date_time_utc, self.date_time, self.starttime, self.distance, self.average,
			self.upositive, self.unegative, self.maxspeed, self.maxpace, self.pace, self.has_data,
			self.x_axis, self.x_limits, self.y1_limits, self.y2_limits, self.x_limits_u, self.y1_limits_u,
			self.y2_limits_u, self.show_laps, self.lap_distance, self.lap_time, self.pace_limit)

	def _set_units(self):
		if self.us_system:
			self.distance_unit = _("miles")
			self.speed_unit = _("miles/h")
			self.pace_unit = _("min/mile")
			self.height_unit = _("feet")
		else:
			self.distance_unit = _("km")
			self.speed_unit = _("km/h")
			self.pace_unit = _("min/km")
			self.height_unit = _("m")
		self.units = { 'distance': self.distance_unit, 'average': self.speed_unit, 'upositive': self.height_unit, 'unegative': self.height_unit, 'maxspeed': self.speed_unit, 'pace': self.pace_unit, 'maxpace': self.pace_unit }

	def _init_from_gpx_file(self):
		'''
		Get activity information from the GPX file
		'''
		logging.debug(">>")
		#Parse GPX file
		#print "Activity initing GPX.. ",
		self.gpx = Gpx(filename = self.gpx_file) #TODO change GPX code to do less....
		self.tree = self.gpx.tree
		self.tracks = self.gpx.getTrackList() #TODO fix - this should removed and replaced with self.tracklist functionality
		self.tracklist = self.gpx.trkpoints
		self.gpx_distance = self.gpx.total_dist
		logging.info("GPX Distance: %s | distance (trkpts): %s | duration: %s | duration (trkpts): %s" % (self.gpx_distance, self.gpx.total_dist_trkpts, self.gpx.total_time, self.gpx.total_time_trkpts))
		time_diff = self.gpx.total_time_trkpts - self.gpx.total_time
		acceptable_lapse = 4 # number of seconds that duration calculated using lap and trkpts data can differ
		if time_diff > acceptable_lapse:
			self.time_pause = time_diff
			logging.debug("Identified non active time: %s s" % self.time_pause)
		logging.debug("<<")

	def _init_from_db(self):
		'''
		Get activity information from the DB
		'''
		logging.debug(">>")
		#Get base information
		cols = ("sports.name","id_sports", "date","distance","time","beats","comments",
						"average","calories","id_record","title","upositive","unegative",
						"maxspeed","maxpace","pace","maxbeats","date_time_utc","date_time_local", "sports.max_pace")
		# outer join on sport id to workaround bug where sport reference is null on records from GPX import
		db_result = self.pytrainer_main.ddbb.select("records left outer join sports on records.sport=sports.id_sports",
					", ".join(cols),
					"id_record=\"%s\" " %self.id)
		if len(db_result) == 1:
			row = db_result[0]
			self.sport_name = row[cols.index('sports.name')]
			if self.sport_name == None:
				self.sport_name = ""
			self.sport_id = row[cols.index('id_sports')]
			self.pace_limit = row[cols.index('sports.max_pace')]
			if self.pace_limit == 0 or self.pace_limit == "":
				self.pace_limit = None
			self.title = row[cols.index('title')]
			if self.title is None:
				self.title = ""
			self.date = row[cols.index('date')]
			self.time = self._int(row[cols.index('time')])
			self.time_tuple = Date().second2time(self.time)
			self.beats = self._int(row[cols.index('beats')])
			self.comments = row[cols.index('comments')]
			if self.comments is None:
				self.comments = ""
			self.calories = self._int(row[cols.index('calories')])
			self.id_record = row[cols.index('id_record')]
			self.maxbeats = self._int(row[cols.index('maxbeats')])
			#Sort time....
			# ... use local time if available otherwise use date_time_utc and create a local datetime...
			self.date_time_local = row[cols.index('date_time_local')]
			self.date_time_utc = row[cols.index('date_time_utc')]
			if self.date_time_local is not None: #Have a local time stored in DB
				self.date_time = dateutil.parser.parse(self.date_time_local)
				self.starttime = self.date_time.strftime("%X")
			else: #No local time in DB
				tmpDateTime = dateutil.parser.parse(self.date_time_utc)
				self.date_time = tmpDateTime.astimezone(tzlocal()) #datetime with localtime offset (using value from OS)
				self.starttime = self.date_time.strftime("%X")
			#Sort data that changes for the US etc
			#if self.us_system:
			#	self.distance = km2miles(self._float(row[cols.index('distance')]))
			#	self.average = km2miles(self._float(row[cols.index('average')]))
			#	self.upositive = m2feet(self._float(row[cols.index('upositive')]))
			#	self.unegative = m2feet(self._float(row[cols.index('unegative')]))
			#	self.maxspeed = km2miles(self._float(row[cols.index('maxspeed')]))
			#	self.maxpace = pacekm2miles(self._float(row[cols.index('maxpace')]))
			#	self.pace = pacekm2miles(self._float(row[cols.index('pace')]))
			#else:
			self.distance = self._float(row[cols.index('distance')])
			if not self.distance:
				self.distance = self.gpx_distance
			self.average = self._float(row[cols.index('average')])
			self.upositive = self._float(row[cols.index('upositive')])
			self.unegative = self._float(row[cols.index('unegative')])
			self.maxspeed = self._float(row[cols.index('maxspeed')])
			self.maxpace = self._float(row[cols.index('maxpace')])
			self.pace = self._float(row[cols.index('pace')])
			self.has_data = True
		else:
			raise Exception( "Error - multiple results from DB for id: %s" % self.id )
		#Get lap information
		laps = self.pytrainer_main.ddbb.select_dict("laps",
					("id_lap", "record", "elapsed_time", "distance", "start_lat", "start_lon", "end_lat", "end_lon", "calories", "lap_number", "intensity", "avg_hr", "max_hr", "max_speed", "laptrigger", "comments"),
					"record=\"%s\"" % self.id)
		if laps is None or laps == [] or len(laps) < 1:  #No laps found
			logging.debug("No laps in DB for record %d" % self.id)
			if self.gpx_file is not None:
				laps = self._get_laps_from_gpx()
		self.laps = laps
		logging.debug("<<")

	def _generate_per_lap_graphs(self):
		'''Build lap based graphs...'''
		logging.debug(">>")
		if self.laps is None:
			logging.debug("No laps to generate graphs from")
			logging.debug("<<")
			return
		#Lap columns
		self.lap_distance = GraphData()
		self.lap_distance.set_color('#CCFF00', '#CCFF00')
		self.lap_distance.graphType = "vspan"
		self.lap_time = GraphData()
		self.lap_time.set_color('#CCFF00', '#CCFF00')
		self.lap_time.graphType = "vspan"
		#Pace
		title=_("Pace by Lap")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Pace'), self.pace_unit)
		self.distance_data['pace_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
		self.distance_data['pace_lap'].graphType = "bar"
		xlabel=_("Time (seconds)")
		self.time_data['pace_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.time_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
		self.time_data['pace_lap'].graphType = "bar"
		#Speed
		title=_("Speed by Lap")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Speed'), self.speed_unit)
		self.distance_data['speed_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['speed_lap'].set_color('#336633', '#336633')
		self.distance_data['speed_lap'].graphType = "bar"
		xlabel=_("Time (seconds)")
		self.time_data['speed_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.time_data['speed_lap'].set_color('#336633', '#336633')
		self.time_data['speed_lap'].graphType = "bar"
		for lap in self.laps:
			time = float( lap['elapsed_time'].decode('utf-8') ) # time in sql is a unicode string
			dist = lap['distance']/1000 #distance in km
			try:
				pace = time/(60*dist) #min/km
			except ZeroDivisionError:
				pace = 0.0
			try:
				avg_speed = dist/(time/3600) # km/hr
			except:
				avg_speed = 0.0
			if self.pace_limit is not None and pace > self.pace_limit:
				logging.debug("Pace (%s) exceeds limit (%s). Setting to 0" % (str(pace), str(self.pace_limit)))
				pace = 0.0
			logging.debug("Time: %f, Dist: %f, Pace: %f, Speed: %f" % (time, dist, pace, avg_speed) )
			self.lap_time.addBars(x=time, y=10)
			if self.us_system:
				self.lap_distance.addBars(x=km2miles(dist), y=10)
				self.distance_data['pace_lap'].addBars(x=km2miles(dist), y=pacekm2miles(pace))
				self.time_data['pace_lap'].addBars(x=time, y=pacekm2miles(pace))
				self.distance_data['speed_lap'].addBars(x=km2miles(dist), y=km2miles(avg_speed))
				self.time_data['speed_lap'].addBars(x=time, y=km2miles(avg_speed))
			else:
				self.lap_distance.addBars(x=dist, y=10)
				self.distance_data['pace_lap'].addBars(x=dist, y=pace)
				self.time_data['pace_lap'].addBars(x=time, y=pace)
				self.distance_data['speed_lap'].addBars(x=dist, y=avg_speed)
				self.time_data['speed_lap'].addBars(x=time, y=avg_speed)
		logging.debug("<<")

	def _get_laps_from_gpx(self):
		logging.debug(">>")
		laps = []
		gpxLaps = self.gpx.getLaps()
		for lap in gpxLaps:
			lap_number = gpxLaps.index(lap)
			tmp_lap = {}
			tmp_lap['record'] = self.id
			tmp_lap['lap_number'] = lap_number
			tmp_lap['elapsed_time'] = lap[0]
			tmp_lap['distance'] = lap[4]
			tmp_lap['start_lat'] = lap[5]
			tmp_lap['start_lon'] = lap[6]
			tmp_lap['end_lat'] = lap[1]
			tmp_lap['end_lon'] = lap[2]
			tmp_lap['calories'] = lap[3]
			laps.append(tmp_lap)
		if laps is not None:
			for lap in laps:
				lap_keys = ", ".join(map(str, lap.keys()))
				lap_values = lap.values()
				self.pytrainer_main.record.insertLaps(lap_keys,lap.values())
		logging.debug("<<")
		return laps

	def _init_graph_data(self):
		logging.debug(">>")
		if self.tracklist is None:
			logging.debug("No tracklist in activity")
			logging.debug("<<")
			return
		#Profile
		title=_("Elevation")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Elevation'), self.height_unit)
		self.distance_data['elevation'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['elevation'].set_color('#ff0000', '#ff0000')
		self.distance_data['elevation'].show_on_y1 = True #Make graph show elevation by default
		xlabel=_("Time (seconds)")
		self.time_data['elevation'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
		self.time_data['elevation'].set_color('#ff0000', '#ff0000')
		self.time_data['elevation'].show_on_y1 = True #Make graph show elevation by default
		#Corrected Elevation...
		title=_("Corrected Elevation")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Corrected Elevation'), self.height_unit)
		self.distance_data['cor_elevation'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['cor_elevation'].set_color('#993333', '#993333')
		xlabel=_("Time (seconds)")
		self.time_data['cor_elevation'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
		self.time_data['cor_elevation'].set_color('#993333', '#993333')
		#Speed
		title=_("Speed")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Speed'), self.speed_unit)
		self.distance_data['speed'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['speed'].set_color('#000000', '#000000')
		xlabel=_("Time (seconds)")
		self.time_data['speed'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
		self.time_data['speed'].set_color('#000000', '#000000')
		#Pace
		title=_("Pace")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Pace'), self.pace_unit)
		self.distance_data['pace'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['pace'].set_color('#0000ff', '#0000ff')
		xlabel=_("Time (seconds)")
		self.time_data['pace'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
		self.time_data['pace'].set_color('#0000ff', '#0000ff')
		#Heartrate
		title=_("Heart Rate")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Heart Rate'), _('bpm'))
		self.distance_data['hr'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['hr'].set_color('#00ff00', '#00ff00')
		xlabel=_("Time (seconds)")
		self.time_data['hr'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
		self.time_data['hr'].set_color('#00ff00', '#00ff00')
		#Heartrate as %
		maxhr = self.pytrainer_main.profile.getMaxHR()
		title=_("Heart Rate (% of max)")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Heart Rate'), _('%'))
		self.distance_data['hr_p'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['hr_p'].set_color('#00ff00', '#00ff00')
		xlabel=_("Time (seconds)")
		self.time_data['hr_p'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
		self.time_data['hr_p'].set_color('#00ff00', '#00ff00')
		#Cadence
		title=_("Cadence")
		xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
		ylabel="%s (%s)" % (_('Cadence'), _('rpm'))
		self.distance_data['cadence'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
		self.distance_data['cadence'].set_color('#cc00ff', '#cc00ff')
		xlabel=_("Time (seconds)")
		self.time_data['cadence'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
		self.time_data['cadence'].set_color('#cc00ff', '#cc00ff')
		for track in self.tracklist:
			try:
				pace = 60/track['velocity']
				if self.pace_limit is not None and pace > self.pace_limit:
					logging.debug("Pace (%s) exceeds limit (%s). Setting to 0" % (str(pace), str(self.pace_limit)))
					pace = 0  #TODO this should be None when we move to newgraph...
			except Exception as e:
				#print type(e), e
				pace = 0
			try:
				hr_p = float(track['hr'])/maxhr*100
			except:
				hr_p = 0
			if self.us_system:
				self.distance_data['elevation'].addPoints(x=km2miles(track['elapsed_distance']), y=m2feet(track['ele']))
				self.distance_data['cor_elevation'].addPoints(x=km2miles(track['elapsed_distance']), y=m2feet(track['correctedElevation']))
				self.distance_data['speed'].addPoints(x=km2miles(track['elapsed_distance']), y=km2miles(track['velocity']))
				self.distance_data['pace'].addPoints(x=km2miles(track['elapsed_distance']), y=pacekm2miles(pace))
				self.distance_data['hr'].addPoints(x=km2miles(track['elapsed_distance']), y=track['hr'])
				self.distance_data['hr_p'].addPoints(x=km2miles(track['elapsed_distance']), y=hr_p)
				self.distance_data['cadence'].addPoints(x=km2miles(track['elapsed_distance']), y=track['cadence'])
				self.time_data['elevation'].addPoints(x=track['time_elapsed'], y=m2feet(track['ele']))
				self.time_data['cor_elevation'].addPoints(x=track['time_elapsed'], y=m2feet(track['correctedElevation']))
				self.time_data['speed'].addPoints(x=track['time_elapsed'], y=km2miles(track['velocity']))
				self.time_data['pace'].addPoints(x=track['time_elapsed'], y=pacekm2miles(pace))
			else:
				self.distance_data['elevation'].addPoints(x=track['elapsed_distance'], y=track['ele'])
				self.distance_data['cor_elevation'].addPoints(x=track['elapsed_distance'], y=track['correctedElevation'])
				self.distance_data['speed'].addPoints(x=track['elapsed_distance'], y=track['velocity'])
				self.distance_data['pace'].addPoints(x=track['elapsed_distance'], y=pace)
				self.distance_data['hr'].addPoints(x=track['elapsed_distance'], y=track['hr'])
				self.distance_data['hr_p'].addPoints(x=track['elapsed_distance'], y=hr_p)
				self.distance_data['cadence'].addPoints(x=track['elapsed_distance'], y=track['cadence'])
				self.time_data['elevation'].addPoints(x=track['time_elapsed'], y=track['ele'])
				self.time_data['cor_elevation'].addPoints(x=track['time_elapsed'], y=track['correctedElevation'])
				self.time_data['speed'].addPoints(x=track['time_elapsed'], y=track['velocity'])
				self.time_data['pace'].addPoints(x=track['time_elapsed'], y=pace)
			self.time_data['hr'].addPoints(x=track['time_elapsed'], y=track['hr'])
			self.time_data['hr_p'].addPoints(x=track['time_elapsed'], y=hr_p)
			self.time_data['cadence'].addPoints(x=track['time_elapsed'], y=track['cadence'])
		#Remove data with no values
		for item in self.distance_data.keys():
			if len(self.distance_data[item]) == 0:
				logging.debug( "No values for %s. Removing...." % item )
				del self.distance_data[item]
		for item in self.time_data.keys():
			if len(self.time_data[item]) == 0:
				logging.debug( "No values for %s. Removing...." % item )
				del self.time_data[item]
		logging.debug("<<")
		#Add Heartrate zones graphs
		if 'hr' in self.distance_data:
			zones = self.pytrainer_main.profile.getZones()		
			title=_("Heart Rate zone")
			xlabel="%s (%s)" % (_('Distance'), self.distance_unit)
			ylabel="%s (%s)" % (_('Heart Rate'), _('bpm'))
			self.distance_data['hr_z'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
			self.distance_data['hr_z'].graphType = "hspan"
			self.distance_data['hr_z'].set_color(None, None)
			xlabel=_("Time (seconds)")
			self.time_data['hr_z'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
			self.time_data['hr_z'].set_color(None, None)
			for zone in zones:
				self.distance_data['hr_z'].addPoints(x=zone[0], y=zone[1], label=zone[3], color=zone[2])
				self.time_data['hr_z'].addPoints(x=zone[0], y=zone[1], label=zone[3], color=zone[2])

	def _float(self, value):
		try:
			result = float(value)
		except:
			result = 0.0
		return result

	def _int(self, value):
		try:
			result = int(value)
		except:
			result = 0
		return result

	def get_value_f(self, param, format=None, with_units=False):
		''' Function to return a value formated as a string
			- takes into account US/metric
			- also appends units if required
		'''
		value = self.get_value(param)
		if not value:
			#Return blank string if value is None or 0
			return ""
		if format is not None:
			result = format % value
		else:
			result = str(value)
		if with_units:
			if param in self.units:
				result += self.units[param]
		#print "activity: 509", result
		return result

	def get_value(self, param):
		''' Function to get the value of various params in this activity instance
			Automatically returns values converted to imperial if needed
		'''
		if param == 'distance':
			if self.us_system:
				return km2miles(self.distance)
			else:
				return self.distance
		elif param == 'average':
			if self.us_system:
				return km2miles(self.average)
			else:
				return self.average
		elif param == 'upositive':
			if self.us_system:
				return m2feet(self.upositive)
			else:
				return self.upositive
		elif param == 'unegative':
			if self.us_system:
				return m2feet(self.unegative)
			else:
				return self.unegative
		elif param == 'maxspeed':
			if self.us_system:
				return km2miles(self.maxspeed)
			else:
				return self.maxspeed
		elif param == 'maxpace':
			if self.us_system:
				return self.pace_from_float(pacekm2miles(self.maxpace))
			else:
				return self.pace_from_float(self.maxpace)
		elif param == 'pace':
			if self.us_system:
				return self.pace_from_float(pacekm2miles(self.pace))
			else:
				return self.pace_from_float(self.pace)
		elif param == 'calories':
			return self.calories
		elif param == 'time':
			if not self.time:
				return ""
			_hour,_min,_sec=self.pytrainer_main.date.second2time(self.time)
			if _hour == 0:
				return "%02d:%02d" % (_min, _sec)
			else:
				return "%0d:%02d:%02d" % (_hour, _min, _sec)
		else:
			print "Unable to provide value for unknown parameter (%s) for activity" % param
			return None

	def set_value(self, param, value):
		''' Function to set the value of various params in this activity instance
			Automatically converts from imperial if using them
		'''
		_value = _float(value)
		if param == 'distance':
			if self.us_system:
				self.distance = miles2mk(_value)
			else:
				self.distance = _value
		elif param == 'average':
			if self.us_system:
				self.average = miles2mk(_value)
			else:
				self.average = _value
		elif param == 'upositive':
			if self.us_system:
				self.upositive = feet2m(_value)
			else:
				self.upositive = _value
		elif param == 'unegative':
			if self.us_system:
				self.unegative = feet2m(_value)
			else:
				self.unegative = _value
		elif param == 'maxspeed':
			if self.us_system:
				self.maxspeed = miles2mk(_value)
			else:
				self.maxspeed = _value
		elif param == 'maxpace':
			if self.us_system:
				_maxpace = pacemiles2mk(_value)
			else:
				_maxpace = _value
			self.maxpace = self.pace_to_float(_maxpace)
		elif param == 'pace':
			if self.us_system:
				_pace = pacemiles2mk(_value)
			else:
				_pace = _value
			self.pace = self.pace_to_float(_pace)
		else:
			print "Unable to set value (%s) for unknown parameter (%s) for activity" % (str(value), param)


	def pace_to_float(self, value):
		'''Take a mm:ss or mm.ss and return float'''
		value = value.replace(':', '.')
		try:
			value = float(value)
		except ValueError:
			value = None
		return value

	def pace_from_float(self, value):
		'''Helper to generate mm:ss from float representation mm.ss (or mm,ss?)'''
		#Check that value supplied is a float
		if not value:
			return ""
		try:
			_value = "%0.2f" % float(value)
		except ValueError:
			_value = str(value)
		return _value.replace('.',':')
Beispiel #8
0
class Activity(DeclarativeBase):
    '''
    Class that knows everything about a particular activity

    All values are stored in the class (and DB) in metric and are converted as needed

    tracks                  - (list) tracklist from gpx
    tracklist               - (list of dict) trackpoint data from gpx
    laps                    - (list of dict) lap list
    us_system               - (bool) True: imperial measurement False: metric measurement
    distance_data   - (dict of graphdata classes) contains the graph data with x axis distance
    time_data               - (dict of graphdata classes) contains the graph data with x axis time
    gpx_file                - (string) gpx file name
    gpx                             - (Gpx class) actual gpx instance
    sport_name              - (string) sport name
    sport_id                - (string) id for sport in sports table
    title                   - (string) title of activity
    date                    - (string) date of activity
    time                    - (int) activity duration in seconds
    time_tuple              - (tuple) activity duration as hours, min, secs tuple
    beats                   - (int) average heartrate for activity
    maxbeats                - (int) maximum heartrate for activity
    comments                - (string) activity comments
    calories                - (int) calories of activity
    id                      - (int) id for activity in records table
    date_time_local - (string) date and time of activity in local timezone
    date_time_utc   - (string) date and time of activity in UTC timezone
    date_time               - (datetime) date and time of activity in local timezone
    starttime               - (string)
    distance                - (float) activity distance
    average                 - (float) average speed of activity
    upositive               - (float) height climbed during activity
    unegative               - (float) height decended during activity
    maxspeed                - (float) maximum speed obtained during activity
    maxpace                 - (float) maxium pace obtained during activity
    pace                    - (float) average pace for activity
    has_data                - (bool) true if instance has data populated
    x_axis                  - (string) distance or time, determines what will be graphed on x axis
    x_limits                - (tuple of float) start, end limits of x axis (as determined by matplotlib)
    y1_limits               - (tuple of float) start, end limits of y1 axis (as determined by matplotlib)
    y2_limits               - (tuple of float) start, end limits of y2 axis (as determined by matplotlib)
    x_limits_u              - (tuple of float) start, end limits of x axis (as requested by user)
    y1_limits_u             - (tuple of float) start, end limits of y1 axis (as requested by user)
    y2_limits_u             - (tuple of float) start, end limits of y2 axis (as requested by user)
    show_laps               - (bool) display laps on graphs
    lap_distance    - (graphdata)
    lap_time                - (graphdata)
    pace_limit              - (int) maximum pace that is valid for this activity
    '''
    __tablename__ = 'records'
    average = Column(Float)
    beats = Column(Float)
    calories = Column(ForcedInteger)
    comments = Column(UnicodeText)
    date = Column(Date)
    date_time_local = Column(String(length=40))
    date_time_utc = Column(String(length=40))
    distance = Column(Float)
    duration = Column(ForcedInteger)
    gpslog = deferred(Column(String(length=200)))
    id = Column("id_record", Integer, primary_key=True)
    maxbeats = Column(Float)
    maxpace = Column(Float)
    maxspeed = Column(Float)
    pace = Column(Float)
    sport_id = Column("sport", Integer, ForeignKey('sports.id_sports'),
                          index=True, nullable=False)
    title = Column(Unicode(length=200))
    unegative = Column(Float)
    upositive = Column(Float)

    #relation definitions
    sport = relationship("Sport", backref=backref("activities", order_by=date,
                                                  cascade='all, delete-orphan'))
    equipment = relationship("Equipment", secondary=record_to_equipment,
                             backref=backref("activities", order_by=date))
    Laps = relationship('Lap', backref=backref('activity'),
                        order_by='Lap.lap_number',
                        cascade='all, delete-orphan')

    def __init__(self, **kwargs):
        self._initialize()
        super(Activity, self).__init__(**kwargs)

    @reconstructor
    def _initialize(self):
        logging.debug(">>")
        self.environment = Environment()
        self.uc = uc.UC()
        self.profile = Profile()
        self.has_data = True
        self._distance_data = {}
        self._time_data = {}
        self._lap_time = None
        self._lap_distance = None
        self.time_pause = 0
        self.pace_limit = None
        self._gpx = None
        self.x_axis = "distance"
        self.x_limits = (None, None)
        self.y1_limits = (None, None)
        self.y2_limits = (None, None)
        self.x_limits_u = (None, None)
        self.y1_limits_u = (None, None)
        self.y2_limits_u = (None, None)
        self.y1_grid = False
        self.y2_grid = False
        self.x_grid = False
        self.show_laps = False
        logging.debug("<<")

    @property
    def gpx_file(self):
        if self.id:
            filename = "%s/%s.gpx" % (self.environment.gpx_dir, self.id)
            #It is OK to not have a GPX file for an activity - this just limits us to information in the DB
            if os.path.isfile(filename):
                return filename
        logging.debug("No GPX file found for record id: %s", self.id)
        return None

    @property
    def tracks(self):
        if self.gpx:
            return self.gpx.getTrackList()
        else:
            return None

    @property
    def tracklist(self):
        if self.gpx:
            return self.gpx.trkpoints
        else:
            return None

    @property
    def distance_data(self):
        if not self._distance_data:
            self._init_graph_data()
        return self._distance_data

    @property
    def time_data(self):
        if not self._time_data:
            self._init_graph_data()
        return self._time_data

    @property
    def lap_time(self):
        if not self._lap_time:
            self._generate_per_lap_graphs()
        return self._lap_time

    @property
    def lap_distance(self):
        if not self._lap_distance:
            self._generate_per_lap_graphs()
        return self._lap_distance

    @property
    def time_tuple(self):
        return second2time(self.duration)

    @property
    def date_time(self):
        if self.date_time_local: #Have a local time stored in DB
            return dateutil.parser.parse(self.date_time_local)
        else: #No local time in DB
            #datetime with localtime offset (using value from OS)
            return dateutil.parser.parse(self.date_time_utc).astimezone(tzlocal())

    @property
    def starttime(self):
        return self.date_time.strftime("%X")

    @property
    def laps(self):
        warnings.warn("Deprecated property Activity.laps called", DeprecationWarning, stacklevel=2)
        ret = []
        for lap in self.Laps:
            d = dict(lap.__dict__)
            d.pop('_sa_instance_state', None)
            ret.append(d)
        return ret

    def __str__(self):
        return '''
tracks (%s)
        tracklist (%s)
        laps (%s)
        us_system (%s)
        distance_data (%s)
        time_data (%s)
        gpx_file (%s)
        gpx (%s)
        sport_name (%s)
        sport_id (%s)
        title (%s)
        date (%s)
        time (%s)
        time_tuple (%s)
        beats (%s)
        maxbeats (%s)
        comments (%s)
        calories (%s)
        id (%s)
        date_time_local (%s)
        date_time_utc (%s)
        date_time (%s)
        starttime (%s)
        distance (%s)
        average (%s)
        upositive (%s)
        unegative (%s)
        maxspeed (%s)
        maxpace (%s)
        pace (%s)
        has_data (%s)
        x_axis (%s)
        x_limits (%s)
        y1_limits (%s)
        y2_limits (%s)
        x_limits_u (%s)
        y1_limits_u (%s)
        y2_limits_u (%s)
        show_laps (%s)
        lap_distance (%s)
        lap_time (%s)
        pace_limit (%s)
''' % ('self.tracks', self.tracklist, self.laps, self.uc.us,
                self.distance_data, self.time_data,
                self.gpx_file, self.gpx, self.sport_name,
                self.sport_id, self.title, self.date, self.duration, self.time_tuple, self.beats,
                self.maxbeats, self.comments, self.calories, self.id, self.date_time_local,
                self.date_time_utc, self.date_time, self.starttime, self.distance, self.average,
                self.upositive, self.unegative, self.maxspeed, self.maxpace, self.pace, self.has_data,
                self.x_axis, self.x_limits, self.y1_limits, self.y2_limits, self.x_limits_u, self.y1_limits_u,
                self.y2_limits_u, self.show_laps, self.lap_distance, self.lap_time, self.pace_limit)

    @property
    def gpx(self):
        '''
        Get activity information from the GPX file
        '''
        logging.debug(">>")
        if self._gpx:
            logging.debug("Return pre-created GPX")
            return self._gpx
        elif self.gpx_file:
            logging.debug("Parse GPX")
            #Parse GPX file
            #print "Activity initing GPX.. ",
            self._gpx = Gpx(filename=self.gpx_file) #TODO change GPX code to do less....
            logging.info("GPX Distance: %s | distance (trkpts): %s | duration: %s | duration (trkpts): %s",
                         self.gpx.total_dist, self.gpx.total_dist_trkpts, self.gpx.total_time,
                         self.gpx.total_time_trkpts)
            time_diff = self.gpx.total_time_trkpts - self.gpx.total_time
            acceptable_lapse = 4 # number of seconds that duration calculated using lap and trkpts data can differ
            if time_diff > acceptable_lapse:
                self.time_pause = time_diff
                logging.debug("Identified non active time: %s s", self.time_pause)
            return self._gpx
        else:
            logging.debug("No GPX file found")
            return None
        logging.debug("<<")

    @property
    def time(self):
        warnings.warn("Deprecated property Activity.time called", DeprecationWarning, stacklevel=2)
        return self.duration

    @property
    def sport_name(self):
        warnings.warn("Deprecated property Activity.sport_name called", DeprecationWarning, stacklevel=2)
        return self.sport.name

    def _generate_per_lap_graphs(self):
        '''Build lap based graphs...'''
        logging.debug(">>")
        if self.laps is None:
            logging.debug("No laps to generate graphs from")
            logging.debug("<<")
            return
        #Lap columns
        self._lap_distance = GraphData()
        self._lap_distance.set_color('#CCFF00', '#CCFF00')
        self._lap_distance.graphType = "vspan"
        self._lap_time = GraphData()
        self._lap_time.set_color('#CCFF00', '#CCFF00')
        self._lap_time.graphType = "vspan"
        #Pace
        title = _("Pace by Lap")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Pace'), self.uc.unit_pace)
        self.distance_data['pace_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self.distance_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
        self.distance_data['pace_lap'].graphType = "bar"
        xlabel=_("Time (seconds)")
        self.time_data['pace_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self.time_data['pace_lap'].set_color('#99CCFF', '#99CCFF')
        self.time_data['pace_lap'].graphType = "bar"
        #Speed
        title = _("Speed by Lap")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Speed'), self.uc.unit_speed)
        self.distance_data['speed_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self.distance_data['speed_lap'].set_color('#336633', '#336633')
        self.distance_data['speed_lap'].graphType = "bar"
        xlabel = _("Time (seconds)")
        self.time_data['speed_lap'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self.time_data['speed_lap'].set_color('#336633', '#336633')
        self.time_data['speed_lap'].graphType = "bar"
        for lap in self.laps:
            time = float(lap['elapsed_time']) # time in sql is a unicode string
            dist = lap['distance']/1000 #distance in km
            try:
                pace = time/(60*dist) #min/km
            except ZeroDivisionError:
                pace = 0.0
            try:
                avg_speed = dist/(time/3600) # km/hr
            except:
                avg_speed = 0.0
            if self.pace_limit is not None and pace > self.pace_limit:
                logging.debug("Pace (%s) exceeds limit (%s). Setting to 0", pace, self.pace_limit)
                pace = 0.0
            logging.debug("Time: %f, Dist: %f, Pace: %f, Speed: %f", time, dist, pace, avg_speed)
            self._lap_time.addBars(x=time, y=10)
            self._lap_distance.addBars(x=self.uc.distance(dist), y=10)
            self.distance_data['pace_lap'].addBars(x=self.uc.distance(dist), y=pacekm2miles(pace))
            self.time_data['pace_lap'].addBars(x=time, y=self.uc.speed(pace))
            self.distance_data['speed_lap'].addBars(x=self.uc.distance(dist), y=self.uc.speed(avg_speed))
            self.time_data['speed_lap'].addBars(x=time, y=self.uc.speed(avg_speed))
        logging.debug("<<")

    def _init_graph_data(self):
        logging.debug(">>")
        if self.tracklist is None:
            logging.debug("No tracklist in activity")
            logging.debug("<<")
            return
        #Profile
        title = _("Elevation")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Elevation'), self.uc.unit_height)
        self._distance_data['elevation'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._distance_data['elevation'].set_color('#ff0000', '#ff0000')
        self._distance_data['elevation'].show_on_y1 = True #Make graph show elevation by default
        xlabel = _("Time (seconds)")
        self._time_data['elevation'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
        self._time_data['elevation'].set_color('#ff0000', '#ff0000')
        self._time_data['elevation'].show_on_y1 = True #Make graph show elevation by default
        #Corrected Elevation...
        title = _("Corrected Elevation")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Corrected Elevation'), self.uc.unit_height)
        self._distance_data['cor_elevation'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._distance_data['cor_elevation'].set_color('#993333', '#993333')
        xlabel=_("Time (seconds)")
        self._time_data['cor_elevation'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
        self._time_data['cor_elevation'].set_color('#993333', '#993333')
        #Speed
        title = _("Speed")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Speed'), self.uc.unit_speed)
        self._distance_data['speed'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._distance_data['speed'].set_color('#000000', '#000000')
        xlabel = _("Time (seconds)")
        self._time_data['speed'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
        self._time_data['speed'].set_color('#000000', '#000000')
        #Pace
        title = _("Pace")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Pace'), self.uc.unit_pace)
        self._distance_data['pace'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._distance_data['pace'].set_color('#0000ff', '#0000ff')
        xlabel = _("Time (seconds)")
        self._time_data['pace'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
        self._time_data['pace'].set_color('#0000ff', '#0000ff')
        #Heartrate
        title = _("Heart Rate")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
        self._distance_data['hr'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._distance_data['hr'].set_color('#00ff00', '#00ff00')
        xlabel = _("Time (seconds)")
        self._time_data['hr'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
        self._time_data['hr'].set_color('#00ff00', '#00ff00')
        #Heartrate as %
        maxhr = self.profile.getMaxHR()
        title = _("Heart Rate (% of max)")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Heart Rate'), _('%'))
        self._distance_data['hr_p'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._distance_data['hr_p'].set_color('#00ff00', '#00ff00')
        xlabel = _("Time (seconds)")
        self._time_data['hr_p'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._time_data['hr_p'].set_color('#00ff00', '#00ff00')
        #Cadence
        title = _("Cadence")
        xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
        ylabel = "%s (%s)" % (_('Cadence'), _('rpm'))
        self._distance_data['cadence'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._distance_data['cadence'].set_color('#cc00ff', '#cc00ff')
        xlabel = _("Time (seconds)")
        self._time_data['cadence'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
        self._time_data['cadence'].set_color('#cc00ff', '#cc00ff')
        for track in self.tracklist:
            try:
                pace = 60/track['velocity']
                if self.pace_limit is not None and pace > self.pace_limit:
                    logging.debug("Pace (%s) exceeds limit (%s). Setting to 0", pace, self.pace_limit)
                    pace = 0  #TODO this should be None when we move to newgraph...
            except Exception as e:
                #print type(e), e
                pace = 0
            try:
                hr_p = float(track['hr'])/maxhr*100
            except:
                hr_p = 0
            self._distance_data['elevation'].addPoints(x=self.uc.distance(track['elapsed_distance']),
                                                       y=self.uc.height(track['ele']))
            self._distance_data['cor_elevation'].addPoints(x=self.uc.distance(track['elapsed_distance']),
                                                           y=self.uc.height(track['correctedElevation']))
            self._distance_data['speed'].addPoints(x=self.uc.distance(track['elapsed_distance']),
                                                   y=self.uc.speed(track['velocity']))
            self._distance_data['pace'].addPoints(x=self.uc.distance(track['elapsed_distance']),
                                                  y=self.uc.distance(pace))
            self._distance_data['hr'].addPoints(x=self.uc.distance(track['elapsed_distance']),
                                                y=track['hr'])
            self._distance_data['hr_p'].addPoints(x=self.uc.distance(track['elapsed_distance']),
                                                  y=hr_p)
            self._distance_data['cadence'].addPoints(x=self.uc.distance(track['elapsed_distance']),
                                                     y=track['cadence'])
            self._time_data['elevation'].addPoints(x=track['time_elapsed'],
                                                   y=self.uc.height(track['ele']))
            self._time_data['cor_elevation'].addPoints(x=track['time_elapsed'],
                                                       y=self.uc.height(track['correctedElevation']))
            self._time_data['speed'].addPoints(x=track['time_elapsed'],
                                               y=self.uc.speed(track['velocity']))
            self._time_data['pace'].addPoints(x=track['time_elapsed'],
                                              y=self.uc.distance(pace))
            self._time_data['hr'].addPoints(x=track['time_elapsed'], y=track['hr'])
            self._time_data['hr_p'].addPoints(x=track['time_elapsed'], y=hr_p)
            self._time_data['cadence'].addPoints(x=track['time_elapsed'], y=track['cadence'])
        #Remove data with no values
        for item in list(self._distance_data.keys()):
            if len(self._distance_data[item]) == 0:
                logging.debug("No values for %s. Removing....", item)
                del self._distance_data[item]
        for item in list(self._time_data.keys()):
            if len(self._time_data[item]) == 0:
                logging.debug("No values for %s. Removing....", item)
                del self._time_data[item]
        logging.debug("<<")
        #Add Heartrate zones graphs
        if 'hr' in self._distance_data:
            zones = self.profile.getZones()
            title = _("Heart Rate zone")
            xlabel = "%s (%s)" % (_('Distance'), self.uc.unit_distance)
            ylabel = "%s (%s)" % (_('Heart Rate'), _('bpm'))
            self._distance_data['hr_z'] = GraphData(title=title, xlabel=xlabel, ylabel=ylabel)
            self._distance_data['hr_z'].graphType = "hspan"
            self._distance_data['hr_z'].set_color(None, None)
            xlabel = _("Time (seconds)")
            self._time_data['hr_z'] = GraphData(title=title,xlabel=xlabel, ylabel=ylabel)
            self._time_data['hr_z'].set_color(None, None)
            for zone in zones:
                self._distance_data['hr_z'].addPoints(x=zone[0], y=zone[1], label=zone[3], color=zone[2])
                self._time_data['hr_z'].addPoints(x=zone[0], y=zone[1], label=zone[3], color=zone[2])

    def _float(self, value):
        try:
            result = float(value)
        except:
            result = 0.0
        return result

    def _int(self, value):
        try:
            result = int(value)
        except:
            result = 0
        return result

    def get_value_f(self, param, format=None):
        ''' Function to return a value formated as a string
                - takes into account US/metric
                - also appends units if required
        '''
        value = self.get_value(param)
        if not value:
            #Return blank string if value is None or 0
            return ""
        if format is not None:
            result = format % value
        else:
            result = str(value)
        return result

    def get_value(self, param):
        ''' Function to get the value of various params in this activity instance
                Automatically returns values converted to imperial if needed
        '''
        if param == 'distance':
            return self.uc.distance(self.distance)
        elif param == 'average':
            return self.uc.speed(self.average)
        elif param == 'upositive':
            return self.uc.height(self.upositive)
        elif param == 'unegative':
            return self.uc.height(self.unegative)
        elif param == 'maxspeed':
            return self.uc.speed(self.maxspeed)
        elif param == 'maxpace':
            return uc.float2pace(self.uc.pace(self.maxpace))
        elif param == 'pace':
            return uc.float2pace(self.uc.pace(self.pace))
        elif param == 'calories':
            return self.calories
        elif param == 'time':
            if not self.duration:
                return ""
            _hour ,_min, _sec = second2time(self.duration)
            if _hour == 0:
                return "%02d:%02d" % (_min, _sec)
            else:
                return "%0d:%02d:%02d" % (_hour, _min, _sec)
        else:
            logging.error("Unable to provide value for unknown parameter (%s) for activity", param)
            return None