Merge branch 'release/v5.94'
This commit is contained in:
+99
-17
@@ -14,6 +14,9 @@ from matplotlib import path
|
|||||||
import xml.etree.ElementTree as et
|
import xml.etree.ElementTree as et
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import dataprep
|
||||||
|
|
||||||
ns = {'opengis': 'http://www.opengis.net/kml/2.2'}
|
ns = {'opengis': 'http://www.opengis.net/kml/2.2'}
|
||||||
|
|
||||||
@@ -83,45 +86,45 @@ def course_coord_maxmin(course):
|
|||||||
return lat_min,lat_max,long_min,long_max
|
return lat_min,lat_max,long_min,long_max
|
||||||
|
|
||||||
def polygon_to_path(polygon):
|
def polygon_to_path(polygon):
|
||||||
points = GeoPoint.objects.filter(polygon==polygon).order_by("order_in_polygon")
|
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
|
||||||
s = []
|
s = []
|
||||||
for point in points:
|
for point in points:
|
||||||
s.append([point.latitude,point.longitude])
|
s.append([point.latitude,point.longitude])
|
||||||
|
|
||||||
p = path.Path(np.array(s))
|
p = path.Path(s[:-1])
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def coordinate_in_polygon(latitude,longitude, polygon):
|
def coordinate_in_path(latitude,longitude, p):
|
||||||
p = polygon_to_path(polygon)
|
|
||||||
|
|
||||||
return p.contains_points([(latitude,longitude)])[0]
|
return p.contains_points([(latitude,longitude)])[0]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def time_in_polygon(df,polygon,maxmin='max'):
|
def time_in_path(df,p,maxmin='max'):
|
||||||
# df has timestamp, latitude, longitude
|
|
||||||
p = polygon_to_path(polygon)
|
if df.empty:
|
||||||
|
return 0
|
||||||
|
|
||||||
latitude = df.latitude
|
latitude = df.latitude
|
||||||
longitude = df.longitude
|
longitude = df.longitude
|
||||||
|
|
||||||
f = lambda x: coordinate_in_polygon(x['latitude'],x['longitude'],polygon)
|
f = lambda x: coordinate_in_path(x['latitude'],x['longitude'],p)
|
||||||
|
|
||||||
df['inpolygon'] = df.apply(f,axis=1)
|
df['inpolygon'] = df.apply(f,axis=1)
|
||||||
|
|
||||||
mask = df['inpolygon'] == True
|
if maxmin=='max':
|
||||||
|
b = (~df['inpolygon']).shift(-1)+df['inpolygon']
|
||||||
if df[mask].empty():
|
|
||||||
raise InvalidTrajectoryError
|
|
||||||
|
|
||||||
if maxmin == 'max':
|
|
||||||
time = df[mask]['time'].max()
|
|
||||||
else:
|
else:
|
||||||
time = df[mask]['time'].min()
|
b = (~df['inpolygon']).shift(1)+df['inpolygon']
|
||||||
|
|
||||||
|
if len(df[b==2]):
|
||||||
|
return df[b==2]['time'].min()
|
||||||
|
|
||||||
|
raise InvalidTrajectoryError("Trajectory doesn't go through path")
|
||||||
|
|
||||||
|
|
||||||
return time
|
return 0
|
||||||
|
|
||||||
def crewnerdcourse(doc):
|
def crewnerdcourse(doc):
|
||||||
courses = []
|
courses = []
|
||||||
@@ -200,8 +203,16 @@ def createcourse(
|
|||||||
j = 0
|
j = 0
|
||||||
for point in p['points']:
|
for point in p['points']:
|
||||||
if i==0 and j==0:
|
if i==0 and j==0:
|
||||||
|
try:
|
||||||
loc = geolocator.reverse((point['latitude'],point['longitude']))
|
loc = geolocator.reverse((point['latitude'],point['longitude']))
|
||||||
country = loc.raw['address']['country']
|
country = loc.raw['address']['country']
|
||||||
|
if isinstance(country,unicode):
|
||||||
|
country = country.encode('utf8')
|
||||||
|
elif isinstance(country, str):
|
||||||
|
country = country.decode('utf8')
|
||||||
|
except GeocoderInsufficientPrivileges:
|
||||||
|
country = "Unknown Country"
|
||||||
|
|
||||||
c.country = country
|
c.country = country
|
||||||
c.save()
|
c.save()
|
||||||
obj = GeoPoint(
|
obj = GeoPoint(
|
||||||
@@ -215,3 +226,74 @@ def createcourse(
|
|||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
def coursetime_paths(data,paths):
|
||||||
|
|
||||||
|
entrytime = data['time'].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 = time_in_path(data,paths[0],maxmin='min')
|
||||||
|
coursecompleted = True
|
||||||
|
print entrytime
|
||||||
|
except InvalidTrajectoryError:
|
||||||
|
entrytime = data['time'].max()
|
||||||
|
coursecompleted = False
|
||||||
|
return entrytime,coursecompleted
|
||||||
|
|
||||||
|
if len(paths) > 1:
|
||||||
|
try:
|
||||||
|
time = time_in_path(data, paths[0])
|
||||||
|
data = data[data['time']>time]
|
||||||
|
data['time'] = data['time']-time
|
||||||
|
print time
|
||||||
|
timenext, coursecompleted = coursetime_paths(data,paths[1:])
|
||||||
|
return time+timenext, coursecompleted
|
||||||
|
except InvalidTrajectoryError:
|
||||||
|
entrytime = data['time'].max()
|
||||||
|
coursecompleted = False
|
||||||
|
|
||||||
|
return entrytime, coursecompleted
|
||||||
|
|
||||||
|
def get_time_course(ws,course):
|
||||||
|
coursetimeseconds = 0.0
|
||||||
|
coursecompleted = 0
|
||||||
|
|
||||||
|
w = ws[0]
|
||||||
|
columns = ['time',' latitude',' longitude']
|
||||||
|
rowdata = dataprep.getsmallrowdata_db(
|
||||||
|
columns,
|
||||||
|
ids = [w.id],
|
||||||
|
doclean=True,
|
||||||
|
workstrokesonly=False
|
||||||
|
)
|
||||||
|
rowdata.rename(columns = {
|
||||||
|
' latitude':'latitude',
|
||||||
|
' longitude':'longitude',
|
||||||
|
}, inplace=True)
|
||||||
|
|
||||||
|
|
||||||
|
rowdata['time'] = rowdata['time']/1000.
|
||||||
|
|
||||||
|
rowdata.fillna(method='backfill',inplace=True)
|
||||||
|
|
||||||
|
rowdata['time'] = rowdata['time']-rowdata.ix[0,'time']
|
||||||
|
# we may want to expand the time (interpolate)
|
||||||
|
|
||||||
|
polygons = GeoPolygon.objects.filter(course=course)
|
||||||
|
paths = []
|
||||||
|
for polygon in polygons:
|
||||||
|
path = polygon_to_path(polygon)
|
||||||
|
paths.append(path)
|
||||||
|
|
||||||
|
coursetimeseconds,coursecompleted = coursetime_paths(rowdata,paths)
|
||||||
|
|
||||||
|
print 'course time?',coursetimeseconds
|
||||||
|
|
||||||
|
return coursetimeseconds,coursecompleted
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from rowers.models import (
|
|||||||
import metrics
|
import metrics
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import dataprep
|
import dataprep
|
||||||
|
import courses
|
||||||
|
|
||||||
# Low Level functions - to be called by higher level methods
|
# Low Level functions - to be called by higher level methods
|
||||||
def add_workouts_plannedsession(ws,ps,r):
|
def add_workouts_plannedsession(ws,ps,r):
|
||||||
@@ -47,7 +48,7 @@ def add_workouts_plannedsession(ws,ps,r):
|
|||||||
ids = [w.id for w in wold] + [w.id for w in ws]
|
ids = [w.id for w in wold] + [w.id for w in ws]
|
||||||
ids = list(set(ids))
|
ids = list(set(ids))
|
||||||
|
|
||||||
if len(ids)>1 and ps.sessiontype == 'test':
|
if len(ids)>1 and ps.sessiontype in ['test','coursetest']:
|
||||||
errors.append('For tests, you can only attach one workout')
|
errors.append('For tests, you can only attach one workout')
|
||||||
return result,comments,errors
|
return result,comments,errors
|
||||||
|
|
||||||
@@ -234,6 +235,34 @@ def is_session_complete_ws(ws,ps):
|
|||||||
if not completiondate:
|
if not completiondate:
|
||||||
completiondate = ws.reverse()[0].date
|
completiondate = ws.reverse()[0].date
|
||||||
return ratio,'partial',completiondate
|
return ratio,'partial',completiondate
|
||||||
|
elif ps.sessiontype == 'coursetest':
|
||||||
|
if ps.course:
|
||||||
|
coursetime,coursecompleted = courses.get_time_course(ws,ps.course)
|
||||||
|
if coursecompleted:
|
||||||
|
return 1.0,'completed',completiondate
|
||||||
|
else:
|
||||||
|
return ratio,'partial',completiondate
|
||||||
|
else:
|
||||||
|
if ps.criterium == 'exact':
|
||||||
|
if ratio == 1.0:
|
||||||
|
return ratio,'completed',completiondate
|
||||||
|
else:
|
||||||
|
if not completiondate:
|
||||||
|
completiondate = ws.reverse()[0].date
|
||||||
|
return ratio,'partial',completiondate
|
||||||
|
elif ps.criterium == 'minimum':
|
||||||
|
if ratio >= 1.0:
|
||||||
|
return ratio,'completed',completiondate
|
||||||
|
else:
|
||||||
|
if not completiondate:
|
||||||
|
completiondate = ws.reverse()[0].date
|
||||||
|
|
||||||
|
return ratio,'partial',completiondate
|
||||||
|
else:
|
||||||
|
if ratio>cratiomin and ratio<cratiomax:
|
||||||
|
return ratio,'completed',completiondate
|
||||||
|
else:
|
||||||
|
return ratio,'partial',completiondate
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not completiondate:
|
if not completiondate:
|
||||||
|
|||||||
+11
-3
@@ -289,6 +289,10 @@ def handle_sendemail_breakthrough(workoutid, useremail,
|
|||||||
btvalues=pd.DataFrame().to_json(),
|
btvalues=pd.DataFrame().to_json(),
|
||||||
**kwargs):
|
**kwargs):
|
||||||
|
|
||||||
|
siteurl = SITE_URL
|
||||||
|
if debug:
|
||||||
|
siteurl = SITE_URL_DEV
|
||||||
|
|
||||||
# send email with attachment
|
# send email with attachment
|
||||||
subject = "A breakthrough workout on rowsandall.com"
|
subject = "A breakthrough workout on rowsandall.com"
|
||||||
message = "Dear " + userfirstname + ",\n"
|
message = "Dear " + userfirstname + ",\n"
|
||||||
@@ -299,11 +303,11 @@ def handle_sendemail_breakthrough(workoutid, useremail,
|
|||||||
message += "sustain for a given duration. For more, see this "
|
message += "sustain for a given duration. For more, see this "
|
||||||
message += " article in the analytics blog:\n\n"
|
message += " article in the analytics blog:\n\n"
|
||||||
message += " http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/ \n\n"
|
message += " http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/ \n\n"
|
||||||
message += "Link to the workout http://rowsandall.com/rowers/workout/"
|
message += "Link to the workout "+siteurl+"/rowers/workout/"
|
||||||
message += str(workoutid)
|
message += str(workoutid)
|
||||||
message += "/edit\n\n"
|
message += "/edit\n\n"
|
||||||
message += "To add the workout to your Ranking workouts and see the updated CP plot, click the following link:\n"
|
message += "To add the workout to your Ranking workouts and see the updated CP plot, click the following link:\n"
|
||||||
message += "http://rowsandall.com/rowers/workout/"
|
message += siteurl+"/rowers/workout/"
|
||||||
message += str(workoutid)
|
message += str(workoutid)
|
||||||
message += "/updatecp\n\n"
|
message += "/updatecp\n\n"
|
||||||
|
|
||||||
@@ -349,6 +353,10 @@ def handle_sendemail_hard(workoutid, useremail,
|
|||||||
btvalues=pd.DataFrame().to_json(),
|
btvalues=pd.DataFrame().to_json(),
|
||||||
debug=False,**kwargs):
|
debug=False,**kwargs):
|
||||||
|
|
||||||
|
siteurl = SITE_URL
|
||||||
|
if debug:
|
||||||
|
siteurl = SITE_URL_DEV
|
||||||
|
|
||||||
# send email with attachment
|
# send email with attachment
|
||||||
subject = "That was a pretty hard workout on rowsandall.com"
|
subject = "That was a pretty hard workout on rowsandall.com"
|
||||||
message = "Dear " + userfirstname + ",\n"
|
message = "Dear " + userfirstname + ",\n"
|
||||||
@@ -359,7 +367,7 @@ def handle_sendemail_hard(workoutid, useremail,
|
|||||||
message += "sustain for a given duration. For more, see this "
|
message += "sustain for a given duration. For more, see this "
|
||||||
message += " article in the analytics blog:\n\n"
|
message += " article in the analytics blog:\n\n"
|
||||||
message += " http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/ \n\n"
|
message += " http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/ \n\n"
|
||||||
message += "Link to the workout http://rowsandall.com/rowers/workout/"
|
message += "Link to the workout: "+siteurl+"/rowers/workout/"
|
||||||
message += str(workoutid)
|
message += str(workoutid)
|
||||||
message += "/edit\n\n"
|
message += "/edit\n\n"
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div id="right" class="grid_6 omega">
|
<div id="right" class="grid_6 omega">
|
||||||
{% if plannedsession.sessiontype == 'test' %}
|
{% if plannedsession.sessiontype == 'test' or plannedsession.sessiontype == 'coursetest' %}
|
||||||
<h1>Ranking</h1>
|
<h1>Ranking</h1>
|
||||||
<table class="listtable shortpadded" width="80%">
|
<table class="listtable shortpadded" width="80%">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
+5
-1
@@ -8463,6 +8463,10 @@ def course_edit_view(request,id=0):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
name = form.cleaned_data['name']
|
name = form.cleaned_data['name']
|
||||||
notes = form.cleaned_data['notes']
|
notes = form.cleaned_data['notes']
|
||||||
|
if isinstance(name,unicode):
|
||||||
|
name = name.encode('utf8')
|
||||||
|
elif isinstance(name, str):
|
||||||
|
name = name.decode('utf8')
|
||||||
|
|
||||||
course.name = name
|
course.name = name
|
||||||
course.notes = notes
|
course.notes = notes
|
||||||
@@ -12645,7 +12649,7 @@ def plannedsession_view(request,id=0,rowerid=0,
|
|||||||
# ranking for test
|
# ranking for test
|
||||||
ranking = []
|
ranking = []
|
||||||
|
|
||||||
if ps.sessiontype == 'test':
|
if ps.sessiontype in ['test','coursetest']:
|
||||||
if ps.sessionmode == 'distance':
|
if ps.sessionmode == 'distance':
|
||||||
rankws = Workout.objects.filter(
|
rankws = Workout.objects.filter(
|
||||||
plannedsession=ps).order_by("duration")
|
plannedsession=ps).order_by("duration")
|
||||||
|
|||||||
Reference in New Issue
Block a user