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 %}
+
+
+
+
+
+
{{ course.name }}
+
+
+
+ {{ 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 %}
+
+
{{ 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 @@
| Country |
Name |
-
+
{% for course in courses %}
- {{ 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 %}
+ |
-
+
{% endfor %}
{% 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.
+
+
+
+
+ - Open Google Earth
+ - Create Start polygon
+ - Optional: Create First "must row through" polygon
+ - Optional: Create subsequent "must row through" polygons
+ - Create Finish polygon
+ - Save Place as KML file
+ - Upload the file to rowsandall.com using the "Add Courses" button
+
+
+
+
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.
-
-
-
-
-
-
{% 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 %}
+
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 %}
+
diff --git a/rowers/templates/plannedsessions.html b/rowers/templates/plannedsessions.html
index 2e229d97..cb7b1615 100644
--- a/rowers/templates/plannedsessions.html
+++ b/rowers/templates/plannedsessions.html
@@ -56,7 +56,9 @@
{% endif %}
-
+
{% 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
}
)