diff --git a/rowers/courses.py b/rowers/courses.py index d63ec28e..7a3066a2 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -33,14 +33,8 @@ from rowers.models import ( ) from utils import geo_distance +from rowers.courseutils import coursetime_paths, coursetime_first -# low level methods -class InvalidTrajectoryError(Exception): - def __init__(self,value): - self.value=value - - def __str__(self): - return repr(self.value) def get_course_timezone(course): @@ -65,31 +59,6 @@ def get_course_timezone(course): -def time_in_path(df,p,maxmin='max'): - - if df.empty: - return 0 - - latitude = df.latitude - longitude = df.longitude - - f = lambda x: coordinate_in_path(x['latitude'],x['longitude'],p) - - df['inpolygon'] = df.apply(f,axis=1) - - if maxmin=='max': - b = (~df['inpolygon']).shift(-1)+df['inpolygon'] - else: - b = (~df['inpolygon']).shift(1)+df['inpolygon'] - - - if len(df[b==2]): - return df[b==2]['time'].min(),df[b==2]['cum_dist'].min() - - raise InvalidTrajectoryError("Trajectory doesn't go through path") - - - return 0 def crewnerdcourse(doc): courses = [] @@ -198,63 +167,6 @@ def createcourse( return c -def coursetime_first(data,paths): - - entrytime = data['time'].max() - entrydistance = data['cum_dist'].max() - coursecompleted = False - - try: - entrytime,entrydistance = time_in_path(data,paths[0],maxmin='max') - coursecompleted = True - except InvalidTrajectoryError: - entrytime = data['time'].max() - entrydistance = data['cum_dist'].max() - coursecompleted = False - return entrytime, entrydistance, coursecompleted - -def coursetime_paths(data,paths,finalmaxmin='min'): - - entrytime = data['time'].max() - entrydistance = data['cum_dist'].max() - coursecompleted = False - - # corner case - empty list of paths - if len(paths) == 0: - return 0,True - - # end - just the Finish polygon - if len(paths) == 1: - try: - ( - entrytime, - entrydistance - ) = time_in_path(data,paths[0],maxmin=finalmaxmin) - coursecompleted = True - except InvalidTrajectoryError: - entrytime = data['time'].max() - entrydistance = data['cum_dist'].max() - coursecompleted = False - return entrytime,entrydistance,coursecompleted - - if len(paths) > 1: - try: - time,dist = time_in_path(data, paths[0]) - data = data[data['time']>time] - data['time'] = data['time']-time - data['cum_dist'] = data['cum_dist']-dist - ( - timenext, - distnext, - coursecompleted - ) = coursetime_paths(data,paths[1:]) - return time+timenext, dist+distnext,coursecompleted - except InvalidTrajectoryError: - entrytime = data['time'].max() - entrydistance = data['cum_dist'].max() - coursecompleted = False - - return entrytime, entrydistance, coursecompleted def get_time_course(ws,course): coursetimeseconds = 0.0 diff --git a/rowers/courseutils.py b/rowers/courseutils.py new file mode 100644 index 00000000..d3b8d636 --- /dev/null +++ b/rowers/courseutils.py @@ -0,0 +1,96 @@ +# low level methods +def coordinate_in_path(latitude,longitude, p): + + return p.contains_points([(latitude,longitude)])[0] + +class InvalidTrajectoryError(Exception): + def __init__(self,value): + self.value=value + + def __str__(self): + return repr(self.value) + +def time_in_path(df,p,maxmin='max'): + + if df.empty: + return 0 + + latitude = df.latitude + longitude = df.longitude + + f = lambda x: coordinate_in_path(x['latitude'],x['longitude'],p) + + df['inpolygon'] = df.apply(f,axis=1) + + if maxmin=='max': + b = (~df['inpolygon']).shift(-1)+df['inpolygon'] + else: + b = (~df['inpolygon']).shift(1)+df['inpolygon'] + + + if len(df[b==2]): + return df[b==2]['time'].min(),df[b==2]['cum_dist'].min() + + raise InvalidTrajectoryError("Trajectory doesn't go through path") + + + return 0 + + +def coursetime_first(data,paths): + + entrytime = data['time'].max() + entrydistance = data['cum_dist'].max() + coursecompleted = False + + try: + entrytime,entrydistance = time_in_path(data,paths[0],maxmin='max') + coursecompleted = True + except InvalidTrajectoryError: + entrytime = data['time'].max() + entrydistance = data['cum_dist'].max() + coursecompleted = False + return entrytime, entrydistance, coursecompleted + +def coursetime_paths(data,paths,finalmaxmin='min'): + + entrytime = data['time'].max() + entrydistance = data['cum_dist'].max() + coursecompleted = False + + # corner case - empty list of paths + if len(paths) == 0: + return 0,True + + # end - just the Finish polygon + if len(paths) == 1: + try: + ( + entrytime, + entrydistance + ) = time_in_path(data,paths[0],maxmin=finalmaxmin) + coursecompleted = True + except InvalidTrajectoryError: + entrytime = data['time'].max() + entrydistance = data['cum_dist'].max() + coursecompleted = False + return entrytime,entrydistance,coursecompleted + + if len(paths) > 1: + try: + time,dist = time_in_path(data, paths[0]) + data = data[data['time']>time] + data['time'] = data['time']-time + data['cum_dist'] = data['cum_dist']-dist + ( + timenext, + distnext, + coursecompleted + ) = coursetime_paths(data,paths[1:]) + return time+timenext, dist+distnext,coursecompleted + except InvalidTrajectoryError: + entrytime = data['time'].max() + entrydistance = data['cum_dist'].max() + coursecompleted = False + + return entrytime, entrydistance, coursecompleted diff --git a/rowers/models.py b/rowers/models.py index 23455021..374339ff 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -376,9 +376,7 @@ def polygon_to_path(polygon): return p -def coordinate_in_path(latitude,longitude, p): - - return p.contains_points([(latitude,longitude)])[0] +from rowers.courseutils import coordinate_in_path def course_spline(coordinates): latitudes = coordinates['latitude'].values diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 10a11d8f..3911458f 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -26,6 +26,8 @@ import numpy as np import dataprep import courses +from rowers.tasks import handle_check_race_course + # Low Level functions - to be called by higher level methods def add_workouts_plannedsession(ws,ps,r): result = 0 @@ -660,11 +662,11 @@ def add_workout_race(ws,race,r): dates = [w.date for w in ws] if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']: errors.append('For tests and training sessions, selected workouts must all be done on the same date') - return result,comments,errors + return result,comments,errors,0 if len(ws)>1 and race.sessiontype == 'test': errors.append('For tests, you can only attach one workout') - return result,comments,errors + return result,comments,errors,0 @@ -673,7 +675,7 @@ def add_workout_race(ws,race,r): if len(ids)>1 and race.sessiontype in ['test','coursetest','race']: errors.append('For tests, you can only attach one workout') - return result,comments,errors + return result,comments,errors,0 # start adding sessions for w in ws: @@ -682,10 +684,9 @@ def add_workout_race(ws,race,r): w.save() result += 1 - comments.append('Your result has been submitted') else: errors.append('Workout %i did not match the race window' % w.id) - return result,comments,errors + return result,comments,errors,0 if result>0: username = r.user.first_name+' '+r.user.last_name @@ -693,16 +694,6 @@ def add_workout_race(ws,race,r): age = calculate_age(r.birthdate) else: age = None - ( - coursetime, - coursemeters, - coursecompleted - ) = courses.get_time_course(ws,race.course) - if not coursecompleted: - errors.append('Your trajectory did not match the race course') - return result,comments,errors - - duration = totaltime_sec_to_string(coursetime) records = VirtualRaceResult.objects.filter( userid=r.id, @@ -713,22 +704,20 @@ def add_workout_race(ws,race,r): if ws[0].boattype != record.boattype: errors.append('Your workout boat type did not match the boat type you registered') - return result,comments,errors + return result,comments,errors,0 if ws[0].weightcategory != record.weightcategory: errors.append('Your workout weight category did not match the weight category you registered') - return result,comments, errors + return result,comments, errors,0 - record.coursecompleted=coursecompleted - record.distance = int(coursemeters) - record.workoutid=ws[0].id - record.duration = duration - record.save() + + job = myqueue(queue,handle_check_race_course,ws[0].csvfilename, + ws[0].id,race.course.id,record.id) add_workouts_plannedsession(ws,race,r) - return result,comments,errors + return result,comments,errors,job.id def delete_race_result(workout,race): results = VirtualRaceResult.objects.filter(workoutid=workout.id,race=race) diff --git a/rowers/tasks.py b/rowers/tasks.py index d9abc222..67e1d34c 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -12,6 +12,8 @@ from scipy import optimize import rowingdata from rowingdata import rowingdata as rdata +from datetime import timedelta +from sqlalchemy import create_engine from celery import app import datetime @@ -21,6 +23,7 @@ import iso8601 from matplotlib.backends.backend_agg import FigureCanvas #from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas import matplotlib.pyplot as plt +from matplotlib import path from rowsandall_app.settings import SITE_URL from rowsandall_app.settings_dev import SITE_URL as SITE_URL_DEV @@ -41,7 +44,8 @@ from rowers.dataprepnodjango import ( getsmallrowdata_db, updatecpdata_sql, update_agegroup_db,fitnessmetric_to_sql, add_c2_stroke_data_db,totaltime_sec_to_string, - create_c2_stroke_data_db,update_empower + create_c2_stroke_data_db,update_empower, + database_url_debug,database_url, ) @@ -66,6 +70,7 @@ siteurl = SITE_URL # testing task from rowers.emails import send_template_email +from rowers.courseutils import coursetime_paths, coursetime_first @app.task def add(x, y): @@ -171,6 +176,136 @@ def getagegrouprecord(age,sex='male',weightcategory='hwt', return power +def polygon_to_path(polygon,debug=True): + pid = polygon[0] + query = 'SELECT "rowers_geopoint"."id", "rowers_geopoint"."latitude", "rowers_geopoint"."longitude" FROM "rowers_geopoint" WHERE "rowers_geopoint"."polygon_id" = {pid} ORDER BY "rowers_geopoint"."order_in_poly" ASC'.format( + pid=pid + ) + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + with engine.connect() as conn, conn.begin(): + result = conn.execute(query) + points = result.fetchall() + + conn.close() + engine.dispose() + s = [] + + for point in points: + s.append([point[1],point[2]]) + + p = path.Path(s[:-1]) + + return p + +@app.task(bind=True) +def handle_check_race_course(self, + f1,workoutid,courseid, + recordid,**kwargs): + + if 'debug' in kwargs: + debug = kwargs['debug'] + else: + debug = False + + columns = ['time',' latitude',' longitude','cum_dist'] + + try: + row = rdata(csvfile=f1) + except IOError: + try: + row = rdata(f1 + '.csv') + except IOError: + try: + row = rdata(f1 + '.gz') + except IOError: + return 0 + + + rowdata = row.df + + rowdata.rename(columns = { + ' latitude':'latitude', + ' longitude':'longitude', + ' ElapsedTime (sec)': 'time', + }, inplace=True) + + + rowdata.fillna(method='backfill',inplace=True) + + rowdata['time'] = rowdata['time']-rowdata.ix[0,'time'] + # we may want to expand the time (interpolate) + rowdata['dt'] = rowdata['time'].apply( + lambda x: timedelta(seconds=x) + ) + rowdata = rowdata.resample('100ms',on='dt').mean() + rowdata = rowdata.interpolate() + + # initiate database engine + + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + # get polygons + query = 'SELECT "rowers_geopolygon"."id" FROM "rowers_geopolygon" WHERE "rowers_geopolygon"."course_id" = {courseid} ORDER BY "rowers_geopolygon"."order_in_course" ASC'.format( + courseid=courseid + ) + + with engine.connect() as conn, conn.begin(): + try: + result = conn.execute(query) + polygons = result.fetchall() + except: + print "Database locked" + conn.close() + engine.dispose() + + paths = [] + for polygon in polygons: + path = polygon_to_path(polygon,debug=debug) + paths.append(path) + + ( + coursetimeseconds, + coursemeters, + coursecompleted, + + ) = coursetime_paths(rowdata,paths) + ( + coursetimefirst, + coursemetersfirst, + firstcompleted + ) = coursetime_first( + rowdata,paths) + + coursetimeseconds = coursetimeseconds-coursetimefirst + coursemeters = coursemeters-coursemetersfirst + + if coursecompleted: + query = 'UPDATE "rowers_virtualraceresult" SET "coursecompleted" = 1, "duration" = "{duration}", "distance" = {distance}, "workoutid" = {workoutid} WHERE "id"="{recordid}"'.format( + recordid=recordid, + duration=totaltime_sec_to_string(coursetimeseconds), + distance=int(coursemeters), + workoutid=workoutid, + ) + + with engine.connect() as conn, conn.begin(): + result = conn.execute(query) + + conn.close() + engine.dispose() + + return 1 + + else: + return 2 + + return 0 + @app.task(bind=True) def handle_getagegrouprecords(self, @@ -287,8 +422,6 @@ def handle_update_empower(self, boattype = workoutdict['boattype'] f1 = workoutdict['filename'] - print wid - # oarlength consistency checks will be done in view havedata = 1 diff --git a/rowers/templates/virtualevent.html b/rowers/templates/virtualevent.html index 36d75779..0765650b 100644 --- a/rowers/templates/virtualevent.html +++ b/rowers/templates/virtualevent.html @@ -4,6 +4,10 @@ {% block title %}Rowsandall Virtual Race{% endblock %} +{% block scripts %} +{% include "monitorjobs.html" %} +{% endblock %} + {% block content %}