460 lines
13 KiB
Python
460 lines
13 KiB
Python
from rowers.courseutils import coursetime_paths, coursetime_first, time_in_path
|
|
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
|
|
)
|
|
from rowers.utils import geo_distance
|
|
import rowers.dataprep as dataprep
|
|
from timezonefinder import TimezoneFinder
|
|
import numpy as np
|
|
|
|
# All the Courses related methods
|
|
|
|
# Python
|
|
from django.utils import timezone
|
|
from datetime import datetime
|
|
from datetime import timedelta
|
|
import time
|
|
from django.db import IntegrityError
|
|
import uuid
|
|
from django.conf import settings
|
|
import math
|
|
|
|
import geocoder
|
|
|
|
# from matplotlib import path
|
|
import xml.etree.ElementTree as et
|
|
|
|
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
|
|
from xml.dom import minidom
|
|
|
|
from rowers.models import VirtualRace
|
|
|
|
# distance of course from lat_lon in km
|
|
|
|
def pass_start(df, course):
|
|
if df.empty:
|
|
return False
|
|
|
|
def f(x):
|
|
return coordinate_in_path(x[' latitude'], x[' longitude'], p)
|
|
|
|
|
|
polygons = course.polygons.all()
|
|
p = polygon_to_path(polygons[0])
|
|
|
|
inpolygon = df.apply(lambda row:f(row), axis=1).copy()
|
|
|
|
return inpolygon.any()
|
|
|
|
|
|
def howfaris(lat_lon, course):
|
|
coords = course.coord
|
|
try:
|
|
distance = geo_distance(lat_lon[0], lat_lon[1], coords[0], coords[1])[0]
|
|
except TypeError:
|
|
return 10000000
|
|
|
|
return distance
|
|
|
|
|
|
def getnearestraces(lat_lon, races, whatisnear=150):
|
|
newlist = []
|
|
counter = 0
|
|
for race in races:
|
|
if race.course is None: # pragma: no cover
|
|
newlist.append(race)
|
|
else:
|
|
c = race.course
|
|
distance = howfaris(lat_lon, c)
|
|
if distance < whatisnear:
|
|
newlist.append(race)
|
|
counter += 1
|
|
|
|
if counter > 0:
|
|
races = newlist
|
|
else:
|
|
courseraces = races.exclude(course__isnull=True)
|
|
orders = [(c.id, howfaris(lat_lon, c.course)) for c in courseraces]
|
|
orders = sorted(orders, key=lambda tup: tup[1])
|
|
ids = [id for id, distance in orders[0:4]]
|
|
for id, distance in orders[5:]: # pragma: no cover
|
|
if distance < whatisnear:
|
|
ids.append(id)
|
|
|
|
for id in ids:
|
|
newlist.append(VirtualRace.objects.get(id=id))
|
|
races = newlist
|
|
|
|
return races
|
|
|
|
|
|
def getnearestcourses(lat_lon, courses, whatisnear=150, strict=False):
|
|
|
|
newlist = []
|
|
counter = 0
|
|
for c in courses:
|
|
try:
|
|
distance = howfaris(lat_lon, c)
|
|
|
|
if distance < whatisnear:
|
|
newlist.append(c)
|
|
counter += 1
|
|
except TypeError:
|
|
pass
|
|
|
|
if counter > 0:
|
|
courses = newlist
|
|
elif strict:
|
|
courses = newlist
|
|
else:
|
|
orders = [(c.id, howfaris(lat_lon, c)) for c in courses]
|
|
orders = sorted(orders, key=lambda tup: tup[1])
|
|
ids = [id for id, distance in orders[0:4]]
|
|
for id, distance in orders[5:]:
|
|
if distance < whatisnear: # pragma: no cover
|
|
ids.append(id)
|
|
|
|
for id in ids:
|
|
newlist.append(GeoCourse.objects.get(id=id))
|
|
courses = newlist
|
|
|
|
return courses
|
|
|
|
|
|
def prettify(elem):
|
|
"""Return a pretty-printed XML string for the Element.
|
|
"""
|
|
rough_string = tostring(elem, 'utf-8')
|
|
reparsed = minidom.parseString(rough_string)
|
|
return reparsed.toprettyxml(indent=" ")
|
|
|
|
|
|
ns = {'opengis': 'http://www.opengis.net/kml/2.2'}
|
|
|
|
xmlns_uris_dict = {'gx': 'http://www.google.com/kml/ext/2.2',
|
|
'kml': 'http://www.google.com/kml/ext/2.2',
|
|
'atom': "http://www.w3.org/2005/Atom",
|
|
'': "http://www.opengis.net/kml/2.2"}
|
|
|
|
|
|
def get_polar_angle(point, reference_point):
|
|
"""
|
|
Calculate the polar angle of a point with respect to a reference point.
|
|
"""
|
|
try:
|
|
delta_x = point.longitude - reference_point.longitude
|
|
delta_y = point.latitude - reference_point.latitude
|
|
except AttributeError:
|
|
delta_x = point['longitude'] - reference_point.longitude
|
|
delta_y = point['latitude'] - reference_point.latitude
|
|
|
|
|
|
return math.atan2(delta_y, delta_x)
|
|
|
|
class Coordinate:
|
|
def __init__(self, latitude, longitude):
|
|
self.latitude = latitude
|
|
self.longitude = longitude
|
|
|
|
def get_coordinates_centerpoint(coordinates):
|
|
try:
|
|
centroid_latitude = sum(coord.latitude for coord in coordinates) / len(coordinates)
|
|
centroid_longitude = sum(coord.longitude for coord in coordinates) / len(coordinates)
|
|
centroid = Coordinate(centroid_latitude, centroid_longitude)
|
|
except AttributeError:
|
|
centroid_latitude = sum(coord['latitude'] for coord in coordinates) / len(coordinates)
|
|
centroid_longitude = sum(coord['longitude'] for coord in coordinates) / len(coordinates)
|
|
centroid = Coordinate(centroid_latitude, centroid_longitude)
|
|
|
|
return centroid
|
|
|
|
def sort_coordinates_ccw(coordinates):
|
|
"""
|
|
Sort coordinates in counterclockwise order.
|
|
"""
|
|
# Find the reference point (centroid)
|
|
centroid = get_coordinates_centerpoint(coordinates)
|
|
|
|
# Sort coordinates based on polar angle with respect to the centroid
|
|
sorted_coords = sorted(coordinates, key=lambda coord: get_polar_angle(coord, centroid))
|
|
|
|
return sorted_coords
|
|
|
|
|
|
def crewnerdcourse(doc):
|
|
courses = []
|
|
for course in doc:
|
|
try:
|
|
name = course.findall('.//opengis:name', ns)[0].text
|
|
except IndexError:
|
|
name = 'No name'
|
|
try:
|
|
description = course.findall('.//opengis:description', ns)[0].text
|
|
except IndexError:
|
|
description = ''
|
|
|
|
polygonpms = course.findall(
|
|
'.//opengis:Placemark[opengis:Polygon]', ns)
|
|
polygons = get_polygons(polygonpms)
|
|
|
|
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
|
|
coordinates = pm.findall('.//opengis:coordinates', ns)
|
|
if coordinates:
|
|
cc = coordinates[0].text
|
|
else: # pragma: no cover
|
|
cc = ''
|
|
|
|
pointstring = cc.split()
|
|
|
|
points = []
|
|
for s in pointstring:
|
|
coordinates = s.split(',')
|
|
points.append({
|
|
'longitude': float(coordinates[0]),
|
|
'latitude': float(coordinates[1]),
|
|
})
|
|
|
|
points = sort_coordinates_ccw(points)
|
|
|
|
polygons.append({
|
|
'name': name,
|
|
'points': points
|
|
})
|
|
|
|
return polygons
|
|
|
|
def crewnerdify(polygons):
|
|
polygon_names = []
|
|
polygon_names.append("Start")
|
|
for i in range(1,len(polygons)-1):
|
|
polygon_name = 'WP{n}'.format(n=polygons[i].order_in_course)
|
|
polygon_names.append(polygon_name)
|
|
|
|
polygon_names.append("Finish")
|
|
return polygon_names
|
|
|
|
def removewhitespace(s):
|
|
regels = s.split('\n')
|
|
regels = [regel for regel in regels if regel.strip()]
|
|
return "\n".join(regels)
|
|
|
|
def getcoursefolder(course, document, cn=False):
|
|
folder2 = SubElement(document, 'Folder', id="{id}".format(id=course.id))
|
|
coursename = SubElement(folder2, 'name')
|
|
coursename.text = course.name
|
|
openst = SubElement(folder2, 'open')
|
|
openst.text = '1'
|
|
coursedescription = SubElement(folder2, 'description')
|
|
if len(removewhitespace(course.notes)):
|
|
coursedescription.text = "Rowsandall.com\n" + removewhitespace(course.notes)
|
|
else:
|
|
coursedescription.text = "Rowsandall.com " + course.name
|
|
|
|
polygons = GeoPolygon.objects.filter(
|
|
course=course).order_by("order_in_course")
|
|
|
|
polygon_names = [polygon.name for polygon in polygons]
|
|
if cn:
|
|
polygon_names = crewnerdify(polygons)
|
|
|
|
for index, polygon in enumerate(polygons):
|
|
placemark = SubElement(folder2, 'Placemark')
|
|
polygonname = SubElement(placemark, 'name')
|
|
polygonname.text = polygon_names[index]
|
|
polygondescription = SubElement(placemark, 'description')
|
|
polygondescription.text = polygon.name
|
|
polygonstyle = SubElement(placemark, 'styleUrl')
|
|
polygonstyle.text = "#default0"
|
|
p = SubElement(placemark, 'Polygon')
|
|
tessellate = SubElement(p, 'tessellate')
|
|
tessellate.text = '1'
|
|
boundary = SubElement(p, 'outerBoundaryIs')
|
|
ring = SubElement(boundary, 'LinearRing')
|
|
coordinates = SubElement(ring, 'coordinates')
|
|
coordinates.text = ''
|
|
points = GeoPoint.objects.filter(
|
|
polygon=polygon).order_by("order_in_poly")
|
|
points = sort_coordinates_ccw(points)
|
|
for point in points:
|
|
coordinates.text += '{lon},{lat},0 '.format(
|
|
lat=point.latitude,
|
|
lon=point.longitude,
|
|
)
|
|
coordinates.text += '{lon},{lat},0'.format(
|
|
lat=points[0].latitude,
|
|
lon=points[0].longitude,
|
|
)
|
|
return folder2
|
|
|
|
def getstyle(document, id):
|
|
style = SubElement(document,'Style', id=id)
|
|
iconstyle = SubElement(style, 'IconStyle')
|
|
scale = SubElement(iconstyle, 'scale')
|
|
scale.text = "1.2"
|
|
linestyle = SubElement(style, 'LineStyle')
|
|
linestylecolor = SubElement(linestyle, 'color')
|
|
linestylecolor.text = "ff00ffff"
|
|
polystyle = SubElement(style, 'PolyStyle')
|
|
polystylecolor = SubElement(polystyle, 'color')
|
|
polystylecolor.text = "ff7fffff"
|
|
|
|
return style
|
|
|
|
def getstylemap(document, id):
|
|
stylemap = SubElement(document, 'StyleMap', id=id)
|
|
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"
|
|
|
|
return stylemap
|
|
|
|
def coursestokml(courseids, cn=False):
|
|
courses = GeoCourse.objects.filter(id__in=courseids)
|
|
top = Element('kml')
|
|
for prefix, uri in xmlns_uris_dict.items():
|
|
if prefix != '':
|
|
top.attrib['xmlns:' + prefix] = uri
|
|
else:
|
|
top.attrib['xmlns'] = uri
|
|
|
|
document = SubElement(top, 'Document')
|
|
name = SubElement(document, 'name')
|
|
name.text = 'courses'
|
|
|
|
opendoc = SubElement(document,'open')
|
|
opendoc.text = '1'
|
|
|
|
style = getstyle(document, "default")
|
|
|
|
stylemap = getstylemap(document, 'default0')
|
|
|
|
stylehl = getstyle(document, "hl")
|
|
|
|
for course in courses:
|
|
coursefolder = getcoursefolder(course, document, cn=cn)
|
|
|
|
return prettify(top)
|
|
|
|
def coursetokml(course,cn=False):
|
|
top = Element('kml')
|
|
for prefix, uri in xmlns_uris_dict.items():
|
|
if prefix != '':
|
|
top.attrib['xmlns:' + prefix] = uri
|
|
else:
|
|
top.attrib['xmlns'] = uri
|
|
|
|
document = SubElement(top, 'Document')
|
|
name = SubElement(document, 'name')
|
|
name.text = 'courses'
|
|
|
|
opendoc = SubElement(document,'open')
|
|
opendoc.text = '1'
|
|
|
|
style = getstyle(document, "default")
|
|
|
|
stylemap = getstylemap(document, 'default0')
|
|
|
|
stylehl = getstyle(document, "hl")
|
|
|
|
coursefolder = getcoursefolder(course, document, cn=cn)
|
|
|
|
|
|
return prettify(top)
|
|
|
|
|
|
def kmltocourse(f):
|
|
doc = et.parse(f)
|
|
|
|
courses = doc.findall('.//opengis:Folder[opengis:Placemark]', ns)
|
|
|
|
if not courses: # pragma: no cover
|
|
courses = doc.findall('.//opengis:Document[opengis:Placemark]', ns)
|
|
if not courses:
|
|
courses = doc.findall('.//opengis:Placemark', ns)
|
|
|
|
if courses:
|
|
return crewnerdcourse(courses)
|
|
|
|
polygonpms = doc.findall(
|
|
'.//opengis:Placemark[opengis:Polygon]', ns) # pragma: no cover
|
|
|
|
return get_polygons(polygonpms) # pragma: no cover
|
|
|
|
|
|
def createcourse(
|
|
manager, name, polygons, notes=''):
|
|
|
|
c = GeoCourse(manager=manager, name=name, notes=notes)
|
|
c.save()
|
|
|
|
i = 0
|
|
for p in polygons:
|
|
pp = GeoPolygon(course=c, order_in_course=i, name=p['name'])
|
|
pp.save()
|
|
j = 0
|
|
for point in p['points']:
|
|
if i == 0 and j == 0:
|
|
latitude = point['latitude']
|
|
longitude = point['longitude']
|
|
g = geocoder.arcgis([latitude, longitude], method='reverse')
|
|
if g.ok:
|
|
try:
|
|
country = g.json['country']
|
|
except KeyError: # pragma: no cover
|
|
country = 'unknown'
|
|
else: # pragma: no cover
|
|
country = 'unknown'
|
|
|
|
c.country = country
|
|
c.save()
|
|
obj = GeoPoint(
|
|
latitude=point['latitude'],
|
|
longitude=point['longitude'],
|
|
polygon=pp,
|
|
order_in_poly=j
|
|
)
|
|
obj.save()
|
|
j += 1
|
|
i += 1
|
|
|
|
c.distance = int(course_length(c))
|
|
c.save()
|
|
|
|
return c
|
|
|
|
|
|
|
|
def replacecourse(course1, course2):
|
|
ps = PlannedSession.objects.filter(course=course1)
|
|
for p in ps:
|
|
p.course = course2
|
|
p.save()
|
|
|
|
course1.delete()
|
|
|
|
return 1
|