diff --git a/rowers/courses.py b/rowers/courses.py new file mode 100644 index 00000000..2d35363c --- /dev/null +++ b/rowers/courses.py @@ -0,0 +1,73 @@ +# All the Courses related methods + +# Python +from django.utils import timezone +from datetime import datetime +from datetime import timedelta +import time +from django.db import IntegrityError +import uuid +from django.conf import settings + +from utils import myqueue + +from matplotlib import path + +import django_rq +queue = django_rq.get_queue('default') +queuelow = django_rq.get_queue('low') +queuehigh = django_rq.get_queue('low') + +from rowers.models import ( + Rower, Workout, + GeoPoint,GeoPolygon, GeoCourse, + ) + +# low level methods +class InvalidTrajectoryError(Exception): + def __init__(self,value): + self.value=value + + def __str__(self): + return repr(self.value) + +def polygon_to_path(polygon): + points = GeoPoint.objects.filter(polygon==polygon).order_by(order_in_polygon) + s = [] + for point in points: + s.append([point.latitude,point.longitude]) + + p = path.Path(np.array(s)) + + return p + +def coordinate_in_polygon(latitude,longitude, polygon): + p = polygon_to_path(polygon) + + retun p.contains_points([(latitude,longitude)])[0] + + + +def time_in_polygon(df,polygon,maxmin='max'): + # df has timestamp, latitude, longitude + p = polygon_to_path(polygon) + + latitude = df.latitude + longitude = df.longitude + + f = lambda x: coordinate_in_polygon(x['latitude'],x['longitude'],polygon) + + df['inpolygon'] = df.apply(f,axis=1) + + mask = df['inpolygon'] == True + + if df[mask].empty(): + raise InvalidTrajectoryError + + if maxmin == 'max': + time = df[mask]['time'].max() + else: + time = df[mask]['time'].min() + + + return time diff --git a/rowers/models.py b/rowers/models.py index 35e54c19..433c4dd3 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -643,6 +643,113 @@ timezones = ( (x,x) for x in pytz.common_timezones ) +# models related to geo data (points, polygon, courses) + +class GeoCourse(models.Model): + manager = models.ForeignKey(Rower) + name = models.CharField(max_length=150,blank=True) + + +class GeoPolygon(models.Model): + course = models.ForeignKey(GeoCourse, blank=True) + order_in_course = models.IntegerField(default=0) + +# Need error checking to insert new polygons into existing course (all later polygons +# increase there order_in_course number + +class GeoPoint(models.Model): + latitude = models.FloatField(default=0) + longitude = models.FloatField(default=0) + polygon = models.ForeignKey(GeoPolygon,blank=True) + order_in_poly = models.IntegerField(default=0) + +# need error checking to "insert" new point into existing polygon? This affects order_in_poly +# of multiple GeoPoint instances + + + + +# models related to training planning - draft +# Do we need a separate class TestTarget? +class TrainingTarget(models.Model): + rower = models.ForeignKey(Rower) + name = models.CharField(max_length=150,blank=True) + date = models.DateField( + default=timezone.now()+datetime.timedelta(days=182)) + notes = models.TextField(max_length=300,blank=True) + +# SportTracks has a TrainingGoal like this +#class TrainingGoal(models.Model): +# rower = models.ForeignKey(Rower) +# name = models.CharField(max_length=150,blank=True) +# startdate = models.DateField(default=timezone.now) +# enddate = models.DateField( +# default=timezone.now()+datetime.timedelta(days=28)) +# goalmetric = models.CharField(max_length=150,default='rower', +# choices = modechoices) +# value = models.IntegerValue(default=1) + +# I think we can use PlannedSession for that (in challenge mode) +# although such a TrainingGoal could have automatically calculated +# values without needing the user to assign + +class TrainingPlan(models.Model): + rower = models.ForeignKey(Rower) + name = models.CharField(max_length=150,blank=True) + target = models.ForeignKey(TrainingTarget,blank=True) + startdate = models.DateField(default=timezone.now) + enddate = models.DateField( + default=timezone.now()+datetime.timedelta(days=182)) + +cycletypechoices = ( + ('filler','System Defined'), + ('userdefined','User Defined') + ) + +class TrainingMacroCycle(models.Model): + plan = models.ForeignKey(TrainingPlan) + name = models.CharField(max_length=150,blank=True) + startdate = models.DateField(default=timezone.now) + enddate = models.DateField( + default=timezone.now()+datetime.timedelta(days=182)) + notes = models.TextField(max_length=300,blank=True) + type = models.CharField(default='filler', + choices=cycletypechoices, + max_length=150) + +class TrainingMesoCycle(models.Model): + plan = models.ForeignKey(TrainingMacroCycle) + name = models.CharField(max_length=150,blank=True) + startdate = models.DateField(default=timezone.now) + enddate = models.DateField( + default=timezone.now()+datetime.timedelta(days=182)) + notes = models.TextField(max_length=300,blank=True) + type = models.CharField(default='filler', + choices=cycletypechoices, + max_length=150) + + +class TrainingMicroCycle(models.Model): + plan = models.ForeignKey(TrainingMesoCycle) + name = models.CharField(max_length=150,blank=True) + startdate = models.DateField(default=timezone.now) + enddate = models.DateField( + default=timezone.now()+datetime.timedelta(days=182)) + notes = models.TextField(max_length=300,blank=True) + type = models.CharField(default='filler', + choices=cycletypechoices, + max_length=150) + + +# Needs some error checking +# - Microcycles should not overlap with other microcycles, same for MesoCycles, MacroCycles +# - When a TrainingPlan is created, it should create 1 "collector" Macro, Meso & MicroCycle - this is invisible for users who choose to not use cycles +# - When a new Microcycle is inserted, the "collector" cycle is automatically adjusted to "go out of the way" of the new MicroCycle - and similar for Macro & Meso +# - If the entire MesoCycle is filled with user defined MicroCycles - there are no "filler" MicroCycles +# - Sessions are automatically linked to the correct Cycles based on their start/end date - no need for a hard link + +# Cycle error checking goes in forms + # model for Planned Session (Workout, Challenge, Test) class PlannedSession(models.Model): @@ -721,7 +828,7 @@ class PlannedSession(models.Model): max_length=150) hasranking = models.BooleanField(default=False) - + class PlannedSessionForm(ModelForm): class Meta: model = PlannedSession @@ -737,6 +844,19 @@ class PlannedSessionForm(ModelForm): timezone.now().year-1,timezone.now().year+1)), } + def __unicode__(self): + + name = self.name + startdate = self.startdate + enddate = self.enddate + + stri = u'{n} {s} - {e}'.format( + s = startdate.strftime('%Y-%m-%d'), + e = enddate.strftime('%Y-%m-%d'), + n = name, + ) + + return stri # Workout class Workout(models.Model): @@ -1438,7 +1558,7 @@ class RowerForm(ModelForm): # optionally sends a tweet to our twitter account class SiteAnnouncement(models.Model): created = models.DateField(default=timezone.now) - announcement = models.TextField(max_length=140) + announcement = models.TextField(max_length=280) expires = models.DateField(default=timezone.now) modified = models.DateField(default=timezone.now) dotweet = models.BooleanField(default=False) diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py new file mode 100644 index 00000000..68ad496c --- /dev/null +++ b/rowers/plannedsessions.py @@ -0,0 +1,39 @@ +# Python +from django.utils import timezone +from datetime import datetime +from datetime import timedelta +import time +from django.db import IntegrityError +import uuid +from django.conf import settings + +from utils import myqueue + +import django_rq +queue = django_rq.get_queue('default') +queuelow = django_rq.get_queue('low') +queuehigh = django_rq.get_queue('low') + +from rowers.models import ( + Rower, Workout, + GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle, + TrainingPlan, + ) + +# Low Level functions - to be called by higher level methods + +# dummies for now +def submit_workout(w,ps): + return 1 + +def remove_workout_plannedsession(w,ps): + return 1 + +def rank_results(ps): + return 1 + +def add_team(t,ps): + return 1 + +def add_rower(r,ps): + return 1