Private
Public Access
1
0

courses functionality

This commit is contained in:
Sander Roosendaal
2018-02-21 12:57:46 +01:00
parent d3e3925625
commit 289d3fc2cc
13 changed files with 322 additions and 62 deletions

View File

@@ -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()

View File

@@ -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("<b>{name}</b>");
""".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}
</script>
""".format(
latmean=latmean,
lonmean=lonmean,
scoordinates=scoordinates,
pcoordinates=pcoordinates
pcoordinates=pcoordinates,
plabels = plabels
)
div = """

View File

@@ -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)

View File

@@ -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 %}
<div class="grid_12 alpha">
<div class="grid_2 alpha">
{% if nosessions %}
<a class="button small red" href="/rowers/courses/{{ course.id }}/delete">Delete</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
{% if course.manager == rower %}
<a class="button small gray" href="/rowers/courses/{{ course.id }}">View Course</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">
<h1>{{ course.name }}</h1>
<div class="grid_6 alpha">
<form id="course_form" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1 alpha">
<input class="button green" type="submit" value="Submit">
</div>
</form>
</div>
<div class="grid_6 omega">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</div>
{% endblock %}

View File

@@ -44,7 +44,7 @@
</form>
</div>
{% endblock %}
{% block scripts %}

View File

@@ -9,21 +9,47 @@
{% block og_title %}{{ course.name }} {% endblock %}
{% block content %}
<div class="grid_12 alpha">
{% if nosessions %}
<div class="grid_2 alpha">
{% if nosessions %}
<a class="button small red" href="/rowers/courses/{{ course.id }}/delete">Delete</a>
</div>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
{% if course.manager == rower %}
<a class="button small gray" href="/rowers/courses/{{ course.id }}/edit">Edit</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">
<h1>{{ course.name }}</h1>
<div class="grid_6 alpha">
<table class="listtable shortpadded" width="100%">
<tr>
<th>Name</th><td>{{ course.name }}</td>
</tr>
<tr>
<th>Country</th><td>{{ course.country }}</td>
</tr>
<tr>
<th>Notes</th><td>{{ course.notes }}</td>
</tr>
</table>
</div>
<div class="grid_6 omega">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</div>

View File

@@ -20,8 +20,8 @@
<div class="grid_12">
<div id="courses_table" class="grid_8 alpha">
<h3>Courses</h3>
<h1>Courses</h1>
{% if courses %}
<table width="100%" class="listtable shortpadded">
<thead>
@@ -29,27 +29,52 @@
<th> Country</th>
<th> Name</th>
</tr>
</thead>
</thead>
<tbody>
{% for course in courses %}
<td> {{ course.country }} </td>
<td>
{% if course.manager.user == user %}
<a href="/rowers/courses/{{ course.id }}/edit">{{ course.name }}</a>
{% else %}
<a href="/rowers/courses/{{ course.id }}">{{ course.name }}</a>
{% endif %}
</td>
<tr>
<td> {{ course.country }} </td>
<td>
{% if course.manager.user == user %}
<a href="/rowers/courses/{{ course.id }}/edit">{{ course.name }}</a>
{% else %}
<a href="/rowers/courses/{{ course.id }}">{{ course.name }}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No courses found </p>
{% endif %}
<div class="grid_6 alpha">
<div class="grid_2 prefix_1 alpha">
<a class="button small green" href="/rowers/courses/upload">Add Courses</a>
</div>
<p>&nbsp;</p>
<form id="searchform" action="/rowers/list-courses/"
method="get" accept-charset="utf-8">
<div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div>
<div class="grid_1 omega">
<button class="button blue small" type="submit">
Search
</button>
</div>
</form>
</div>
<div class="grid_2 omega">
&nbsp;
</div>
</div>
<div class="grid_4 omega">
@@ -68,40 +93,52 @@
{% endif %}
</div>
<div class="grid_4" id="about">
<h3>About</h3>
<p>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 <a href="http://blog.rowsandall.com/">blog</a>
<h2>How-to</h2>
<p>
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 <a href="earth.google.com">Google Earth</a>
to mark the start and finish lines using polygons. The process is identical
to creating custom courses for the
<a href="http://performancephones.com/crewnerd/">CrewNerd</a>
app.
</p>
<div style="text-align: right; padding: 2em">
<a href="http://blog.rowsandall.com/">
<img src="/static/img/sander.jpg" width="80"></a>
</div>
<p>CrewNerd has published a nice video tutorial of the process.
<a href="https://youtu.be/whhWFmMJbhM">Click here</a> to see the video. The part
we're interested in starts at 2:05.
</p>
<p>
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.
</p>
<p>
<ul>
<li>Open Google Earth</li>
<li>Create Start polygon</li>
<li>Optional: Create First "must row through" polygon</li>
<li>Optional: Create subsequent "must row through" polygons</li>
<li>Create Finish polygon</li>
<li>Save Place as KML file</li>
<li>Upload the file to rowsandall.com using the "Add Courses" button</li>
</ul>
</p>
<p>You are allowed to have multiple courses in one KML file.
Your CrewNerd "courses.kml" file works out of the box</p>
<p>The site doesn't test for duplicate courses.</p>
</div>
</div>
</div>
</div>
<div class="grid_6 alpha">
<div class="grid_2 prefix_1 alpha">
<a class="button small green" href="/rowers/courses/upload">Add Course</a>
</div>
<p>&nbsp;</p>
<form id="searchform" action="/rowers/list-courses/"
method="get" accept-charset="utf-8">
<div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div>
<div class="grid_1 omega">
<button class="button blue small" type="submit">
Search
</button>
</div>
</form>
</div>
<div class="grid_2 omega">
&nbsp;
</div>
{% endblock %}

View File

@@ -57,7 +57,11 @@
{% endfor %}
</div>
</div>
{% endif %}
<div class="grid_2 omega">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">

View File

@@ -53,6 +53,9 @@
</a>
</div>
{% endif %}
<div class="grid_2 omega">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
</div>
<div class="grid_12 alpha">
<div id="right" class="grid_6 alpha">

View File

@@ -56,7 +56,9 @@
</div>
</div>
{% endif %}
<div class="grid_2 omega">
<a class="button small gray" href="/rowers/list-courses">Courses</a>
</div>
<div class="grid_12 alpha">
{% if plannedsessions %}
<p>

View File

@@ -22,6 +22,13 @@
&nbsp;
{% endif %}
</div>
<div class="grid_2">
{% if plannedsession.sessiontype == 'coursetest' %}
<a class="button small gray" href="/rowers/list-courses">Courses</a>
{% else %}
&nbsp;
{% endif %}
</div>
</div>
<div class="grid_12 alpha">
<div id="left" class="grid_6 alpha">
@@ -128,6 +135,16 @@
</tbody>
</table>
</div>
<div class="grid_12 alpha">
<div id="left" class="grid_6 alpha">
{% if coursescript %}
<h1>Course</h1>
{{ coursediv|safe }}
{{ coursescript|safe }}
{% endif %}
</div>
</div>

View File

@@ -489,6 +489,7 @@ urlpatterns = [
url(r'^courses/(?P<id>\d+)/edit$',views.course_edit_view,
name='course_edit_view'),
url(r'^courses/(?P<id>\d+)/delete$',views.course_delete_view),
url(r'^courses/(?P<id>\d+)$',views.course_view),
]
if settings.DEBUG:

View File

@@ -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
}
)