From 3bec1d4289267d095d7a3babeb999a1f72dbf3bd Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 8 Oct 2021 09:17:20 +0200 Subject: [PATCH 1/6] non functional mockup stage --- rowers/courses.py | 68 ++++++++++++++++++++++++++++++ rowers/dataprep.py | 14 ++++++ rowers/forms.py | 7 +-- rowers/models.py | 13 +++++- rowers/templates/summary_edit.html | 19 ++++++++- rowers/views/racesviews.py | 67 +---------------------------- rowers/views/workoutviews.py | 11 ++++- 7 files changed, 126 insertions(+), 73 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index eb8967be..59048cd1 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -22,6 +22,74 @@ import xml.etree.ElementTree as et from xml.etree.ElementTree import Element, SubElement, Comment, tostring from xml.dom import minidom +# distance of course from lat_lon in km +def howfaris(lat_lon,course): + coords = course.coord + distance = geo_distance(lat_lon[0],lat_lon[1],coords[0],coords[1])[0] + + return distance + +#whatisnear = 150 + +# get nearest races +def getnearestraces(lat_lon,races,whatisnear=150): + newlist = [] + counter = 0 + for race in races: + if race.course is None: # pragma: no cover + newlist.append(race) + else: + c = race.course + coords = c.coord + distance = howfaris(lat_lon,c) + if distance < whatisnear: + newlist.append(race) + counter += 1 + + if counter>0: + races = newlist + else: + courseraces = races.exclude(course__isnull=True) + orders = [(c.id,howfaris(lat_lon,c.course)) for c in courseraces] + orders = sorted(orders,key = lambda tup:tup[1]) + ids = [id for id,distance in orders[0:4]] + for id, distance in orders[5:]: # pragma: no cover + if distance0: + courses = newlist + else: + orders = [(c.id,howfaris(lat_lon,c)) for c in courses] + orders = sorted(orders,key = lambda tup:tup[1]) + ids = [id for id,distance in orders[0:4]] + for id, distance in orders[5:]: + if distance 0 and longitude.std() > 0: + return True, latmean,lonmean + + return False, latmean,lonmean + + def workout_summary_to_df( rower, startdate=datetime.datetime(1970,1,1), diff --git a/rowers/forms.py b/rowers/forms.py index 7e18b393..6f27093e 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1128,27 +1128,28 @@ class PlanSelectForm(forms.Form): "price","shortname" ) - class CourseSelectForm(forms.Form): course = forms.ModelChoiceField(queryset=GeoCourse.objects.filter()) def __init__(self, *args, **kwargs): # pragma: no cover course = kwargs.pop('course',None) manager = kwargs.pop('manager',None) + choices = kwargs.pop('choices',[]) super(CourseSelectForm,self).__init__(*args,**kwargs) + if len(choices)>0: + self.fields['course'].queryset = GeoCourse.objects.filter(id__in=[c.id for c in choices]) if course is not None: d_min = 0.5*course.distance d_max = 2*course.distance country = course.country countries = ['unknown',country] - print(countries) + self.fields['course'].queryset = self.fields['course'].queryset.filter( distance__gt = d_min,distance__lt = d_max, country__in = countries ).exclude(id=course.id) if manager is not None: self.fields['course'].queryset = self.fields['course'].queryset.filter(manager=manager) - print(self.fields['course'].queryset) class WorkoutSingleSelectForm(forms.Form): workout = forms.ModelChoiceField( diff --git a/rowers/models.py b/rowers/models.py index 82349291..b7b2e6f2 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -3408,7 +3408,8 @@ class VirtualRaceResult(models.Model): verbose_name="Adaptive Class") skillclass = models.CharField(default="Open",max_length=50, verbose_name="Skill Class") - race = models.ForeignKey(VirtualRace,on_delete=models.CASCADE,related_name='entries') + race = models.ForeignKey(VirtualRace,on_delete=models.CASCADE,related_name='entries', + blank=True,null=True) duration = models.TimeField(default=datetime.time(1,0)) distance = models.IntegerField(default=0) points = models.FloatField(default=0) @@ -3448,6 +3449,10 @@ class VirtualRaceResult(models.Model): return False if self.skillclass != other.skillclass: return False + if self.race is None and other.race is not None: + return False + if self.rae is not None and other.race is None: + return False if self.race != other.race: return False if self.boatclass != other.boatclass: @@ -3522,7 +3527,7 @@ class IndoorVirtualRaceResult(models.Model): verbose_name="Adaptive Class") skillclass = models.CharField(default="Open",max_length=50, verbose_name="Skill Class") - race = models.ForeignKey(VirtualRace,on_delete=models.CASCADE) + race = models.ForeignKey(VirtualRace,on_delete=models.CASCADE,null=True,blank=True) duration = models.TimeField(default=datetime.time(1,0)) distance = models.IntegerField(default=0) referencespeed = models.FloatField(default=5.0) @@ -3554,6 +3559,10 @@ class IndoorVirtualRaceResult(models.Model): endsecond = models.FloatField(default=0) def isduplicate(self,other): # pragma: no cover + if self.race is None and other.race is not None: + return False + if self.race is not None and other.race is None: + return False if self.userid != other.userid: return False if self.weightcategory != other.weightcategory: diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index ee84aaa6..5b9a473a 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -158,7 +158,7 @@

