diff --git a/rowers/courses.py b/rowers/courses.py index 171e906e..16c3f7cd 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -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) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 756299f5..74a2cce2 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -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='', diff --git a/rowers/models.py b/rowers/models.py index db5795ed..6a15539b 100644 --- a/rowers/models.py +++ b/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): diff --git a/rowers/templates/list_courses.html b/rowers/templates/list_courses.html index 597931f9..5505a2ef 100644 --- a/rowers/templates/list_courses.html +++ b/rowers/templates/list_courses.html @@ -28,6 +28,7 @@ Country Name + Distance @@ -41,6 +42,9 @@ {{ course.name }} {% endif %} + + {{ course|courselength }} m + diff --git a/rowers/templates/plannedsessioncreate.html b/rowers/templates/plannedsessioncreate.html index 1d48baba..d803e32c 100644 --- a/rowers/templates/plannedsessioncreate.html +++ b/rowers/templates/plannedsessioncreate.html @@ -103,7 +103,7 @@ Edit - Clone + Clone diff --git a/rowers/templates/plannedsessionedit.html b/rowers/templates/plannedsessionedit.html index e1bbedb3..deb11173 100644 --- a/rowers/templates/plannedsessionedit.html +++ b/rowers/templates/plannedsessionedit.html @@ -96,8 +96,9 @@ Edit - Clone - + Clone + Delete diff --git a/rowers/templates/virtualevent.html b/rowers/templates/virtualevent.html index 8fe2c290..36d75779 100644 --- a/rowers/templates/virtualevent.html +++ b/rowers/templates/virtualevent.html @@ -2,7 +2,7 @@ {% load staticfiles %} {% load rowerfilters %} -{% block title %}New Virtual Race{% endblock %} +{% block title %}Rowsandall Virtual Race{% endblock %} {% block content %} diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index 88342bea..80e888a7 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -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) diff --git a/rowers/urls.py b/rowers/urls.py index b207fa47..23117eac 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -463,9 +463,9 @@ urlpatterns = [ url(r'^sessions/multicreate/(?P[\w\ ]+.*)$', views.plannedsession_multicreate_view), - url(r'^sessions/(?P\d+)/edit$',views.plannedsession_edit_view), url(r'^sessions/(?P\d+)/edit/(?P[\w\ ]+.*)/rower/(?P\d+)$',views.plannedsession_edit_view), url(r'^sessions/(?P\d+)/edit/(?P[\w\ ]+.*)$',views.plannedsession_edit_view), + url(r'^sessions/(?P\d+)/edit$',views.plannedsession_edit_view), url(r'^sessions/(?P\d+)/clone$',views.plannedsession_clone_view), url(r'^sessions/(?P\d+)/clone/(?P[\w\ ]+.*)/rower/(?P\d+)$',views.plannedsession_clone_view), diff --git a/rowers/utils.py b/rowers/utils.py index c3c1eaca..d9c7ec54 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -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) diff --git a/rowers/views.py b/rowers/views.py index 20dba719..61a0ac09 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -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