From af1adf132eaf7a7a3ef10e078c07a843f3afc362 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 28 Feb 2024 14:05:10 +0100 Subject: [PATCH] better kml export --- rowers/courses.py | 63 +++++++++++++++++++++--- rowers/interactiveplots.py | 4 +- rowers/models.py | 78 +++++++++++++++++++++++++++++- rowers/templates/list_courses.html | 1 + rowers/urls.py | 1 + rowers/utils.py | 25 ++++++++++ rowers/views/apiviews.py | 53 ++++++++++++++++++-- 7 files changed, 211 insertions(+), 14 deletions(-) diff --git a/rowers/courses.py b/rowers/courses.py index 3b21f110..2104ea8f 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -4,6 +4,7 @@ from rowers.models import ( Rower, Workout, GeoPoint, GeoPolygon, GeoCourse, course_length, course_coord_center, course_coord_maxmin, + course_coord_crewnerd_navigation, polygon_coord_center, PlannedSession, polygon_to_path, coordinate_in_path ) @@ -216,8 +217,17 @@ def get_polygons(polygonpms): return polygons +def crewnerdify(polygons): + polygons[0].name = "Start" + polygons[len(polygons)-1].name = "Finish" -def coursetokml(course): + if len(polygons) > 2: + for i in range(1,len(polygons)-1): + polygons[i].name = 'WP{n}'.format(n=polygons[i].order_in_course) + + return polygons + +def coursetokml(course,cn=False): top = Element('kml') for prefix, uri in xmlns_uris_dict.items(): if prefix != '': @@ -227,23 +237,60 @@ def coursetokml(course): document = SubElement(top, 'Document') name = SubElement(document, 'name') - name.text = 'Courses.kml' - folder = SubElement(document, 'Folder') - foldername = SubElement(folder, 'name') - foldername.text = 'Courses' - folder2 = SubElement(folder, 'Folder') + name.text = 'courses' + opendoc = SubElement(document,'open') + opendoc.text = '1' + style = SubElement(document,'Style', id="default") + linestyle = SubElement(style, 'LineStyle') + linestylecolor = SubElement(linestyle, 'color') + linestylecolor.text = "ff00ffff" + polystyle = SubElement(style, 'PolyStyle') + polystylecolor = SubElement(polystyle, 'color') + polystylecolor.text = "ff7fffff" + + stylemap = SubElement(document, 'StyleMap', id="default0") + pair1 = SubElement(stylemap, 'Pair') + pair1key = SubElement(pair1, 'key') + pair1key.text = "normal" + pair1styleurl = SubElement(pair1, 'styleUrl') + pair1styleurl.text = "#default" + pair2 = SubElement(stylemap, 'Pair') + pair2key = SubElement(pair2, 'key') + pair2key.text = "highlight" + pair2styleurl = SubElement(pair2, 'styleUrl') + pair2styleurl.text = "#hl" + + stylehl = SubElement(document, 'Style', id="hl") + iconstyle = SubElement(stylehl, 'IconStyle') + scale = SubElement(iconstyle, 'scale') + scale.text = "1.2" + linestyle = SubElement(stylehl, 'LineStyle') + linestylecolor = SubElement(linestyle, 'color') + linestylecolor.text = "ff00ffff" + polystyle = SubElement(stylehl, 'PolyStyle') + polystylecolor = SubElement(polystyle, 'color') + polystylecolor.text = "ff7fffff" + + folder2 = SubElement(document, 'Folder') coursename = SubElement(folder2, 'name') coursename.text = course.name - open = SubElement(folder2, 'open') - open.text = '1' + openst = SubElement(folder2, 'open') + openst.text = '1' + coursedescription = SubElement(folder2, 'description') + coursedescription.text = "Rowsandall.com\n" + course.notes polygons = GeoPolygon.objects.filter( course=course).order_by("order_in_course") + if cn: + polygons = crewnerdify(polygons) + for polygon in polygons: placemark = SubElement(folder2, 'Placemark') polygonname = SubElement(placemark, 'name') polygonname.text = polygon.name + polygonstyle = SubElement(placemark, 'styleUrl') + polygonstyle.text = "#default0" p = SubElement(placemark, 'Polygon') tessellate = SubElement(p, 'tessellate') tessellate.text = '1' diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1264b7af..5bc84a34 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -33,7 +33,7 @@ import datetime from rowers import mytypes from rowers.courses import ( course_coord_center, course_coord_maxmin, - polygon_coord_center + polygon_coord_center, course_coord_crewnerd_navigation, ) from django.conf import settings from collections import OrderedDict @@ -2165,6 +2165,8 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, def course_map(course): latmean, lonmean, coordinates = course_coord_center(course) + if course.with_cn_nav_waypoints: + latmean, lonmean, coordinates = course_coord_crewnerd_navigation(course) lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) coordinates = course_spline(coordinates) diff --git a/rowers/models.py b/rowers/models.py index 6d0adf97..c1760c78 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -8,7 +8,7 @@ from rowers.utils import ( steps_read_fit, steps_write_fit, ps_dict_order, uniqify ) from rowers.metrics import axlabels -from rowers.utils import geo_distance +from rowers.utils import geo_distance, move_one_meter from rowers.formfields import * from rowers.database import * import uuid @@ -594,7 +594,65 @@ def course_spline(coordinates): return newcoordinates +def polygon_nearest_point(polygon, latitude, longitude,debug=False): + points = GeoPoint.objects.filter(polygon=polygon) + points = sorted(points, key = lambda p: geo_distance(p.latitude, p.longitude, latitude, longitude)) + if debug: + for p in points: + print(p,p.latitude, p.longitude, latitude, longitude, geo_distance(p.latitude, p.longitude, latitude, longitude)) + + return points[0].latitude, points[0].longitude + +def polygon_exit_point(polygon, lat1, lon1, lat2, lon2): + dist, bearing = geo_distance(lat1, lon1, lat2, lon2) + dirveclat = (lat2-lat1)/dist + dirveclon = (lon2-lon1)/dist + + newlat, newlon = move_one_meter(lat2, lon2, bearing) + path = polygon_to_path(polygon) + while path.contains_points([(newlat, newlon)])[0]: + newlat, newlon = move_one_meter(newlat, newlon, bearing) + + return newlat, newlon + +def course_coord_crewnerd_navigation(course): + polygons = GeoPolygon.objects.filter( + course=course).order_by("order_in_course") + + latitudes = [] + longitudes = [] + + latitude, longitude = polygon_coord_center(polygons[0]) + + latitudes.append(latitude) + longitudes.append(longitude) + + debug = True + + for p in polygons[1:]: + oldlat = latitude + oldlon = longitude + latitude, longitude = polygon_nearest_point(p,latitude,longitude, debug=debug) + debug = False + latitudes.append(latitude) + longitudes.append(longitude) + latitude, longitude = polygon_exit_point(p, oldlat, oldlon, latitude, longitude) + latitudes.append(latitude) + longitudes.append(longitude) + + + latitude = pd.Series(latitudes).median() + longitude = pd.Series(longitudes).median() + + coordinates = pd.DataFrame({ + 'latitude': latitudes, + 'longitude': longitudes, + }) + + return latitude, longitude, coordinates + + def course_coord_center(course): polygons = GeoPolygon.objects.filter( @@ -1590,6 +1648,24 @@ class GeoCourse(models.Model): def coord(self): return course_coord_center(self) + @property + def with_cn_nav_waypoints(self): + polygons = GeoPolygon.objects.filter(course=self).order_by("order_in_course") + + if polygons[0].name != "Start": + return False + if polygons[len(polygons)-1].name != "Finish": + return False + for i in range(1,len(polygons)-1): + if polygons[i].name[0:2].lower() != 'wp': + return False + try: + getal = float(polygons[i].name[2:]) + except ValueError: + return False + + return True + class GeoCourseEditForm(ModelForm): class Meta: diff --git a/rowers/templates/list_courses.html b/rowers/templates/list_courses.html index bc66697a..3f37a654 100644 --- a/rowers/templates/list_courses.html +++ b/rowers/templates/list_courses.html @@ -106,6 +106,7 @@

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. + There is also a written tutorial by CrewNerd.

diff --git a/rowers/urls.py b/rowers/urls.py index e179b99b..0ef84538 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -252,6 +252,7 @@ urlpatterns = [ re_path(r'^api/TCX/workouts/$', views.strokedata_tcx, name='strokedata_tcx'), re_path(r'^api/courses/$', views.course_list, name='course_list'), + re_path(r'^api/courses/(?P\d+)/$', views.get_crewnerd_kml, name='get_crewnerd_kml'), re_path(r'^500v/$', views.error500_view, name='error500_view'), re_path(r'^500q/$', views.servererror_view, name='servererror_view'), path('502/', TemplateView.as_view(template_name='502.html'), name='502'), diff --git a/rowers/utils.py b/rowers/utils.py index 92357775..bf71456b 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -312,6 +312,31 @@ def geo_distance(lat1, lon1, lat2, lon2): return [distance, bearing] +def move_one_meter(latitude, longitude, bearing): + # Earth radius in meters + earth_radius = 6371000 + + # Convert latitude and longitude from degrees to radians + lat_rad = math.radians(latitude) + lon_rad = math.radians(longitude) + + # Convert bearing from degrees to radians + bearing_rad = math.radians(bearing) + + # Calculate the new latitude + new_lat = math.asin(math.sin(lat_rad) * math.cos(1/earth_radius) + + math.cos(lat_rad) * math.sin(1/earth_radius) * math.cos(bearing_rad)) + + # Calculate the new longitude + new_lon = lon_rad + math.atan2(math.sin(bearing_rad) * math.sin(1/earth_radius) * math.cos(lat_rad), + math.cos(1/earth_radius) - math.sin(lat_rad) * math.sin(new_lat)) + + # Convert new latitude and longitude from radians to degrees + new_lat = math.degrees(new_lat) + new_lon = math.degrees(new_lon) + + return new_lat, new_lon + def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio): pwr = abs(p0)/(1+(delta/abs(p2))) diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index bdbb2782..8827de99 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -1,6 +1,7 @@ from rowers.views.statements import * from rowers.tasks import handle_calctrimp from rowers.opaque import encoder +from rowers.courses import coursetokml from xml.etree import ElementTree as ET import arrow @@ -11,6 +12,8 @@ from rowers.dataroutines import get_workouttype_from_tcx, get_startdate_time_zon from rest_framework.decorators import parser_classes from rest_framework.parsers import BaseParser +from rowers.utils import geo_distance + from datetime import datetime as dt import rowingdata.tcxtools as tcxtools @@ -252,9 +255,38 @@ Optional, not for CN def course_list(request): if request.method != 'GET': dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method)) - return HttpResponseNotAllower("Method not supported") + return HttpResponseNotAllowed("Method not supported") + + query_data = {} + name = request.GET.get('name') + distance = request.GET.get('course_distance') + latitude = request.GET.get('latitude') + longitude = request.GET.get('longitude') + distance_from = request.GET.get('distance_from') + if name is not None: + query_data['name__contains'] = name + if distance is not None: + try: + query_data['distance__lte'] = int(distance)+50 + query_data['distance__gte'] = int(distance)-50 + except: + pass + + + + courses = GeoCourse.objects.filter(**query_data) + if latitude is not None and longitude is not None and distance_from is not None: + try: + newlist = [] + for c in courses: + distance = geo_distance(float(latitude), float(longitude), c.coord[0], c.coord[1])[0] + if distance < float(distance_from): + newlist.append(c) + courses = newlist + except ValueError: + pass + - courses = GeoCourse.objects.all() courselist = [] for c in courses: d = { @@ -269,10 +301,23 @@ def course_list(request): response_dict = {'courses': courselist} - print(response_dict) - return JsonResponse(response_dict, content_type='application/json; charset=utf8') +@api_view(["GET"]) +def get_crewnerd_kml(request,id=0): + if request.method != 'GET': + dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method)) + return HttpResponseNotAllowed("Method not supported") + + try: + c = GeoCourse.objects.get(id=id) + except GeoCourse.DoesNotExist: + raise Http404("This course does not exist") + + kml = coursetokml(c, cn=True) + + return HttpResponse(kml) + # Stroke data views @csrf_exempt