Commit aa2304e6 authored by Clouard Regis's avatar Clouard Regis
Browse files

Added route statistics.

parent a17d2105
function addTimeSheet(pseudo, punchedPoints, gpxTrace) {
timeSheet.push([pseudo, punchedPoints, gpxTrace]);
function addTimeSheet(pseudo, punchedPoints, splitTimes, distance, pace) {
timeSheetData.push([pseudo, punchedPoints, splitTimes, distance, pace]);
showTimeSheet();
}
function removeTimeSheet(event, pseudo) {
timeSheet.forEach(function (orienteer, index) {
timeSheetData.forEach(function (orienteer, index) {
if (orienteer[0] === pseudo) {
timeSheet.splice(index, 1);
timeSheetData.splice(index, 1);
}
});
showTimeSheet();
}
function renderTimeSheet(distances) {
if (timeSheet.length === 0) {
if (timeSheetData.length === 0) {
return document.createElement("p");
}
let elTable = document.createElement("table");
elTable.setAttribute("class", "table");
let name = document.createElement("th");
name.setAttribute("scope", "col");
name.innerHTML = "Poste";
let row = document.createElement("tr");
row.appendChild(name);
let tableElement = document.createElement("table");
tableElement.setAttribute("class", "table");
// Head
let headRow1 = document.createElement("tr");
let cell = document.createElement("th");
cell.setAttribute("scope", "col");
headRow1.appendChild(cell);
cell = document.createElement("th");
cell.setAttribute("scope", "col");
headRow1.appendChild(cell);
let headRow2 = document.createElement("tr");
cell = document.createElement("th");
cell.setAttribute("scope", "col");
cell.innerHTML = "poste";
headRow2.appendChild(cell);
cell = document.createElement("th");
cell.setAttribute("scope", "col");
cell.innerHTML = "longueur";
headRow2.appendChild(cell);
let head = document.createElement("thead");
head.appendChild(row);
head.appendChild(headRow1);
head.appendChild(headRow2);
// Body
let body = document.createElement("tbody");
let rows = [];
let cps = timeSheet[0][1];
for ($i = 0; $i < cps.length; $i++) {
var punchedPoint = cps[$i]["controlPoint"];
console.log("ICI" + JSON.stringify(punchedPoint));
var index = cps[$i]["controlPoint"];
let name = document.createElement("th");
name.setAttribute("scope", "col");
let cps = timeSheetData[0][1];
for (let $i = 0; $i < cps.length; $i++) {
const index = cps[$i]["controlPoint"];
let cell1 = document.createElement("th");
let cell2 = document.createElement("td");
cell1.setAttribute("scope", "col");
if (index === 0) {
name.innerHTML = "Départ";
} else if (index === timeSheet[0][1].length - 1) {
name.innerHTML = "Arrivée" + " (" + Math.floor(distances[$i]) + "m)";
cell1.innerHTML = "Départ";
} else if (index === timeSheetData[0][1].length - 1) {
cell1.innerHTML = "Arrivée";
cell2.innerHTML = Math.floor(distances[$i]) + " m";
} else {
name.innerHTML = index + " (" + Math.floor(distances[$i]) + "m)";
cell1.innerHTML = index;
cell2.innerHTML = Math.floor(distances[$i]) + " m";
}
let row = document.createElement("tr");
row.appendChild(name);
row.appendChild(cell1);
row.appendChild(cell2);
body.appendChild(row);
rows.push(row);
}
timeSheet.forEach(function (orienteer) {
let name = document.createElement("th");
name.setAttribute("scope", "col");
name.innerHTML = orienteer[0];
row.appendChild(name);
orienteer[1].forEach(function (punchedPoint, index) {
let elControlPoint = document.createElement("th");
elControlPoint.setAttribute("scope", "col");
if (punchedPoint.punchTime === 0) {
elControlPoint.innerHTML = "---";
} else {
let lastTime = 0;
let lastIndex = index;
while (lastTime === 0 && lastIndex > 0) {
lastIndex--;
lastTime = orienteer[1][lastIndex].punchTime;
// Body tracks
timeSheetData.forEach(function (orienteerData) {
let cell = document.createElement("th");
cell.colSpan = 3;
cell.setAttribute("scope", "col");
cell.innerHTML = orienteerData[0];
headRow1.appendChild(cell);
cell = document.createElement("th");
cell.setAttribute("scope", "col");
cell.innerHTML = "temps";
headRow2.appendChild(cell);
cell = document.createElement("th");
cell.setAttribute("scope", "col");
cell.innerHTML = "distance";
headRow2.appendChild(cell);
cell = document.createElement("th");
cell.setAttribute("scope", "col");
cell.innerHTML = "RK";
headRow2.appendChild(cell);
for (let i = 0; i < orienteerData[1].length; i++) {
let index = i;
let punchedPoint = orienteerData[1][i];
let timeCell = document.createElement("th");
timeCell.style.background = "lightgray";
timeCell.setAttribute("scope", "col");
let distanceCell = document.createElement("td");
distanceCell.setAttribute("scope", "col");
let paceCell = document.createElement("td");
paceCell.setAttribute("scope", "col");
if (i > 0) {
timeCell.innerHTML = orienteerData[2][i];
distanceCell.innerHTML = orienteerData[3][i] + " m";
paceCell.innerHTML = orienteerData[4][i];
}
let time = punchedPoint.punchTime - lastTime;
let min = Math.floor(time / 1000 / 60);
let sec = Math.floor((time / 1000) % 60);
sec = sec.toString();
sec = (sec.length === 1 ? "0" + sec : sec);
elControlPoint.innerHTML = min + ":" + sec;
rows[index].appendChild(timeCell);
rows[index].appendChild(distanceCell);
rows[index].appendChild(paceCell);
}
rows[index].appendChild(elControlPoint);
});
});
elTable.appendChild(head);
elTable.appendChild(body);
}
);
tableElement.appendChild(head);
tableElement.appendChild(body);
return elTable;
return tableElement;
}
......@@ -116,7 +116,7 @@ function previewMap(data, divMap, divLeaflet, map, overlay, img) {
} else {
let error = document.createElement("p");
error.setAttribute("class", "alert alert-danger");
error.innerHTML = data.image;
error.innerHTML = "Erreur : impossible de trouver la carte : " + data.image;
divMap.appendChild(error);
img.width = 0;
img.height = 0;
......
......@@ -2,6 +2,7 @@
namespace App\Controller;
use App\Model\TrackStatistics;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
......@@ -15,7 +16,7 @@ class CourseController extends AbstractController
/**
* @Route("/routes/{lat}-{lng}", name="course", requirements={"lat"="{\-?\d+(\.\d+)?}", "lng"="{\-?\d+(\.\d+)?}"}, defaults={"lat": 10000, "lng": 1000})
*/
public function courses($lat, $lng)
public function courses($lat, $lng, TrackStatistics $statistics)
{
$latitude = (float)substr($lat, 1, strlen($lat) - 2);
$longitude = (float)substr($lng, 1, strlen($lng) - 2);
......@@ -23,7 +24,7 @@ class CourseController extends AbstractController
if ($lat < 10000 && $lng < 1000) {
$quantifiedCourses = [];
foreach ($allCourses as $course) {
$distance = $this->_distanceInMeters($latitude, $longitude, $course->getLatitude(), $course->getLongitude());
$distance = $statistics->distanceInMeters($latitude, $longitude, $course->getLatitude(), $course->getLongitude());
if ($distance < 100_000) {
$quantifiedCourses[] = ["distance" => $distance, "course" => $course];
}
......@@ -57,7 +58,7 @@ class CourseController extends AbstractController
/**
* @Route("/courses/{id}/tracks", name="show_tracks")
*/
public function track($id)
public function track($id, TrackStatistics $statistics)
{
$course = $this->getDoctrine()->getRepository(Course::class)->find($id);
$xml = $course->getXml();
......@@ -65,7 +66,7 @@ class CourseController extends AbstractController
$controls = $sxml->RaceCourseData->Control;
$locations = array(0);
for ($i = 1; $i < $controls->count(); $i++) {
$d = $this->_distanceInMeters($controls[$i - 1]->Position["lat"], $controls[$i - 1]->Position["lng"], $controls[$i]->Position["lat"], $controls[$i]->Position["lng"]);
$d = $statistics->distanceInMeters($controls[$i - 1]->Position["lat"], $controls[$i - 1]->Position["lng"], $controls[$i]->Position["lat"], $controls[$i]->Position["lng"]);
array_push($locations, $d);
}
......@@ -114,11 +115,14 @@ class CourseController extends AbstractController
* @Route("/courses/course_track/{id}", name="course_track")
* json data are not well send with render method, have to use xhr to access this function
*/
function course_track($id)
function course_track($id, TrackStatistics $statistics)
{
$tracks = $this->getDoctrine()->getRepository(Track::class)->findByCourse($id);
$routes = array();
foreach ($tracks as $track) {
$splitTimes = $statistics->computeSplitTime($track->getControlPoints());
$distances = $statistics->computeActualSplitDistance($track->getControlPoints(), $track->getTrace());
$rk = $statistics->computeSplitPaces($splitTimes, $distances);
array_push(
$routes,
[
......@@ -127,6 +131,9 @@ class CourseController extends AbstractController
$track->getTrace(),
$track->getControlPoints(),
$track->getFormat(),
$splitTimes,
$distances,
$rk,
]
);
}
......@@ -148,17 +155,4 @@ class CourseController extends AbstractController
]
);
}
private function _distanceInMeters($lat1, $lng1, $lat2, $lng2)
{
$rad = M_PI / 180;
$lat1r = $lat1 * $rad;
$lat2r = $lat2 * $rad;
$sinDLat = sin(($lat2 - $lat1) * $rad / 2);
$sinDLon = sin(($lng2 - $lng1) * $rad / 2);
$a = $sinDLat * $sinDLat + cos($lat1r) * cos($lat2r) * $sinDLon * $sinDLon;
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
return 6371000 * $c;
}
}
......@@ -123,4 +123,7 @@ class Track
return $this;
}
public function getActualDistance(): string
{ return "toto"; }
}
......@@ -307,6 +307,4 @@ class CreateCourse
return $this->translator->trans("error.unknown");
}
}
}
?>
\ No newline at end of file
}
\ No newline at end of file
<?php
namespace App\Model;
class TrackStatistics
{
public function distanceInMeters($lat1, $lng1, $lat2, $lng2)
{
$rad = M_PI / 180;
$lat1r = $lat1 * $rad;
$lat2r = $lat2 * $rad;
$sinDLat = sin(($lat2 - $lat1) * $rad / 2);
$sinDLon = sin(($lng2 - $lng1) * $rad / 2);
$a = $sinDLat * $sinDLat + cos($lat1r) * cos($lat2r) * $sinDLon * $sinDLon;
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
return 6371000 * $c;
}
public function computeSplitTime(array $controlPoints)
{
$splitTimes = array();
$splitTimes[] = "";
for ($i = 1; $i < count($controlPoints); $i++) {
$lastTime = 0;
$lastIndex = $i;
while ($lastTime === 0 && $lastIndex > 0) {
$lastIndex--;
$lastTime = $controlPoints[$lastIndex]->punchTime;
}
$time = $controlPoints[$i]->punchTime - $lastTime;
$splitTimes[] = $this->convertTimetoString($time);
}
return $splitTimes;
}
public function computeActualSplitDistance(array $controlPoints, string $trace)
{
$distances = array();
$distances[] = "";
$wayPoints = $this->parseXMLAsWayPoints($trace);
$distance = 0;
$currentLeg = 1;
$previousWayPoint = $wayPoints[0];
foreach ($wayPoints as $wayPoint) {
$nextLegTimeInMillis = $controlPoints[$currentLeg]->punchTime;
$currentWayPointTimeInMillis = $wayPoint[2];
$distance += $this->distanceInMeters($previousWayPoint[0], $previousWayPoint[1], $wayPoint[0], $wayPoint[1]);
$previousWayPoint = $wayPoint;
if ($currentWayPointTimeInMillis >= $nextLegTimeInMillis) {
$currentLeg++;
$distances[] = (int)$distance;
$distance = 0;
}
}
// TODO complete with 0???
for ($i=count($distances) ; $i < count($controlPoints); $i++) {
$distances[] = 0;
}
return $distances;
}
public function computeSplitPaces(array $splitTimes, array $distances)
{
$paces = array();
$paces[] = "";
for ($i = 1; $i < count($splitTimes); $i++) {
if (is_numeric($distances[$i]) && intval($distances[$i]) > 0) {
$time = $this->convertStringToTimeInMs($splitTimes[$i]);
$distance = intval($distances[$i]);
$pace = $this->convertTimetoString($time * 1000 / $distance);
$paces[] = $pace;
} else {
$paces[] = "--:--";
}
}
return $paces;
}
private function convertTimetoString(int $time): string
{
$hrs = floor($time / 1000 / 3600);
$min = floor($time / 1000 / 60);
$sec = floor(($time / 1000) % 60);
$sec = (string)$sec;
$sec = strlen($sec) === 1 ? "0".$sec : $sec;
if ($hrs > 0) {
return $hrs.":".$min.":".$sec;
} else {
return $min.":".$sec;
}
}
private function convertStringToTimeInMs(string $formattedTime): int
{
list($min, $sec) = sscanf($formattedTime, "%d:%d");
return ($min * 60 + $sec) * 1000;
}
// <trk><trkseg><trkpt lat=\"49.285667\" lon=\"-0.538659\"><time>1970-01-01T00:00:00.005Z</time></trkpt>
private function parseXMLAsWayPoints(string $trace): array
{
$wayPoints = array();
$xmlData = simplexml_load_string($trace);
foreach ($xmlData->trk->trkseg->children() as $trkpt) {
$timeInMs = $this->convertDateToMilliseconds($trkpt->time);
$lat = $trkpt["lat"];
$lon = $trkpt["lon"];
array_push($wayPoints, [$lat, $lon, $timeInMs]);
}
return $wayPoints;
}
private function convertDateToMilliseconds(string $formattedTime): int
{
preg_match('/^(.+)\.(\d+)Z$/i', $formattedTime, $matches);
$milliseconds = $matches[2];
$timeInMs = strtotime($formattedTime);
return $timeInMs * 1000 + $milliseconds;
}
}
\ No newline at end of file
......@@ -30,33 +30,4 @@ class TrackRepository extends ServiceEntityRepository
->getQuery()
->getResult();
}
// /**
// * @return Track[] Returns an array of Track objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('t')
->andWhere('t.exampleField = :val')
->setParameter('val', $value)
->orderBy('t.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?Track
{
return $this->createQueryBuilder('t')
->andWhere('t.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}
......@@ -20,6 +20,9 @@
cursor: pointer;
color: var(--primary);
}
.table th, td {
text-align: center;
}
</style>
{% endblock %}
......@@ -37,11 +40,11 @@
<script>
const colorPicker = new ColorPicker();
const tableEnable = false;
const gpxDictionary = {};
const buttonDictionary = {};
let divLeaflet, map, overlay;
let timeSheet = [];
var tableEnable = false;
var gpxDictionary = {};
var buttonDictionary = {};
let timeSheetData = [];
function showMap() {
let xhr = new XMLHttpRequest();
......@@ -54,11 +57,11 @@
}
function showTimeSheet() {
let divTable = document.getElementById("time-sheet-div");
const divTable = document.getElementById("time-sheet-div");
while (divTable.lastElementChild) {
divTable.removeChild(divTable.lastElementChild);
}
var controlPointData = JSON.parse({{ control_points_location|json_encode|raw }});
const controlPointData = JSON.parse({{ control_points_location|json_encode|raw }});
divTable.appendChild(renderTimeSheet(controlPointData["distances"]));
}
......@@ -88,6 +91,10 @@
let gpxTrace = result[2];
let punchedPoints = result[3];
let courseFormat = result[4];
let splitTimes = result[5];
let distances = result[6];
let paces = result[7];
let trackButton = document.createElement("button");
trackButton.className = "route";
let seconds = Math.floor((totalTimeInMs / 1000) % 60);
......@@ -99,12 +106,12 @@
if (courseFormat > 0) {
trackButton.innerHTML = trackButton.innerHTML + "*";
}
trackButton.value = index++;
trackButton.value = String(index++);
trackButton.addEventListener("click", function (event) {
var element = event.target;
const element = event.target;
if (!gpxDictionary[element.value]) {
gpxDictionary[element.value] = addTrack(event, map, gpxTrace);
addTimeSheet(element.innerHTML, punchedPoints, gpxTrace);
addTimeSheet(element.innerHTML, punchedPoints, splitTimes, distances, paces);
buttonDictionary[element.value] = addDownloadButton(event, element.innerHTML, gpxTrace, document.getElementById("files-tab"));
} else {
let gpxLayer = gpxDictionary[element.value];
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment