From 0a88a059bc9e75bbc1a6e6601d8daffab3de9c7b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 1 Jun 2020 13:02:22 +0200 Subject: [PATCH 1/8] interactive compare map (not working) --- rowers/interactiveplots.py | 139 +++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 07a84620..62ef4e79 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2047,6 +2047,145 @@ def leaflet_chart(lat,lon,name=""): + return script,div + +def leaflet_chart_compare(latlondf,name=""): + if lat.empty or lon.empty: + return [0,"invalid coordinate data"] + + + # Throw out 0,0 + df = pd.DataFrame({ + 'lat':lat, + 'lon':lon + }) + + df = df.replace(0,np.nan) + df = df.loc[(df!=0).any(axis=1)] + df.fillna(method='bfill',axis=0,inplace=True) + df.fillna(method='ffill',axis=0,inplace=True) + lat = df['lat'] + lon = df['lon'] + if lat.empty or lon.empty: + return [0,"invalid coordinate data"] + + latmean = lat.mean() + lonmean = lon.mean() + latbegin = lat[lat.index[0]] + longbegin = lon[lon.index[0]] + latend = lat[lat.index[-1]] + longend = lon[lon.index[-1]] + + coordinates = zip(lat,lon) + + scoordinates = "[" + + for x,y in coordinates: + scoordinates += """[{x},{y}], + """.format( + x=x, + y=y + ) + + scoordinates += "]" + + script = """ + + """.format( + latmean=latmean, + lonmean=lonmean, + latbegin = latbegin, + latend=latend, + longbegin=longbegin, + longend=longend, + scoordinates=scoordinates, + ) + + div = """ +

 

