diff --git a/rowers/courses.py b/rowers/courses.py index 060ab246..9857b230 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -9,7 +9,6 @@ from django.db import IntegrityError import uuid from django.conf import settings -from utils import myqueue from matplotlib import path import xml.etree.ElementTree as et @@ -18,10 +17,6 @@ import pandas as pd ns = {'opengis': 'http://www.opengis.net/kml/2.2'} -import django_rq -queue = django_rq.get_queue('default') -queuelow = django_rq.get_queue('low') -queuehigh = django_rq.get_queue('low') from rowers.models import ( Rower, Workout, @@ -128,10 +123,27 @@ def time_in_polygon(df,polygon,maxmin='max'): return time +def crewnerdcourse(doc): + courses = [] + for course in doc: + name = course.findall('.//opengis:name',ns)[0].text + try: + description = course.findall('.//opengis:description',ns)[0].text + except IndexError: + description = '' + + polygonpms = course.findall('.//opengis:Placemark[opengis:Polygon]',ns) + polygons = get_polygons(polygonpms) -def kmltocourse(f): - doc = et.parse(f) - polygonpms = doc.findall('.//opengis:Placemark[opengis:Polygon]',ns) + courses.append({ + 'name':name, + 'description':description, + 'polygons':polygons + }) + + return courses + +def get_polygons(polygonpms): polygons = [] for pm in polygonpms: name = pm.findall('.//opengis:name',ns)[0].text @@ -157,6 +169,19 @@ def kmltocourse(f): }) return polygons + + +def kmltocourse(f): + doc = et.parse(f) + courses = doc.findall('.//opengis:Folder[opengis:Placemark]',ns) + + if courses: + return crewnerdcourse(courses) + + polygonpms = doc.findall('.//opengis:Placemark[opengis:Polygon]',ns) + + return get_polygons(polygonpms) + from geopy.geocoders import Nominatim geolocator = Nominatim() diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index cf9bbdd1..6b3ae2e4 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -40,6 +40,7 @@ from django.conf import settings from courses import ( course_coord_center,course_coord_maxmin, + polygon_coord_center ) import datetime @@ -731,6 +732,20 @@ def course_map(course): 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 + ) pcoordinates = """[ """ @@ -818,12 +833,15 @@ def course_map(course): var platlongs = {pcoordinates} var polygons = L.polygon(platlongs, {{color:'blue'}}).addTo(mymap) + {plabels} + """.format( latmean=latmean, lonmean=lonmean, scoordinates=scoordinates, - pcoordinates=pcoordinates + pcoordinates=pcoordinates, + plabels = plabels ) div = """ diff --git a/rowers/models.py b/rowers/models.py index 2c3f7e08..ab4f613f 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -667,8 +667,22 @@ class GeoCourse(models.Model): notes = models.CharField(blank=True,max_length=200,verbose_name='Course Notes') def __unicode__(self): name = self.name - return u'{name}'.format(name=name) + country = self.country + + return u'{country} - {name}'.format( + name=name, + country=country + ) +class GeoCourseEditForm(ModelForm): + class Meta: + model = GeoCourse + fields = ['name','notes'] + + widgets = { + 'notes': forms.Textarea, + } + class GeoPolygon(models.Model): name = models.CharField(max_length=150,blank=True) course = models.ForeignKey(GeoCourse, blank=True) diff --git a/rowers/templates/course_edit_view.html b/rowers/templates/course_edit_view.html new file mode 100644 index 00000000..64c46e59 --- /dev/null +++ b/rowers/templates/course_edit_view.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} +{% block scripts %} +{% include "monitorjobs.html" %} +{% endblock %} + +{% block title %}{{ course.name }} {% endblock %} +{% block og_title %}{{ course.name }} {% endblock %} +{% block content %} +
+
+ {% if nosessions %} + Delete + {% else %} +   + {% endif %} +
+
+ {% if course.manager == rower %} + View Course + {% else %} +   + {% endif %} +
+
+ Courses +
+
+
+ +

{{ course.name }}

