Merge branch 'release/v6.64'
This commit is contained in:
@@ -28,9 +28,12 @@ from rowers.models import (
|
||||
Rower, Workout,
|
||||
GeoPoint,GeoPolygon, GeoCourse,
|
||||
course_length,course_coord_center,course_coord_maxmin,
|
||||
polygon_coord_center,PlannedSession
|
||||
polygon_coord_center,PlannedSession,
|
||||
polygon_to_path,coordinate_in_path
|
||||
)
|
||||
|
||||
from utils import geo_distance
|
||||
|
||||
# low level methods
|
||||
class InvalidTrajectoryError(Exception):
|
||||
def __init__(self,value):
|
||||
@@ -39,6 +42,7 @@ class InvalidTrajectoryError(Exception):
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
def get_course_timezone(course):
|
||||
polygons = GeoPolygon.objects.filter(course = course)
|
||||
points = GeoPoint.objects.filter(polygon = polygons[0])
|
||||
@@ -58,19 +62,6 @@ def get_course_timezone(course):
|
||||
|
||||
return timezone_str
|
||||
|
||||
def polygon_to_path(polygon):
|
||||
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
|
||||
s = []
|
||||
for point in points:
|
||||
s.append([point.latitude,point.longitude])
|
||||
|
||||
p = path.Path(s[:-1])
|
||||
|
||||
return p
|
||||
|
||||
def coordinate_in_path(latitude,longitude, p):
|
||||
|
||||
return p.contains_points([(latitude,longitude)])[0]
|
||||
|
||||
|
||||
|
||||
@@ -293,7 +284,7 @@ def get_time_course(ws,course):
|
||||
rowdata = rowdata.resample('100ms',on='dt').mean()
|
||||
rowdata = rowdata.interpolate()
|
||||
|
||||
polygons = GeoPolygon.objects.filter(course=course)
|
||||
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
|
||||
paths = []
|
||||
for polygon in polygons:
|
||||
path = polygon_to_path(polygon)
|
||||
|
||||
@@ -43,6 +43,8 @@ from courses import (
|
||||
polygon_coord_center
|
||||
)
|
||||
|
||||
from rowers.models import course_spline
|
||||
|
||||
import datetime
|
||||
import math
|
||||
import numpy as np
|
||||
@@ -853,6 +855,8 @@ def course_map(course):
|
||||
latmean,lonmean,coordinates = course_coord_center(course)
|
||||
lat_min, lat_max, long_min, long_max = course_coord_maxmin(course)
|
||||
|
||||
coordinates = course_spline(coordinates)
|
||||
|
||||
scoordinates = "["
|
||||
|
||||
for index,row in coordinates.iterrows():
|
||||
@@ -2208,7 +2212,6 @@ def interactive_chart(id=0,promember=0):
|
||||
|
||||
script, div = components(plot)
|
||||
|
||||
|
||||
return [script,div]
|
||||
|
||||
def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
|
||||
|
||||
225
rowers/models.py
225
rowers/models.py
@@ -18,6 +18,9 @@ import twitter
|
||||
import re
|
||||
import pytz
|
||||
|
||||
from scipy.interpolate import splprep, splev, CubicSpline
|
||||
import numpy as np
|
||||
|
||||
from django.conf import settings
|
||||
from sqlalchemy import create_engine
|
||||
import sqlalchemy as sa
|
||||
@@ -29,9 +32,10 @@ import datetime
|
||||
from django.core.exceptions import ValidationError
|
||||
from rowers.rows import validate_file_extension
|
||||
from collections import OrderedDict
|
||||
from timezonefinder import TimezoneFinder
|
||||
|
||||
import types
|
||||
|
||||
from matplotlib import path
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
TWEET_ACCESS_TOKEN_KEY,
|
||||
@@ -362,7 +366,46 @@ def polygon_coord_center(polygon):
|
||||
|
||||
|
||||
|
||||
def polygon_to_path(polygon):
|
||||
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
|
||||
s = []
|
||||
for point in points:
|
||||
s.append([point.latitude,point.longitude])
|
||||
|
||||
p = path.Path(s[:-1])
|
||||
|
||||
return p
|
||||
|
||||
def coordinate_in_path(latitude,longitude, p):
|
||||
|
||||
return p.contains_points([(latitude,longitude)])[0]
|
||||
|
||||
def course_spline(coordinates):
|
||||
latitudes = coordinates['latitude'].values
|
||||
longitudes = coordinates['longitude'].values
|
||||
|
||||
# spline parameters
|
||||
s = 1.0
|
||||
k = min([5,len(latitudes)-1])
|
||||
nest = -1
|
||||
|
||||
t = np.linspace(0,1,len(latitudes))
|
||||
tnew = np.linspace(0,1,100)
|
||||
|
||||
latnew = CubicSpline(t,latitudes,bc_type='clamped')(tnew)
|
||||
lonnew = CubicSpline(t,longitudes,bc_type='clamped')(tnew)
|
||||
# latnew = CubicSpline(t,latitudes,bc_type='natural')(tnew)
|
||||
# lonnew = CubicSpline(t,longitudes,bc_type='natural')(tnew)
|
||||
|
||||
# tckp,u = splprep([t,latitudes,longitudes],s=s,k=k,nest=nest)
|
||||
# tnew,latnew,lonnew = splev(np.linspace(0,1,100),tckp)
|
||||
|
||||
newcoordinates = pd.DataFrame({
|
||||
'latitude':latnew,
|
||||
'longitude':lonnew,
|
||||
})
|
||||
|
||||
return newcoordinates
|
||||
|
||||
def course_coord_center(course):
|
||||
|
||||
@@ -379,10 +422,12 @@ def course_coord_center(course):
|
||||
latitude = pd.Series(latitudes).median()
|
||||
longitude = pd.Series(longitudes).median()
|
||||
|
||||
|
||||
coordinates = pd.DataFrame({
|
||||
'latitude':latitudes,
|
||||
'longitude':longitudes,
|
||||
})
|
||||
|
||||
|
||||
return latitude,longitude,coordinates
|
||||
|
||||
@@ -406,6 +451,62 @@ def course_coord_maxmin(course):
|
||||
|
||||
return lat_min,lat_max,long_min,long_max
|
||||
|
||||
def get_dir_vector(polygon1,polygon2):
|
||||
lat1,lon1 = polygon_coord_center(polygon1)
|
||||
lat2,lon2 = polygon_coord_center(polygon2)
|
||||
|
||||
return [lat2-lat1,lon2-lon1]
|
||||
|
||||
def get_delta(vector,polygon):
|
||||
x = pd.Series(range(10000))/9999.
|
||||
vlat = vector[0]
|
||||
vlon = vector[1]
|
||||
|
||||
lat1,lon1 = polygon_coord_center(polygon)
|
||||
|
||||
lat = x.apply(lambda x:lat1+x*vlat)
|
||||
lon = x.apply(lambda x:lon1+x*vlon)
|
||||
|
||||
totdist,bearing = geo_distance(lat1,lon1,lat1+vlat,lon1+vlon)
|
||||
|
||||
dist = x*totdist
|
||||
|
||||
p = polygon_to_path(polygon)
|
||||
|
||||
f = lambda x: coordinate_in_path(x['lat'],x['lon'],p)
|
||||
|
||||
df = pd.DataFrame({'x':x,
|
||||
'lat':lat,
|
||||
'lon':lon,
|
||||
'dist':dist,
|
||||
})
|
||||
|
||||
df['inpolygon'] = df.apply(f,axis=1)
|
||||
|
||||
|
||||
b = (~df['inpolygon']).shift(-1)+df['inpolygon']
|
||||
|
||||
|
||||
if len(df[b==2]):
|
||||
return 1.0e3*df[b==2]['dist'].min()
|
||||
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def get_delta_start(course):
|
||||
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
|
||||
vector = get_dir_vector(polygons[0],polygons[1])
|
||||
delta = get_delta(vector,polygons[0])
|
||||
|
||||
return delta
|
||||
|
||||
def get_delta_finish(course):
|
||||
polygons = GeoPolygon.objects.filter(course=course).order_by("-order_in_course")
|
||||
vector = get_dir_vector(polygons[0],polygons[1])
|
||||
delta = get_delta(vector,polygons[0])
|
||||
|
||||
return delta
|
||||
|
||||
def course_length(course):
|
||||
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
|
||||
@@ -420,7 +521,14 @@ def course_length(course):
|
||||
|
||||
totaldist += 1000.*dist[0]
|
||||
|
||||
return int(totaldist)
|
||||
vector = get_dir_vector(polygons[0],polygons[1])
|
||||
deltastart = get_delta(vector,polygons[0])
|
||||
|
||||
polygons = polygons.reverse()
|
||||
vector = get_dir_vector(polygons[0],polygons[1])
|
||||
deltafinish = get_delta(vector,polygons[0])
|
||||
|
||||
return int(totaldist-deltastart-deltafinish)
|
||||
|
||||
sexcategories = (
|
||||
('male','male'),
|
||||
@@ -765,9 +873,10 @@ class GeoCourse(models.Model):
|
||||
name = self.name
|
||||
country = self.country
|
||||
|
||||
return u'{country} - {name}'.format(
|
||||
return u'{country} - {name} - {d}m'.format(
|
||||
name=name,
|
||||
country=country
|
||||
country=country,
|
||||
d = course_length(self)
|
||||
)
|
||||
|
||||
class GeoCourseEditForm(ModelForm):
|
||||
@@ -784,6 +893,16 @@ class GeoPolygon(models.Model):
|
||||
course = models.ForeignKey(GeoCourse, blank=True)
|
||||
order_in_course = models.IntegerField(default=0)
|
||||
|
||||
def __unicode__(self):
|
||||
name = self.name
|
||||
coursename = self.course.name
|
||||
|
||||
return u'{coursename} - {name}'.format(
|
||||
name=name,
|
||||
coursename=coursename
|
||||
)
|
||||
|
||||
|
||||
# Need error checking to insert new polygons into existing course (all later polygons
|
||||
# increase there order_in_course number
|
||||
|
||||
@@ -1069,9 +1188,12 @@ registerchoices = (
|
||||
|
||||
class VirtualRace(PlannedSession):
|
||||
# has_registration = models.BooleanField(default=False)
|
||||
registration_form = models.CharField(max_length=100,
|
||||
default='windowstart',
|
||||
choices=registerchoices)
|
||||
registration_form = models.CharField(
|
||||
max_length=100,
|
||||
default='windowstart',
|
||||
choices=registerchoices,
|
||||
verbose_name='Registration Closure Quick Selector'
|
||||
)
|
||||
registration_closure = models.DateTimeField(blank=True,null=True)
|
||||
evaluation_closure = models.DateTimeField(blank=True,null=True)
|
||||
start_time = models.TimeField(blank=True,null=True)
|
||||
@@ -1104,6 +1226,35 @@ class VirtualRace(PlannedSession):
|
||||
|
||||
return stri
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# test race window logic
|
||||
|
||||
start_time = self.start_time
|
||||
start_date = self.startdate
|
||||
startdatetime = datetime.datetime.combine(start_date,start_time)
|
||||
startdatetime = pytz.timezone(self.timezone).localize(
|
||||
startdatetime
|
||||
)
|
||||
|
||||
end_time = self.end_time
|
||||
end_date = self.enddate
|
||||
enddatetime = datetime.datetime.combine(end_date,end_time)
|
||||
enddatetime = pytz.timezone(self.timezone).localize(
|
||||
enddatetime
|
||||
)
|
||||
|
||||
if startdatetime > enddatetime:
|
||||
self.start_time = end_time
|
||||
self.startdate = end_date
|
||||
self.end_time = start_time
|
||||
self.enddate = start_date
|
||||
enddatetime = startdatetime
|
||||
|
||||
if self.evaluation_closure < enddatetime:
|
||||
self.evaluation_closure = enddatetime + timezone.timedelta(days=1)
|
||||
|
||||
super(VirtualRace,self).save(*args, **kwargs)
|
||||
|
||||
|
||||
# Date input utility
|
||||
@@ -1142,7 +1293,27 @@ class PlannedSessionForm(ModelForm):
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(PlannedSessionForm, self).__init__(*args, **kwargs)
|
||||
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
|
||||
|
||||
|
||||
def get_course_timezone(course):
|
||||
polygons = GeoPolygon.objects.filter(course = course)
|
||||
points = GeoPoint.objects.filter(polygon = polygons[0])
|
||||
lat = points[0].latitude
|
||||
lon = points[0].longitude
|
||||
|
||||
tf = TimezoneFinder()
|
||||
try:
|
||||
timezone_str = tf.timezone_at(lng=lon,lat=lat)
|
||||
except ValueError:
|
||||
timezone_str = 'UTC'
|
||||
|
||||
if timezone_str is None:
|
||||
timezone_str = tf.closest_timezone_at(lng=lon,lat=lat)
|
||||
if timezone_str is None:
|
||||
timezone_str = 'UTC'
|
||||
|
||||
return timezone_str
|
||||
|
||||
|
||||
class VirtualRaceForm(ModelForm):
|
||||
course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None)
|
||||
registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
|
||||
@@ -1187,6 +1358,44 @@ class VirtualRaceForm(ModelForm):
|
||||
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
|
||||
|
||||
|
||||
def clean(self):
|
||||
cd = self.cleaned_data
|
||||
course = cd['course']
|
||||
geocourse = GeoCourse.objects.get(id=course.id)
|
||||
timezone_str = get_course_timezone(geocourse)
|
||||
|
||||
start_time = cd['start_time']
|
||||
start_date = cd['startdate']
|
||||
startdatetime = datetime.datetime.combine(start_date,start_time)
|
||||
startdatetime = pytz.timezone(timezone_str).localize(
|
||||
startdatetime
|
||||
)
|
||||
|
||||
end_time = cd['end_time']
|
||||
end_date = cd['enddate']
|
||||
enddatetime = datetime.datetime.combine(end_date,end_time)
|
||||
enddatetime = pytz.timezone(timezone_str).localize(
|
||||
enddatetime
|
||||
)
|
||||
|
||||
|
||||
if startdatetime > enddatetime:
|
||||
raise forms.ValidationError("The Start of the Race Window should be before the End of the Race Window")
|
||||
|
||||
try:
|
||||
evaluation_closure = cd['evaluation_closure']
|
||||
except KeyError:
|
||||
evaluation_closure = enddatetime+datetime.timedelta(days=1)
|
||||
cd['evaluation_closure'] = evaluation_closure
|
||||
|
||||
if cd['evaluation_closure'] <= enddatetime:
|
||||
raise forms.ValidationError("Evaluation closure deadline should be after the Race Window closes")
|
||||
|
||||
if cd['evaluation_closure'] <= timezone.now():
|
||||
raise forms.ValidationError("Evaluation closure cannot be in the past")
|
||||
|
||||
|
||||
return cd
|
||||
|
||||
class PlannedSessionFormSmall(ModelForm):
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<tr>
|
||||
<th> Country</th>
|
||||
<th> Name</th>
|
||||
<th> Distance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -41,6 +42,9 @@
|
||||
<a href="/rowers/courses/{{ course.id }}">{{ course.name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ course|courselength }} m
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/{{ timeperiod }}/rower/{{ rower.id }}">Edit</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/clone/{{ timeperoid }}/rower/{{ rower.id }}">Clone</a>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/clone/{{ timeperiod }}/rower/{{ rower.id }}">Clone</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
@@ -96,8 +96,9 @@
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/{{ timeperiod }}/rower/{{ rower.id }}">Edit</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/clone/{{ timeperiod }}/rower/{{ rower.id }}">Clone</a>
|
||||
</td>
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}/clone/{{ timeperiod }}/rower/{{ rower.id }}">Clone</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm">Delete</a>
|
||||
</td>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}New Virtual Race{% endblock %}
|
||||
{% block title %}Rowsandall Virtual Race{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
import datetime
|
||||
register = template.Library()
|
||||
from rowers.utils import calculate_age
|
||||
from rowers.models import course_length
|
||||
from rowers.plannedsessions import (
|
||||
race_can_register, race_can_submit,race_rower_status
|
||||
)
|
||||
@@ -75,7 +76,10 @@ def deltatimeprint(d):
|
||||
else:
|
||||
return strfdeltah(d)
|
||||
|
||||
|
||||
@register.filter
|
||||
def courselength(course):
|
||||
return course_length(course)
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def jsdict(dict,key):
|
||||
s = dict.get(key)
|
||||
|
||||
@@ -463,9 +463,9 @@ urlpatterns = [
|
||||
url(r'^sessions/multicreate/(?P<timeperiod>[\w\ ]+.*)$',
|
||||
views.plannedsession_multicreate_view),
|
||||
|
||||
url(r'^sessions/(?P<id>\d+)/edit$',views.plannedsession_edit_view),
|
||||
url(r'^sessions/(?P<id>\d+)/edit/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',views.plannedsession_edit_view),
|
||||
url(r'^sessions/(?P<id>\d+)/edit/(?P<timeperiod>[\w\ ]+.*)$',views.plannedsession_edit_view),
|
||||
url(r'^sessions/(?P<id>\d+)/edit$',views.plannedsession_edit_view),
|
||||
|
||||
url(r'^sessions/(?P<id>\d+)/clone$',views.plannedsession_clone_view),
|
||||
url(r'^sessions/(?P<id>\d+)/clone/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',views.plannedsession_clone_view),
|
||||
|
||||
@@ -253,8 +253,9 @@ def isbreakthrough(delta,cpvalues,p0,p1,p2,p3,ratio):
|
||||
pwr *= ratio
|
||||
|
||||
|
||||
delta = delta.values
|
||||
cpvalues = cpvalues.values
|
||||
delta = delta.values.astype(int)
|
||||
cpvalues = cpvalues.values.astype(int)
|
||||
pwr = pwr.astype(int)
|
||||
|
||||
res = np.sum(cpvalues>pwr)
|
||||
res2 = np.sum(cpvalues>pwr2)
|
||||
|
||||
@@ -10822,7 +10822,13 @@ def workout_split_view(request,id=id):
|
||||
splitsecond += splittime.second
|
||||
splitsecond += splittime.microsecond/1.e6
|
||||
splitmode = form.cleaned_data['splitmode']
|
||||
ids,mesgs = dataprep.split_workout(r,row,splitsecond,splitmode)
|
||||
try:
|
||||
ids,mesgs = dataprep.split_workout(
|
||||
r,row,splitsecond,splitmode
|
||||
)
|
||||
except IndexError:
|
||||
messages.error("Something went wrong in Split")
|
||||
|
||||
for message in mesgs:
|
||||
messages.info(request,message)
|
||||
|
||||
@@ -13588,7 +13594,6 @@ def virtualevent_create_view(request):
|
||||
startdatetime = datetime.datetime.combine(startdate,start_time)
|
||||
enddatetime = datetime.datetime.combine(enddate,end_time)
|
||||
|
||||
print enddatetime
|
||||
|
||||
startdatetime = pytz.timezone(timezone_str).localize(
|
||||
startdatetime
|
||||
|
||||
Reference in New Issue
Block a user