A typical interval is described as "10min/5min", with the work part before the "/" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "1km" or "1000m". The basic units can be combined with "+" and "Nx". You can use parentheses as in the example below.

-

Here are a few examples.

+

Here are a few examples

@@ -182,6 +182,23 @@
8x500m/2min8 times 500m with 2 minutes rest
+ {% if courses %} +
  • +

    Interval by Course

    +

    + This functionality allows you to record a time on a set course that you've rowed during the workout. + The summary will be updated to show time on course, and you can compare this with other + attempts. +

    +
    + + {{ courseselectform.as_table }} +
    + {% csrf_token %} + +
    +
  • + {% endif %} {% endblock %} diff --git a/rowers/views/racesviews.py b/rowers/views/racesviews.py index 7d73abca..48a1cc55 100644 --- a/rowers/views/racesviews.py +++ b/rowers/views/racesviews.py @@ -11,72 +11,7 @@ from django.contrib.gis.geoip2 import GeoIP2 from django import forms from rowers.plannedsessions import timefield_to_seconds_duration -# distance of course from lat_lon in km -def howfaris(lat_lon,course): - coords = course.coord - distance = geo_distance(lat_lon[0],lat_lon[1],coords[0],coords[1])[0] - - return distance - -whatisnear = 150 - -# get nearest races -def getnearestraces(lat_lon,races): - newlist = [] - counter = 0 - for race in races: - if race.course is None: # pragma: no cover - newlist.append(race) - else: - c = race.course - coords = c.coord - distance = howfaris(lat_lon,c) - if distance < whatisnear: - newlist.append(race) - counter += 1 - - if counter>0: - races = newlist - else: - courseraces = races.exclude(course__isnull=True) - orders = [(c.id,howfaris(lat_lon,c.course)) for c in courseraces] - orders = sorted(orders,key = lambda tup:tup[1]) - ids = [id for id,distance in orders[0:4]] - for id, distance in orders[5:]: # pragma: no cover - if distance0: - courses = newlist - else: - orders = [(c.id,howfaris(lat_lon,c)) for c in courses] - orders = sorted(orders,key = lambda tup:tup[1]) - ids = [id for id,distance in orders[0:4]] - for id, distance in orders[5:]: - if distance Date: Fri, 8 Oct 2021 10:45:59 +0200 Subject: [PATCH 2/6] Now writing results --- rowers/models.py | 2 ++ rowers/tasks.py | 32 +++++++++++++++++++++++++++ rowers/views/workoutviews.py | 42 ++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/rowers/models.py b/rowers/models.py index b7b2e6f2..53b77559 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -3410,6 +3410,8 @@ class VirtualRaceResult(models.Model): verbose_name="Skill Class") race = models.ForeignKey(VirtualRace,on_delete=models.CASCADE,related_name='entries', blank=True,null=True) + course = models.ForeignKey(GeoCourse,on_delete=models.CASCADE,null=True,blank=True) + duration = models.TimeField(default=datetime.time(1,0)) distance = models.IntegerField(default=0) points = models.FloatField(default=0) diff --git a/rowers/tasks.py b/rowers/tasks.py index ed400fb2..7c6bea03 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -540,6 +540,10 @@ def handle_check_race_course(self, if 'mode' in kwargs: # pragma: no cover mode = kwargs['mode'] + summary = False + if 'summary' in kwargs: + summary = kwargs['summary'] + columns = ['time',' latitude',' longitude','cum_dist'] try: @@ -599,6 +603,7 @@ def handle_check_race_course(self, courseid=courseid ) + with engine.connect() as conn, conn.begin(): result = conn.execute(query) polygons = result.fetchall() @@ -736,6 +741,33 @@ def handle_check_race_course(self, with engine.connect() as conn, conn.begin(): result = conn.execute(query) + if summary: + + try: + row = rdata(csvfile=f1) + except IOError: # pragma: no cover + try: + row = rdata(csvfile=f1 + '.csv') + except IOError: # pragma: no cover + try: + row = rdata(csvfile=f1 + '.gz') + except IOError: # pragma: no cover + pass + + vals, units, typ = row.updateinterval_metric( + ' AverageBoatSpeed (m/s)',0.1,mode='larger', + debug=False,smoothwindow=15., + activewindow=[startsecond,endsecond] + ) + + summary = row.allstats() + row.write_csv(f1,gzip=True) + + query = "UPDATE `rowers_workout` SET `summary` = '%s' WHERE `id` = %s" % (summary, workoutid) + + with engine.connect() as conn, conn.begin(): + result = conn.execute(query) + conn.close() engine.dispose() diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index b88034f0..6aecc128 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -6168,6 +6168,48 @@ def workout_summary_edit_view(request,id,message="",successmessage="" powerupdateform = PowerIntervalUpdateForm(initial=data) + if request.method == 'POST' and "course" in request.POST: + courseselectform = CourseSelectForm(request.POST,choices=courses) + if courseselectform.is_valid(): + course = courseselectform.cleaned_data['course'] + # get or create a record + records = VirtualRaceResult.objects.filter( + userid=r.id, + course=course, + workoutid=row.id + ) + if records: + record = records[0] + else: + # create record + record = VirtualRaceResult( + userid = r.id, + username = r.user.first_name+' '+r.user.last_name, + workoutid = row.id, + weightcategory = r.weightcategory, + adaptiveclass = r.adaptiveclass, + course = course, + distance = course.distance, + boatclass = row.workouttype, + boattype = row.boattype, + sex = r.sex, + age = calculate_age(r.birthdate), + ) + record.save() + + job = myqueue( + queue, + handle_check_race_course, + row.csvfilename, + row.id, + course.id, + record.id, + r.user.email, + r.user.first_name, + summary=True, + ) + messages.info(request,'We are checking your time on the course in the background') + # feeling lucky / ruptures if request.method == 'POST' and "ruptures" in request.POST: df = pd.DataFrame({ From e604350e238a02b1671d27e7d5a6ee0c4519d26c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 8 Oct 2021 16:24:58 +0200 Subject: [PATCH 3/6] working MPV --- rowers/courses.py | 6 +- rowers/forms.py | 36 ++-- rowers/interactiveplots.py | 269 ++++++++++++++++++++++++---- rowers/models.py | 4 + rowers/templates/course_view.html | 65 +++++++ rowers/templates/list_courses.html | 4 - rowers/templatetags/rowerfilters.py | 8 + rowers/views/racesviews.py | 57 ++++++ rowers/views/workoutviews.py | 6 +- 9 files changed, 401 insertions(+), 54 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index 59048cd1..abc44597 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -63,18 +63,22 @@ def getnearestraces(lat_lon,races,whatisnear=150): return races -def getnearestcourses(lat_lon,courses,whatisnear=150): +def getnearestcourses(lat_lon,courses,whatisnear=150,strict=False): + print(lat_lon,whatisnear) newlist = [] counter = 0 for c in courses: coords = c.coord distance = howfaris(lat_lon,c) + if distance < whatisnear: newlist.append(c) counter += 1 if counter>0: courses = newlist + elif strict: + courses = newlist else: orders = [(c.id,howfaris(lat_lon,c)) for c in courses] orders = sorted(orders,key = lambda tup:tup[1]) diff --git a/rowers/forms.py b/rowers/forms.py index 6f27093e..db7daea4 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1472,30 +1472,36 @@ class RaceResultFilterForm(forms.Form): entrycategory = forms.MultipleChoiceField( choices = [], label = 'Groups', - widget=forms.CheckboxSelectMultiple() + widget=forms.CheckboxSelectMultiple(), + required=False, ) def __init__(self, *args, **kwargs): - if 'records' in kwargs: - records = kwargs.pop('records',None) + records = kwargs.pop('records',None) + groups = kwargs.pop('groups',None) + + super(RaceResultFilterForm,self).__init__(*args,**kwargs) if records: # group - thecategories = [record.entrycategory for record in records] - thecategories = list(set(thecategories)) - if len(thecategories) <= 1: - del self.fields['entrycategory'] + if groups: + thecategories = [record.entrycategory for record in records] + thecategories = list(set(thecategories)) + if len(thecategories) <= 1: + del self.fields['entrycategory'] + else: + categorychoices = [] + for category in thecategories: + if category is not None: + categorychoices.append( + (category.id,category) + ) + self.fields['entrycategory'].choices = categorychoices + self.fields['entrycategory'].initial = [cat[0] for cat in categorychoices] else: - categorychoices = [] - for category in thecategories: - if category is not None: - categorychoices.append( - (category.id,category) - ) - self.fields['entrycategory'].choices = categorychoices - self.fields['entrycategory'].initial = [cat[0] for cat in categorychoices] + del self.fields['entrycategory'] # sex thesexes = [record.sex for record in records] diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 09bac286..f811b63d 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -66,7 +66,7 @@ from rowers.courses import ( ) from rowers import mytypes -from rowers.models import course_spline +from rowers.models import course_spline,VirtualRaceResult import datetime import math @@ -2419,47 +2419,185 @@ def course_map(course): return script,div -def leaflet_chart(lat,lon,name=""): - if lat.empty or lon.empty: # pragma: no cover - return [0,"invalid coordinate data"] +def get_map_script_course( + latmean, + lonmean, + latbegin, + latend, + longbegin, + longend, + scoordinates, + course, + ): + latmean,lonmean,coordinates = course_coord_center(course) + lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - # Throw out 0,0 - df = pd.DataFrame({ - 'lat':lat, - 'lon':lon - }) - - df = df.replace(0,np.nan) - df = df.loc[(df!=0).any(axis=1)] - df.fillna(method='bfill',axis=0,inplace=True) - df.fillna(method='ffill',axis=0,inplace=True) - lat = df['lat'] - lon = df['lon'] - if lat.empty or lon.empty: # pragma: no cover - return [0,"invalid coordinate data"] - - latmean = lat.mean() - lonmean = lon.mean() - - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - latend = lat[lat.index[-1]] - longend = lon[lon.index[-1]] - - coordinates = zip(lat,lon) + coordinates = course_spline(coordinates) scoordinates = "[" - for x,y in coordinates: + for index,row in coordinates.iterrows(): scoordinates += """[{x},{y}], """.format( - x=x, - y=y + x=row['latitude'], + y=row['longitude'] + ) + + scoordinates +="]" + + polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course") + + plabels = '' + + for p in polygons: + coords = polygon_coord_center(p) + + plabels += """ + var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); + marker.bindPopup("{name}"); + + """.format( + latbegin = coords[0], + longbegin = coords[1], + name = p.name ) - scoordinates += "]" + pcoordinates = """[ + """ + for p in polygons: + pcoordinates += """[ + [""" + + points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") + + for pt in points: + pcoordinates += "[{x},{y}],".format( + x = pt.latitude, + y = pt.longitude + ) + + # remove last comma + pcoordinates = pcoordinates[:-1] + pcoordinates += """] + ], + """ + + pcoordinates += """ + ]""" + + script = """ + + """.format( + latmean=latmean, + lonmean=lonmean, + latbegin = latbegin, + latend=latend, + longbegin=longbegin, + longend=longend, + scoordinates=scoordinates, + pcoordinates=pcoordinates, + plabels=plabels + ) + + return script + + +def get_map_script( + latmean, + lonmean, + latbegin, + latend, + longbegin, + longend, + scoordinates, + ): script = """