+ +
+
+ + {{ form.as_table }} +
+ {% csrf_token %} +
+ +
+
+
+
+ {{ mapdiv|safe }} + + + {{ mapscript|safe }} +
+ +
+ +{% endblock %} diff --git a/rowers/templates/course_form.html b/rowers/templates/course_form.html index 957e181d..de1d0db7 100644 --- a/rowers/templates/course_form.html +++ b/rowers/templates/course_form.html @@ -44,7 +44,7 @@ - + {% endblock %} {% block scripts %} diff --git a/rowers/templates/course_view.html b/rowers/templates/course_view.html index 88c74081..89b8b03d 100644 --- a/rowers/templates/course_view.html +++ b/rowers/templates/course_view.html @@ -9,21 +9,47 @@ {% block og_title %}{{ course.name }} {% endblock %} {% block content %}
- {% if nosessions %}
+ {% if nosessions %} Delete -
+ {% else %} +   {% endif %} +
+
+ {% if course.manager == rower %} + Edit + {% else %} +   + {% endif %} +
+
+ Courses +

{{ course.name }}

+
+ + + + + + + + + + +
Name{{ course.name }}
Country{{ course.country }}
Notes{{ course.notes }}
+
+
{{ mapdiv|safe }} {{ mapscript|safe }} - +
diff --git a/rowers/templates/list_courses.html b/rowers/templates/list_courses.html index 84255ebd..6f486448 100644 --- a/rowers/templates/list_courses.html +++ b/rowers/templates/list_courses.html @@ -20,8 +20,8 @@
-

Courses

- +

Courses

+ {% if courses %} @@ -29,27 +29,52 @@ - + {% for course in courses %} - - + + + - + {% endfor %}
Country Name
{{ course.country }} - {% if course.manager.user == user %} - {{ course.name }} - {% else %} - {{ course.name }} - {% endif %} -
{{ course.country }} + {% if course.manager.user == user %} + {{ course.name }} + {% else %} + {{ course.name }} + {% endif %} +
{% else %}

No courses found

{% endif %} + +
+ +

 

+
+
+ +
+
+ +
+
+
+   +
+ + +
+
@@ -68,40 +93,52 @@ {% endif %}
-

About

-

This site is a beta site, pioneering rowing data visualization and analysis. No warranties. The site's author is - Sander Roosendaal. A Masters rower. - - Read his blog +

How-to

+

+ Courses allow you to mark the start & finish lines of your + test pieces and measure the time spent on the course (as opposed + to the total duration of a workout). This allows you to row and rank + marked courses. + + To create a course, you use Google Earth + to mark the start and finish lines using polygons. The process is identical + to creating custom courses for the + CrewNerd + app. +

-
- - -
+ +

CrewNerd has published a nice video tutorial of the process. + Click here to see the video. The part + we're interested in starts at 2:05. +

+ +

+ In addition to start and finish areas, on rowsandall.com you can add additional + polygons to mark areas that you must pass (in that order). This allows for + courses with turns around buoys, respecting buoy lines, or respecting traffic + patterns on rivers and lakes. +

+ +

+

+

+ +

You are allowed to have multiple courses in one KML file. + Your CrewNerd "courses.kml" file works out of the box

+ +

The site doesn't test for duplicate courses.

- - -
-
- Add Course -
-

 

-
-
- -
-
- -
-
-
-   -
{% endblock %} diff --git a/rowers/templates/plannedsessioncreate.html b/rowers/templates/plannedsessioncreate.html index 6c1e8f58..4b3830e8 100644 --- a/rowers/templates/plannedsessioncreate.html +++ b/rowers/templates/plannedsessioncreate.html @@ -57,7 +57,11 @@ {% endfor %} + {% endif %} +
+ Courses +
diff --git a/rowers/templates/plannedsessionedit.html b/rowers/templates/plannedsessionedit.html index 28847759..fe759a50 100644 --- a/rowers/templates/plannedsessionedit.html +++ b/rowers/templates/plannedsessionedit.html @@ -53,6 +53,9 @@
{% endif %} +
+ Courses +
{% endif %} - +
+ Courses +
{% if plannedsessions %}

diff --git a/rowers/templates/plannedsessionview.html b/rowers/templates/plannedsessionview.html index 2ee8c3d6..9234b030 100644 --- a/rowers/templates/plannedsessionview.html +++ b/rowers/templates/plannedsessionview.html @@ -22,6 +22,13 @@   {% endif %}

