better kml export
This commit is contained in:
@@ -4,6 +4,7 @@ from rowers.models import (
|
|||||||
Rower, Workout,
|
Rower, Workout,
|
||||||
GeoPoint, GeoPolygon, GeoCourse,
|
GeoPoint, GeoPolygon, GeoCourse,
|
||||||
course_length, course_coord_center, course_coord_maxmin,
|
course_length, course_coord_center, course_coord_maxmin,
|
||||||
|
course_coord_crewnerd_navigation,
|
||||||
polygon_coord_center, PlannedSession,
|
polygon_coord_center, PlannedSession,
|
||||||
polygon_to_path, coordinate_in_path
|
polygon_to_path, coordinate_in_path
|
||||||
)
|
)
|
||||||
@@ -216,8 +217,17 @@ def get_polygons(polygonpms):
|
|||||||
|
|
||||||
return polygons
|
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')
|
top = Element('kml')
|
||||||
for prefix, uri in xmlns_uris_dict.items():
|
for prefix, uri in xmlns_uris_dict.items():
|
||||||
if prefix != '':
|
if prefix != '':
|
||||||
@@ -227,23 +237,60 @@ def coursetokml(course):
|
|||||||
|
|
||||||
document = SubElement(top, 'Document')
|
document = SubElement(top, 'Document')
|
||||||
name = SubElement(document, 'name')
|
name = SubElement(document, 'name')
|
||||||
name.text = 'Courses.kml'
|
name.text = 'courses'
|
||||||
folder = SubElement(document, 'Folder')
|
opendoc = SubElement(document,'open')
|
||||||
foldername = SubElement(folder, 'name')
|
opendoc.text = '1'
|
||||||
foldername.text = 'Courses'
|
style = SubElement(document,'Style', id="default")
|
||||||
folder2 = SubElement(folder, 'Folder')
|
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 = SubElement(folder2, 'name')
|
||||||
coursename.text = course.name
|
coursename.text = course.name
|
||||||
open = SubElement(folder2, 'open')
|
openst = SubElement(folder2, 'open')
|
||||||
open.text = '1'
|
openst.text = '1'
|
||||||
|
coursedescription = SubElement(folder2, 'description')
|
||||||
|
coursedescription.text = "Rowsandall.com\n" + course.notes
|
||||||
|
|
||||||
polygons = GeoPolygon.objects.filter(
|
polygons = GeoPolygon.objects.filter(
|
||||||
course=course).order_by("order_in_course")
|
course=course).order_by("order_in_course")
|
||||||
|
|
||||||
|
if cn:
|
||||||
|
polygons = crewnerdify(polygons)
|
||||||
|
|
||||||
for polygon in polygons:
|
for polygon in polygons:
|
||||||
placemark = SubElement(folder2, 'Placemark')
|
placemark = SubElement(folder2, 'Placemark')
|
||||||
polygonname = SubElement(placemark, 'name')
|
polygonname = SubElement(placemark, 'name')
|
||||||
polygonname.text = polygon.name
|
polygonname.text = polygon.name
|
||||||
|
polygonstyle = SubElement(placemark, 'styleUrl')
|
||||||
|
polygonstyle.text = "#default0"
|
||||||
p = SubElement(placemark, 'Polygon')
|
p = SubElement(placemark, 'Polygon')
|
||||||
tessellate = SubElement(p, 'tessellate')
|
tessellate = SubElement(p, 'tessellate')
|
||||||
tessellate.text = '1'
|
tessellate.text = '1'
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import datetime
|
|||||||
from rowers import mytypes
|
from rowers import mytypes
|
||||||
from rowers.courses import (
|
from rowers.courses import (
|
||||||
course_coord_center, course_coord_maxmin,
|
course_coord_center, course_coord_maxmin,
|
||||||
polygon_coord_center
|
polygon_coord_center, course_coord_crewnerd_navigation,
|
||||||
)
|
)
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@@ -2165,6 +2165,8 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes,
|
|||||||
|
|
||||||
def course_map(course):
|
def course_map(course):
|
||||||
latmean, lonmean, coordinates = course_coord_center(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)
|
lat_min, lat_max, long_min, long_max = course_coord_maxmin(course)
|
||||||
|
|
||||||
coordinates = course_spline(coordinates)
|
coordinates = course_spline(coordinates)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from rowers.utils import (
|
|||||||
steps_read_fit, steps_write_fit, ps_dict_order, uniqify
|
steps_read_fit, steps_write_fit, ps_dict_order, uniqify
|
||||||
)
|
)
|
||||||
from rowers.metrics import axlabels
|
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.formfields import *
|
||||||
from rowers.database import *
|
from rowers.database import *
|
||||||
import uuid
|
import uuid
|
||||||
@@ -594,6 +594,64 @@ def course_spline(coordinates):
|
|||||||
|
|
||||||
return newcoordinates
|
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):
|
def course_coord_center(course):
|
||||||
|
|
||||||
@@ -1590,6 +1648,24 @@ class GeoCourse(models.Model):
|
|||||||
def coord(self):
|
def coord(self):
|
||||||
return course_coord_center(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 GeoCourseEditForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
<p>CrewNerd has published a nice video tutorial of the process.
|
<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
|
<a href="https://youtu.be/whhWFmMJbhM">Click here</a> to see the video. The part
|
||||||
we're interested in starts at 2:05.
|
we're interested in starts at 2:05.
|
||||||
|
There is also a <a href="https://performancephones.com/custom-courses/">written tutorial</a> by CrewNerd.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ urlpatterns = [
|
|||||||
re_path(r'^api/TCX/workouts/$', views.strokedata_tcx,
|
re_path(r'^api/TCX/workouts/$', views.strokedata_tcx,
|
||||||
name='strokedata_tcx'),
|
name='strokedata_tcx'),
|
||||||
re_path(r'^api/courses/$', views.course_list, name='course_list'),
|
re_path(r'^api/courses/$', views.course_list, name='course_list'),
|
||||||
|
re_path(r'^api/courses/(?P<id>\d+)/$', views.get_crewnerd_kml, name='get_crewnerd_kml'),
|
||||||
re_path(r'^500v/$', views.error500_view, name='error500_view'),
|
re_path(r'^500v/$', views.error500_view, name='error500_view'),
|
||||||
re_path(r'^500q/$', views.servererror_view, name='servererror_view'),
|
re_path(r'^500q/$', views.servererror_view, name='servererror_view'),
|
||||||
path('502/', TemplateView.as_view(template_name='502.html'), name='502'),
|
path('502/', TemplateView.as_view(template_name='502.html'), name='502'),
|
||||||
|
|||||||
@@ -312,6 +312,31 @@ def geo_distance(lat1, lon1, lat2, lon2):
|
|||||||
|
|
||||||
return [distance, bearing]
|
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):
|
def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio):
|
||||||
pwr = abs(p0)/(1+(delta/abs(p2)))
|
pwr = abs(p0)/(1+(delta/abs(p2)))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from rowers.views.statements import *
|
from rowers.views.statements import *
|
||||||
from rowers.tasks import handle_calctrimp
|
from rowers.tasks import handle_calctrimp
|
||||||
from rowers.opaque import encoder
|
from rowers.opaque import encoder
|
||||||
|
from rowers.courses import coursetokml
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
import arrow
|
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.decorators import parser_classes
|
||||||
from rest_framework.parsers import BaseParser
|
from rest_framework.parsers import BaseParser
|
||||||
|
|
||||||
|
from rowers.utils import geo_distance
|
||||||
|
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|
||||||
import rowingdata.tcxtools as tcxtools
|
import rowingdata.tcxtools as tcxtools
|
||||||
@@ -252,9 +255,38 @@ Optional, not for CN
|
|||||||
def course_list(request):
|
def course_list(request):
|
||||||
if request.method != 'GET':
|
if request.method != 'GET':
|
||||||
dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method))
|
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 = []
|
courselist = []
|
||||||
for c in courses:
|
for c in courses:
|
||||||
d = {
|
d = {
|
||||||
@@ -269,10 +301,23 @@ def course_list(request):
|
|||||||
|
|
||||||
response_dict = {'courses': courselist}
|
response_dict = {'courses': courselist}
|
||||||
|
|
||||||
print(response_dict)
|
|
||||||
|
|
||||||
return JsonResponse(response_dict, content_type='application/json; charset=utf8')
|
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
|
# Stroke data views
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
|
|||||||
Reference in New Issue
Block a user