+ """ + + + return script,div def leaflet_chart2(lat,lon,name=""): From ecb90c9ac493779de90c67ec7114c852c969fe35 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 1 Jun 2020 21:45:46 +0200 Subject: [PATCH 2/8] initial attempt - not working yet --- rowers/interactiveplots.py | 15 +++++- rowers/views/workoutviews.py | 97 +++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 62ef4e79..e764f853 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2049,7 +2049,20 @@ def leaflet_chart(lat,lon,name=""): return script,div -def leaflet_chart_compare(latlondf,name=""): +def leaflet_chart_compare(workoutids,labeldict={},startenddict={}): + data = [] + for id in workoutids: + w = Workout.objects.get(id=id) + rowdata = rdata(w.csvfilename) + df = pd.DataFrame({ + 'id':id, + 'latitude':rowdata.df[' latitude'], + 'longitude':rowdata.df[' longitude'] + }) + data.append(f) + + df = pd.concat(data,axis=0) + if lat.empty or lon.empty: return [0,"invalid coordinate data"] diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 40be42c1..8751b45f 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1491,6 +1491,99 @@ def team_comparison_select(request, 'teams':get_my_teams(request.user), }) +def virtualevent_mapcompare_view(request,id=0): + results = [] + + r = None + if not request.user.is_anonymous: + r = getrower(request.user) + + try: + race = VirtualRace.objects.get(id=id) + except VirtualRace.DoesNotExist: + raise Http404("Virtual Challenge does not exist") + + if race.sessiontype != 'race': + url = reverse(virtualevent_view,kwargs={'id':id}) + messages.error(request,"This challenge doesn't have map data") + return HttpResponseRedirect(request) + + results = VirtualRaceResult.objects.filter( + race=race, + workoutid__isnull=False, + coursecompleted=True, + ).order_by("distance","duration") + + workoutids = [result.workoutid for result in results] + + startenddict = {} + if race.sessiontype == 'race': + for result in results: + startenddict[result.workoutid] = (result.startsecond,result.endsecond) + + if len(workoutids) == 0: + url = reverse('virtualevent_view', + kwargs={ + 'id':race.id, + }) + + messages.info(request,'There are no results to display') + + return HttpResponseRedirect(url) + + workouts = [] + for id in workoutids: + try: + workouts.append(Workout.objects.get(id=id)) + except Workout.DoesNotExist: + pass + + labeldict = { + int(w.id): w.__str__() for w in workouts + } + + script,div = leaflet_chart_compare(workoutids, + labeldict=labeldict, + startenddict=startenddict) + + breadcrumbs = [ + { + 'url': reverse('virtualevents_view'), + 'name': 'Racing' + }, + { + 'url':reverse('virtualevent_view', + kwargs={ + 'id':race.id, + } + ), + 'name': race.name + }, + { + 'url':reverse('virtualevent_mapcompare_view', + kwargs={ + 'id':race.id, + } + ), + 'name': 'Compare' + } + ] + + + return render(request,'mapcompare.html', + {'interactiveplot':script, + 'the_div':div, + 'breadcrumbs':breadcrumbs, + 'rower':r, + 'race':race, + 'results':results, + 'active':'nav-racing', + 'promember':promember, + 'teamid':0, + 'chartform':chartform, + 'teams':[] + }) + def virtualevent_compare_view(request,id=0): results = [] @@ -1506,7 +1599,7 @@ def virtualevent_compare_view(request,id=0): try: race = VirtualRace.objects.get(id=id) except VirtualRace.DoesNotExist: - raise Http404("Virtual Race does not exist") + raise Http404("Virtual Challenge does not exist") if race.sessiontype == 'race': script,div = course_map(race.course) @@ -1531,6 +1624,8 @@ def virtualevent_compare_view(request,id=0): + + if len(workoutids) == 0: url = reverse('virtualevent_view', kwargs={ From e219b8580f993f250458221fd9babaeee7bffddf Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 2 Jun 2020 15:40:45 +0200 Subject: [PATCH 3/8] a few updates --- rowers/templates/mapcompare.html | 49 ++++++++++++++++++++++++++++++++ rowers/urls.py | 2 ++ rowers/views/workoutviews.py | 2 -- 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 rowers/templates/mapcompare.html diff --git a/rowers/templates/mapcompare.html b/rowers/templates/mapcompare.html new file mode 100644 index 00000000..e9ca1e92 --- /dev/null +++ b/rowers/templates/mapcompare.html @@ -0,0 +1,49 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}View Comparison {% endblock %} + +{% block main %} + + + + +{{ interactiveplot |safe }} + +

Interactive Comparison

+ + +
    +
  • +
    + {{ the_div|safe }} +
    + +
  • +
  • +
    + {% csrf_token %} + + {{ chartform.as_table }} +
    + +

    + +

    +
    +
  • +
+ + +{% endblock %} + +{% block sidebar %} +{% if active == 'nav-racing' %} +{% include 'menu_racing.html' %} +{% else %} +{% include 'menu_workouts.html' %} +{% endif %} +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 1997e73f..3b0c6a4c 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -342,6 +342,8 @@ urlpatterns = [ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/wind/$',views.workout_wind_view,name='workout_wind_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/image/$',views.workout_uploadimage_view,name='workout_uploadimage_view'), re_path(r'^virtualevent/(?P\d+)/compare/$',views.virtualevent_compare_view,name='virtualevent_compare_view'), + re_path(r'^virtualevent/(?P\d+)/mapcompare/$',views.virtualevent_mapcompare_view, + name='virtualevent_mapcompare_view'), re_path(r'^virtualevent/(?P\d+)/image/$', views.virtualevent_uploadimage_view,name='virtualevent_uploadimage_view'), re_path(r'^virtualevent/(?P\d+)/setimage/(?P\d+)/$', diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 8751b45f..2765a2d3 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1578,9 +1578,7 @@ def virtualevent_mapcompare_view(request,id=0): 'race':race, 'results':results, 'active':'nav-racing', - 'promember':promember, 'teamid':0, - 'chartform':chartform, 'teams':[] }) From bf8666a988956ecea53804727895d332174fa65c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 2 Jun 2020 22:05:20 +0200 Subject: [PATCH 4/8] first working prototype --- rowers/interactiveplots.py | 76 ++++++++++++++++++++++++++------ rowers/templates/mapcompare.html | 31 +++++-------- rowers/views/workoutviews.py | 4 +- 3 files changed, 74 insertions(+), 37 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 76aa0a2d..3833dda9 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2054,10 +2054,12 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): for id in workoutids: w = Workout.objects.get(id=id) rowdata = rdata(w.csvfilename) + time = rowdata.df['TimeStamp (sec)'] df = pd.DataFrame({ - 'id':id, + 'workoutid':id, 'lat':rowdata.df[' latitude'], - 'lon':rowdata.df[' longitude'] + 'lon':rowdata.df[' longitude'], + 'time':time-time[0], }) data.append(df) @@ -2072,15 +2074,24 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): df = df.loc[(df!=0).any(axis=1)] df.fillna(method='bfill',axis=0,inplace=True) df.fillna(method='ffill',axis=0,inplace=True) + + + lat = df['lat'] lon = df['lon'] if lat.empty or lon.empty: return [0,"invalid coordinate data"] - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - latend = lat[lat.index[-1]] - longend = lon[lon.index[-1]] + latbegin = lat.values[0] + longbegin = lon.values[0] + latend = lat.values[-1] + longend = lon.values[-1] + + colors = itertools.cycle(palette) + try: + items = itertools.izip(workoutids,colors) + except AttributeError: + items = zip(workoutids,colors) coordinates = zip(lat,lon) @@ -2166,16 +2177,8 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): "Navionics":navionics, }}).addTo(mymap); - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("Start"); - var emarker = new L.marker([{latend}, {longend}]).addTo(mymap); - emarker.bindPopup("End"); - var latlongs = {scoordinates} - var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap) - mymap.fitBounds(polyline.getBounds()) - """.format( latmean=latmean, lonmean=lonmean, @@ -2186,6 +2189,51 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): scoordinates=scoordinates, ) + for id,color in items: + group = df[df['workoutid']==int(id)].copy() + try: + startsecond,endsecond = startenddict[id] + except KeyError: + startsecond = 0 + endsecond = 0 + + group.sort_values(by='time',ascending=True,inplace=True) + group.dropna(axis=0,how='any',inplace=True) + if endsecond > 0: + group['time'] = group['time'] - startsecond + mask = group['time'] < 0 + group.mask(mask,inplace=True) + mask = group['time'] > (endsecond-startsecond) + group.mask(mask,inplace=True) + + lat = group['lat'].dropna() + lon = group['lon'].dropna() + + coordinates = zip(lat,lon) + + scoordinates = "[" + for x,y in coordinates: + scoordinates += """[{x},{y}], + """.format(x=x,y=y) + scoordinates += "]" + + script += """ + var latlongs = {scoordinates} + var polyline = L.polyline(latlongs, {{color:'{color}'}}).addTo(mymap) + mymap.fitBounds(polyline.getBounds()) + """.format( + scoordinates=scoordinates, + color=color, + ) + + script += """ + + + """ + + + + div = """

 

""" diff --git a/rowers/templates/mapcompare.html b/rowers/templates/mapcompare.html index e9ca1e92..9791b74b 100644 --- a/rowers/templates/mapcompare.html +++ b/rowers/templates/mapcompare.html @@ -1,6 +1,12 @@ {% extends "newbase.html" %} {% load staticfiles %} {% load rowerfilters %} +{% load leaflet_tags %} + +{% block meta %} +{% leaflet_js %} +{% leaflet_css %} +{% endblock %} {% block title %}View Comparison {% endblock %} @@ -11,32 +17,15 @@ Bokeh.set_log_level("info"); -{{ interactiveplot |safe }} -