+
+ {% if plannedsession.sessiontype == 'coursetest' %} + Courses + {% else %} +   + {% endif %} +
@@ -128,6 +135,16 @@
+
+
+ {% if coursescript %} +

Course

+ {{ coursediv|safe }} + + {{ coursescript|safe }} + {% endif %} +
+
diff --git a/rowers/urls.py b/rowers/urls.py index acf5dfa2..27d9f7cf 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -489,6 +489,7 @@ urlpatterns = [ url(r'^courses/(?P\d+)/edit$',views.course_edit_view, name='course_edit_view'), url(r'^courses/(?P\d+)/delete$',views.course_delete_view), + url(r'^courses/(?P\d+)$',views.course_view), ] if settings.DEBUG: diff --git a/rowers/views.py b/rowers/views.py index c9578471..4f7e770f 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -62,7 +62,7 @@ from rowers.models import ( Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest, WorkoutComment,WorkoutCommentForm,RowerExportForm, CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm, - PlannedSessionFormSmall, + PlannedSessionFormSmall,GeoCourseEditForm, ) from rowers.models import ( FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet @@ -6051,9 +6051,20 @@ def boxplot_view(request,userid=0, def courses_view(request): r = getrower(request.user) - courses = GeoCourse.objects.all().order_by("country") + courses = GeoCourse.objects.all().order_by("country","name") # add search processing + query = request.GET.get('q') + if query: + query_list = query.split() + courses = GeoCourse.objects.filter( + reduce(operator.and_, + (Q(name__icontains=q) for q in query_list)) | + reduce(operator.and_, + (Q(country__icontains=q) for q in query_list)) | + reduce(operator.and_, + (Q(notes__icontains=q) for q in query_list)) + ) return render(request,'list_courses.html', {'courses':courses, @@ -8444,7 +8455,40 @@ def course_edit_view(request,id=0): ps = PlannedSession.objects.filter(course=course) nosessions = len(ps) == 0 + + script,div = course_map(course) + if request.method == 'POST': + form = GeoCourseEditForm(request.POST) + if form.is_valid(): + name = form.cleaned_data['name'] + notes = form.cleaned_data['notes'] + + course.name = name + course.notes = notes + course.save() + + form = GeoCourseEditForm(instance=course) + + return render(request, 'course_edit_view.html', + { + 'course':course, + 'mapscript':script, + 'mapdiv':div, + 'nosessions':nosessions, + 'rower':r, + 'form':form, + } + ) + +@login_required() +def course_view(request,id=0): + try: + course = GeoCourse.objects.get(id=id) + except GeoCourse.DoesNotExist: + return Http404("Course doesn't exist") + + r = getrower(request.user) script,div = course_map(course) @@ -8453,7 +8497,8 @@ def course_edit_view(request,id=0): 'course':course, 'mapscript':script, 'mapdiv':div, - 'nosessions':nosessions, + 'nosessions':False, + 'rower':r, } ) @@ -8945,9 +8990,15 @@ def course_upload_view(request): if f is not None: filename,path_and_filename = handle_uploaded_file(f) - polygons = courses.kmltocourse(path_and_filename) + cs = courses.kmltocourse(path_and_filename) - course = courses.createcourse(r,name,polygons,notes=notes) + for course in cs: + cname = name+' - '+course['name'] + cnotes = notes+'\n\n'+course['description'] + polygons = course['polygons'] + + course = courses.createcourse(r,cname,polygons,notes=cnotes) + os.remove(path_and_filename) url = reverse(courses_view) @@ -12571,6 +12622,12 @@ def plannedsession_view(request,id=0,rowerid=0, raise Http404("Planned Session does not exist") + if ps.course: + coursescript,coursediv = course_map(ps.course) + else: + coursescript = '' + coursediv = '' + if ps.manager != request.user and r not in ps.rower.all(): raise PermissionDenied("You do not have access to this session") @@ -12623,6 +12680,8 @@ def plannedsession_view(request,id=0,rowerid=0, 'plannedsession':ps, 'timeperiod':timeperiod, 'ranking':ranking, + 'coursescript': coursescript, + 'coursediv': coursediv } )