Interactive Comparison

-
    -
  • -
    - {{ the_div|safe }} + +
    + {{ mapdiv|safe }} + {{ mapscript|safe }}
    -
  • -
  • -
    - {% csrf_token %} - - {{ chartform.as_table }} -
    - -

    - -

    -
    -
  • -
- {% endblock %} diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index c672d11d..f5c06fd5 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1571,8 +1571,8 @@ def virtualevent_mapcompare_view(request,id=0): return render(request,'mapcompare.html', - {'interactiveplot':script, - 'the_div':div, + {'mapscript':script, + 'mapdiv':div, 'breadcrumbs':breadcrumbs, 'rower':r, 'race':race, From 326aed8f7d43e3de6243bd27f117ac1f5b4ede61 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 2 Jun 2020 22:13:41 +0200 Subject: [PATCH 5/8] working pop up --- rowers/interactiveplots.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 3833dda9..2d4980bb 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2197,6 +2197,11 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): startsecond = 0 endsecond = 0 + try: + label = labeldict[id] + except KeyError: + label = str(id) + group.sort_values(by='time',ascending=True,inplace=True) group.dropna(axis=0,how='any',inplace=True) if endsecond > 0: @@ -2220,10 +2225,15 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): script += """ var latlongs = {scoordinates} var polyline = L.polyline(latlongs, {{color:'{color}'}}).addTo(mymap) + polyline.bindPopup("{label}") + polyline.on('mouseover',function(ev) {{ + ev.target.openPopup(); + }}); mymap.fitBounds(polyline.getBounds()) """.format( scoordinates=scoordinates, color=color, + label=label ) script += """ From 36586293e2948097275f32678300a487a2d13a0a Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 2 Jun 2020 22:14:52 +0200 Subject: [PATCH 6/8] breadcrumbs --- rowers/views/workoutviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index f5c06fd5..88d75939 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1565,7 +1565,7 @@ def virtualevent_mapcompare_view(request,id=0): 'id':race.id, } ), - 'name': 'Compare' + 'name': 'Course Compare' } ] From 7ac7dbe4226d340b36c19282b2a2c74ae3aec1fd Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 3 Jun 2020 14:03:40 +0200 Subject: [PATCH 7/8] working with explanation --- rowers/interactiveplots.py | 10 +++++++--- rowers/templates/mapcompare.html | 4 +++- rowers/templates/virtualevent.html | 4 ++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 2d4980bb..ac99a831 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2225,21 +2225,25 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): script += """ var latlongs = {scoordinates} var polyline = L.polyline(latlongs, {{color:'{color}'}}).addTo(mymap) - polyline.bindPopup("{label}") + polyline.bindPopup("{label}",{{ autoPan: false, autoClose: false }}).openPopup() polyline.on('mouseover',function(ev) {{ ev.target.openPopup(); }}); + polyline.on('dblclick', function (e) {{ + mymap.removeLayer(this); + }}); mymap.fitBounds(polyline.getBounds()) """.format( scoordinates=scoordinates, color=color, - label=label + label=label, + id=id, ) script += """ - """ + """ diff --git a/rowers/templates/mapcompare.html b/rowers/templates/mapcompare.html index 9791b74b..db678501 100644 --- a/rowers/templates/mapcompare.html +++ b/rowers/templates/mapcompare.html @@ -17,7 +17,9 @@ Bokeh.set_log_level("info"); -

Interactive Comparison

+

Course Map Comparison

+ +

Click on a line to see the label. Double click on a line to remove it. Reload to get back all lines.

diff --git a/rowers/templates/virtualevent.html b/rowers/templates/virtualevent.html index 86e61402..3b8c6c57 100644 --- a/rowers/templates/virtualevent.html +++ b/rowers/templates/virtualevent.html @@ -377,6 +377,10 @@

Compare Results + {% if race.sessiontype == 'race' %} + Compare Course + {% endif %}

{% if race.manager == request.user %} Date: Wed, 3 Jun 2020 14:16:09 +0200 Subject: [PATCH 8/8] adding gates --- rowers/interactiveplots.py | 64 ++++++++++++++++++++++++++------ rowers/templates/mapcompare.html | 4 +- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index ac99a831..aa884ea1 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2069,6 +2069,52 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): latmean,lonmean,coordinates = course_coord_center(course) lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) + coordinates = course_spline(coordinates) + + + + 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 = """[ + """ + + for p in polygons: + pcoordinates += """[ + [""" + + points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") + + for pt in points: + pcoordinates += "[{x},{y}],".format( + x = pt.latitude, + y = pt.longitude + ) + + # remove last comma + pcoordinates = pcoordinates[:-1] + pcoordinates += """] + ], + """ + + pcoordinates += """ + ]""" + + # Throw out 0,0 df = df.replace(0,np.nan) df = df.loc[(df!=0).any(axis=1)] @@ -2093,18 +2139,7 @@ def leaflet_chart_compare(course,workoutids,labeldict={},startenddict={}): except AttributeError: items = zip(workoutids,colors) - coordinates = zip(lat,lon) - scoordinates = "[" - - for x,y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" script = """