root->getUserFolder($userId); $tracks = $this->gatherTrackFiles($userFolder, true); $this->deleteAllTracksFromDB($userId); foreach ($tracks as $track) { $this->addTrackToDB($userId, $track->getId(), $track); yield $track->getPath(); } } public function addByFile(Node $file) { $userFolder = $this->root->getUserFolder($file->getOwner()->getUID()); if ($this->isTrack($file)) { $this->addTrackToDB($file->getOwner()->getUID(), $file->getId(), $file); } } // add the file for its owner and users that have access // check if it's already in DB before adding public function safeAddByFile(Node $file) { $ownerId = $file->getOwner()->getUID(); $userFolder = $this->root->getUserFolder($ownerId); if ($this->isTrack($file)) { $this->safeAddTrack($file, $ownerId); // is the file accessible to other users ? $accesses = $this->shareManager->getAccessList($file); foreach ($accesses['users'] as $uid) { if ($uid !== $ownerId) { $this->safeAddTrack($file, $uid); } } return true; } else { return false; } } public function safeAddByFileIdUserId($fileId, $userId) { $userFolder = $this->root->getUserFolder($userId); $files = $userFolder->getById($fileId); if (empty($files)) { return; } $file = array_shift($files); if ($file !== null and $this->isTrack($file)) { $this->safeAddTrack($file, $userId); } } public function safeAddByFolderIdUserId($folderId, $userId) { $folders = $this->root->getById($folderId); if (empty($folders)) { return; } $folder = array_shift($folders); if ($folder !== null) { $tracks = $this->gatherTrackFiles($folder, true); foreach ($tracks as $track) { $this->safeAddTrack($track, $userId); } } } // avoid adding track if it already exists in the DB private function safeAddTrack($track, $userId) { // filehooks are triggered several times (2 times for file creation) // so we need to be sure it's not inserted several times // by checking if it already exists in DB // OR by using file_id in primary key if ($this->getTrackByFileIDFromDB($track->getId(), $userId) === null) { $this->addTrackToDB($userId, $track->getId(), $track); } } // add all tracks of a folder taking care of shared accesses public function safeAddByFolder($folder) { $tracks = $this->gatherTrackFiles($folder, true); foreach ($tracks as $track) { $this->safeAddByFile($track); } } public function addByFolder(Node $folder) { $tracks = $this->gatherTrackFiles($folder, true); foreach ($tracks as $track) { $this->addTrackToDB($folder->getOwner()->getUID(), $track->getId(), $track); } } // delete track only if it's not accessible to user anymore // it might have been shared multiple times by different users public function safeDeleteByFileIdUserId($fileId, $userId) { $userFolder = $this->root->getUserFolder($userId); $files = $userFolder->getById($fileId); if (!is_array($files) or count($files) === 0) { $this->deleteByFileIdUserId($fileId, $userId); } } public function deleteByFile(Node $file) { $this->deleteByFileId($file->getId()); } public function deleteByFolder(Node $folder) { $tracks = $this->gatherTrackFiles($folder, true); foreach ($tracks as $track) { $this->deleteByFileId($track->getId()); } } // delete folder tracks only if it's not accessible to user anymore public function safeDeleteByFolderIdUserId($folderId, $userId) { $userFolder = $this->root->getUserFolder($userId); $folders = $userFolder->getById($folderId); if (is_array($folders) and count($folders) === 1) { $folder = array_shift($folders); $tracks = $this->gatherTrackFiles($folder, true); foreach ($tracks as $track) { $this->deleteByFileIdUserId($track->getId(), $userId); } } } private function gatherTrackFiles($folder, $recursive) { $notes = []; $nodes = $folder->getDirectoryListing(); foreach ($nodes as $node) { if ($node->getType() === FileInfo::TYPE_FOLDER and $recursive) { try { $notes = array_merge($notes, $this->gatherTrackFiles($node, $recursive)); } catch (\OCP\Files\StorageNotAvailableException|\Exception $e) { $msg = 'WARNING: Could not access ' . $node->getName(); echo($msg . "\n"); $this->logger->error($msg); } continue; } if ($this->isTrack($node)) { $notes[] = $node; } } return $notes; } private function isTrack($file) { if ($file->getType() !== \OCP\Files\FileInfo::TYPE_FILE) { return false; } if (!in_array($file->getMimetype(), self::TRACK_MIME_TYPES)) { return false; } return true; } private function dbRowToTrack($row, $folder, $userFolder, $defaultMap, $ignoredPaths) { // avoid tracks that are not in "this map's" folder $files = $folder->getById(intval($row['file_id'])); if (empty($files)) { if ($defaultMap) { $this->deleteTrackFromDB($row['id']); } return null; } $file = array_shift($files); if ($file === null || $file->getType() !== \OCP\Files\FileInfo::TYPE_FILE) { if ($defaultMap) { $this->deleteTrackFromDB($row['id']); } return null; } $path = $userFolder->getRelativePath($file->getPath()); $isIgnored = false; foreach ($ignoredPaths as $ignoredPath) { if (str_starts_with($path, $ignoredPath)) { $isIgnored = true; break; } } if ($isIgnored) { return null; } return [ 'id' => intval($row['id']), 'file_id' => intval($row['file_id']), 'color' => $row['color'], 'metadata' => $row['metadata'], 'etag' => $row['etag'], 'path' => $path, 'isShareable' => $file->isShareable(), 'isDeletable' => $file->isDeletable(), 'isUpdateable' => $file->isUpdateable(), 'isReadable' => $file->isReadable(), 'mtime' => $file->getMTime(), 'file_name' => $file->getName(), 'file_path' => $userFolder->getRelativePath($file->getPath()), ]; } /** * @param string $userId */ public function getTracksFromDB($userId, $folder = null, bool $respectNomediaAndNoimage = true, bool $hideTracksOnCustomMaps = false, bool $hideTracksInMapsFolder = true) { $ignoredPaths = $respectNomediaAndNoimage ? $this->getIgnoredPaths($userId, $folder, $hideTracksOnCustomMaps) : []; if ($hideTracksInMapsFolder) { $ignoredPaths[] = '/Maps'; } $userFolder = $this->root->getUserFolder($userId); $tracks = []; $qb = $this->dbconnection->getQueryBuilder(); $qb->select('id', 'file_id', 'color', 'metadata', 'etag') ->from('maps_tracks', 't') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); $req = $qb->executeQuery(); if (is_null($folder)) { $folder = $userFolder; } $defaultMap = $folder->getId() === $userFolder->getId(); // my-maps context while ($row = $req->fetch()) { $track = $this->dbRowToTrack($row, $folder, $userFolder, $defaultMap, $ignoredPaths); if (is_null($track)) { continue; } $tracks[] = $track; } $req->closeCursor(); return $tracks; } /** * @param $userId * @param $folder * @return array * @throws \OCP\Files\NotFoundException * @throws \OCP\Files\NotPermittedException * @throws \OC\User\NoUserException */ private function getIgnoredPaths($userId, $folder = null, $hideImagesOnCustomMaps = true) { $ignoredPaths = []; $userFolder = $this->root->getUserFolder($userId); if (is_null($folder)) { $folder = $userFolder; } $ignoreFileMimetypes = [ 'application/x-nextcloud-noindex', 'application/x-nextcloud-nomedia', 'application/x-nextcloud-notrack', ]; if ($hideImagesOnCustomMaps) { $ignoreFileMimetypes[] = 'application/x-nextcloud-maps'; } $func = function (string $i): SearchComparison { return new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $i); }; $excludedNodes = $folder->search(new SearchQuery( new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, array_map( $func, $ignoreFileMimetypes) ), 0, 0, [] )); foreach ($excludedNodes as $node) { $ignoredPaths[] = $userFolder->getRelativePath($node->getParent()->getPath()); } return $ignoredPaths; } public function getTrackFromDB($id, $userId = null) { $track = null; $qb = $this->dbconnection->getQueryBuilder(); $qb->select('id', 'file_id', 'color', 'metadata', 'etag') ->from('maps_tracks', 't') ->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) ); if ($userId !== null) { $qb->andWhere( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); } $req = $qb->executeQuery(); while ($row = $req->fetch()) { if ($userId !== '' and $userId !== null) { $userFolder = $this->root->getUserFolder($userId); $files = $userFolder->getById(intval($row['file_id'])); if (empty($files)) { break; } $file = array_shift($files); if ($file === null) { break; } $track = $this->dbRowToTrack($row, $userFolder, $userFolder, true, []); } else { $track = [ 'id' => intval($row['id']), 'file_id' => intval($row['file_id']), 'color' => $row['color'], 'metadata' => $row['metadata'], 'etag' => $row['etag'], 'path' => '', 'isShareable' => false, 'isDeletable' => false, 'isUpdateable' => false, 'isReadable' => false, 'mtime' => 0, 'file_name' => '', 'file_path' => '', ]; } break; } $req->closeCursor(); return $track; } public function getTrackByFileIDFromDB(int $fileId, ?string $userId = null) { $track = null; $qb = $this->dbconnection->getQueryBuilder(); $qb->select('id', 'file_id', 'color', 'metadata', 'etag') ->from('maps_tracks', 't') ->where( $qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)) ); if ($userId !== null) { $qb->andWhere( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); } $req = $qb->executeQuery(); while ($row = $req->fetch()) { if ($userId !== '' and $userId !== null) { $userFolder = $this->root->getUserFolder($userId); $files = $userFolder->getById(intval($row['file_id'])); if (empty($files)) { break; } $file = array_shift($files); if ($file === null) { break; } $track = $this->dbRowToTrack($row, $userFolder, $userFolder, true, []); } else { $track = [ 'id' => intval($row['id']), 'file_id' => intval($row['file_id']), 'color' => $row['color'], 'metadata' => $row['metadata'], 'etag' => $row['etag'], 'path' => '', 'isShareable' => false, 'isDeletable' => false, 'isUpdateable' => false, 'isReadable' => false, 'mtime' => 0, 'file_name' => '', 'file_path' => '', ]; } break; } $req->closeCursor(); return $track; } public function addTrackToDB($userId, $fileId, $file) { $metadata = ''; $etag = $file->getEtag(); $qb = $this->dbconnection->getQueryBuilder(); $qb->insert('maps_tracks') ->values([ 'user_id' => $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR), 'file_id' => $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT), 'metadata' => $qb->createNamedParameter($metadata, IQueryBuilder::PARAM_STR), 'etag' => $qb->createNamedParameter($etag, IQueryBuilder::PARAM_STR) ]); $qb->executeStatement(); $trackId = $qb->getLastInsertId(); return $trackId; } public function editTrackInDB($id, $color, $metadata, $etag): void { $qb = $this->dbconnection->getQueryBuilder(); $qb->update('maps_tracks'); if ($color !== null) { $qb->set('color', $qb->createNamedParameter($color, IQueryBuilder::PARAM_STR)); } if ($metadata !== null) { $qb->set('metadata', $qb->createNamedParameter($metadata, IQueryBuilder::PARAM_STR)); } if ($etag !== null) { $qb->set('etag', $qb->createNamedParameter($etag, IQueryBuilder::PARAM_STR)); } $qb->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) ); $qb->executeStatement(); } public function deleteByFileId(int $fileId): void { $qb = $this->dbconnection->getQueryBuilder(); $qb->delete('maps_tracks') ->where( $qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)) ); $qb->executeStatement(); } public function deleteByFileIdUserId(int $fileId, string $userId): void { $qb = $this->dbconnection->getQueryBuilder(); $qb->delete('maps_tracks') ->where( $qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)) ) ->andWhere( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); $qb->executeStatement(); } public function deleteTrackFromDB(int $id): void { $qb = $this->dbconnection->getQueryBuilder(); $qb->delete('maps_tracks') ->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) ); $qb->executeStatement(); } public function deleteAllTracksFromDB(string $userId): void { $qb = $this->dbconnection->getQueryBuilder(); $qb->delete('maps_tracks') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ); $qb->executeStatement(); } public function deleteTracksFromDB(array $ids, string $userId): void { if (empty($ids)) { return; } $qb = $this->dbconnection->getQueryBuilder(); $qb->delete('maps_tracks') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) ) ->andWhere($qb->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); $qb->executeStatement(); } public function generateTrackMetadata($file) { $DISTANCE_BETWEEN_SHORT_POINTS = 300; $STOPPED_SPEED_THRESHOLD = 0.9; $name = $file->getName(); $gpx_content = $file->getContent(); $lat = '0'; $lon = '0'; $total_distance = 0; $total_duration = 0; $date_begin = null; $date_end = null; $distAccCumulEle = 0; $pos_elevation = 0; $neg_elevation = 0; $min_elevation = null; $max_elevation = null; $avg_speed = '???'; $moving_time = 0; $moving_distance = 0; $stopped_distance = 0; $moving_max_speed = 0; $moving_avg_speed = 0; $stopped_time = 0; $north = null; $south = null; $east = null; $west = null; $trackNameList = []; $linkurl = ''; $linktext = ''; $pointsBySegment = []; $lastTime = null; try { $gpx = new \SimpleXMLElement($gpx_content); } catch (\Throwable $e) { $this->logger->error( 'Exception in ' . $name . ' gpx parsing : ' . $e->getMessage(), ['app' => 'maps'] ); return null; } if (count($gpx->trk) === 0 and count($gpx->rte) === 0 and count($gpx->wpt) === 0) { $this->logger->error( 'Nothing to parse in ' . $name . ' gpx file', ['app' => 'maps'] ); return null; } // METADATA if (!empty($gpx->metadata) and !empty($gpx->metadata->link)) { $linkurl = $gpx->metadata->link['href']; if (!empty($gpx->metadata->link->text)) { $linktext = $gpx->metadata->link->text; } } // TRACKS foreach ($gpx->trk as $track) { $trackname = str_replace("\n", '', $track->name); if (empty($trackname)) { $trackname = ''; } $trackname = str_replace('"', "'", $trackname); $trackNameList[] = sprintf('"%s"', $trackname); foreach ($track->trkseg as $segment) { $lastPoint = null; $lastTime = null; $pointIndex = 0; $pointsBySegment[] = $segment->trkpt; foreach ($segment->trkpt as $point) { if (empty($point['lat']) or empty($point['lon'])) { continue; } if (empty($point->ele)) { $pointele = null; } else { $pointele = floatval($point->ele); } if (empty($point->time)) { $pointtime = null; } else { $pointtime = new \DateTime($point->time); } if ($lastPoint !== null and (!empty($lastPoint->ele))) { $lastPointele = floatval($lastPoint->ele); } else { $lastPointele = null; } if ($lastPoint !== null and (!empty($lastPoint->time))) { $lastTime = new \DateTime($lastPoint->time); } else { $lastTime = null; } if ($lastPoint !== null) { $distToLast = distance($lastPoint, $point); } else { $distToLast = null; } $pointlat = floatval($point['lat']); $pointlon = floatval($point['lon']); if ($pointIndex === 0) { if ($lat === '0' and $lon === '0') { $lat = $pointlat; $lon = $pointlon; } if ($pointtime !== null and ($date_begin === null or $pointtime < $date_begin)) { $date_begin = $pointtime; } $downBegin = $pointele; if ($north === null) { $north = $pointlat; $south = $pointlat; $east = $pointlon; $west = $pointlon; } } if ($pointlat > $north) { $north = $pointlat; } if ($pointlat < $south) { $south = $pointlat; } if ($pointlon > $east) { $east = $pointlon; } if ($pointlon < $west) { $west = $pointlon; } if ($pointele !== null and ($min_elevation === null or $pointele < $min_elevation)) { $min_elevation = $pointele; } if ($pointele !== null and ($max_elevation === null or $pointele > $max_elevation)) { $max_elevation = $pointele; } if ($lastPoint !== null and $pointtime !== null and $lastTime !== null) { $t = abs($lastTime->getTimestamp() - $pointtime->getTimestamp()); $speed = 0; if ($t > 0) { $speed = $distToLast / $t; $speed = $speed / 1000; $speed = $speed * 3600; } if ($speed <= $STOPPED_SPEED_THRESHOLD) { $stopped_time += $t; $stopped_distance += $distToLast; } else { $moving_time += $t; $moving_distance += $distToLast; } } if ($lastPoint !== null) { $total_distance += $distToLast; } $lastPoint = $point; $pointIndex += 1; } if ($lastTime !== null and ($date_end === null or $lastTime > $date_end)) { $date_end = $lastTime; } } } # ROUTES foreach ($gpx->rte as $route) { $routename = str_replace("\n", '', $route->name); if (empty($routename)) { $routename = ''; } $routename = str_replace('"', "'", $routename); $trackNameList[] = sprintf('"%s"', $routename); $lastPoint = null; $lastTime = null; $pointIndex = 0; $pointsBySegment[] = $route->rtept; foreach ($route->rtept as $point) { if (empty($point['lat']) or empty($point['lon'])) { continue; } if (empty($point->ele)) { $pointele = null; } else { $pointele = floatval($point->ele); } if (empty($point->time)) { $pointtime = null; } else { $pointtime = new \DateTime($point->time); } if ($lastPoint !== null and (!empty($lastPoint->ele))) { $lastPointele = floatval($lastPoint->ele); } else { $lastPointele = null; } if ($lastPoint !== null and (!empty($lastPoint->time))) { $lastTime = new \DateTime($lastPoint->time); } else { $lastTime = null; } if ($lastPoint !== null) { $distToLast = distance($lastPoint, $point); } else { $distToLast = null; } $pointlat = floatval($point['lat']); $pointlon = floatval($point['lon']); if ($pointIndex === 0) { if ($lat === '0' and $lon === '0') { $lat = $pointlat; $lon = $pointlon; } if ($pointtime !== null and ($date_begin === null or $pointtime < $date_begin)) { $date_begin = $pointtime; } $downBegin = $pointele; if ($north === null) { $north = $pointlat; $south = $pointlat; $east = $pointlon; $west = $pointlon; } } if ($pointlat > $north) { $north = $pointlat; } if ($pointlat < $south) { $south = $pointlat; } if ($pointlon > $east) { $east = $pointlon; } if ($pointlon < $west) { $west = $pointlon; } if ($pointele !== null and ($min_elevation === null or $pointele < $min_elevation)) { $min_elevation = $pointele; } if ($pointele !== null and ($max_elevation === null or $pointele > $max_elevation)) { $max_elevation = $pointele; } if ($lastPoint !== null and $pointtime !== null and $lastTime !== null) { $t = abs($lastTime->getTimestamp() - $pointtime->getTimestamp()); $speed = 0; if ($t > 0) { $speed = $distToLast / $t; $speed = $speed / 1000; $speed = $speed * 3600; } if ($speed <= $STOPPED_SPEED_THRESHOLD) { $stopped_time += $t; $stopped_distance += $distToLast; } else { $moving_time += $t; $moving_distance += $distToLast; } } if ($lastPoint !== null) { $total_distance += $distToLast; } $lastPoint = $point; $pointIndex += 1; } if ($lastTime !== null and ($date_end === null or $lastTime > $date_end)) { $date_end = $lastTime; } } # TOTAL STATS : duration, avg speed, avg_moving_speed if ($date_end !== null and $date_begin !== null) { $totsec = abs($date_end->getTimestamp() - $date_begin->getTimestamp()); $total_duration = $totsec; if ($totsec === 0) { $avg_speed = 0; } else { $avg_speed = $total_distance / $totsec; $avg_speed = $avg_speed / 1000; $avg_speed = $avg_speed * 3600; $avg_speed = sprintf('%.2f', $avg_speed); } } // determination of real moving average speed from moving time $moving_avg_speed = 0; $moving_pace = 0; if ($moving_time > 0) { $moving_avg_speed = $total_distance / $moving_time; $moving_avg_speed = $moving_avg_speed / 1000; $moving_avg_speed = $moving_avg_speed * 3600; $moving_avg_speed = sprintf('%.2f', $moving_avg_speed); // pace in minutes/km $moving_pace = $moving_time / $total_distance; $moving_pace = $moving_pace / 60; $moving_pace = $moving_pace * 1000; $moving_pace = sprintf('%.2f', $moving_pace); } # WAYPOINTS foreach ($gpx->wpt as $waypoint) { $waypointlat = floatval($waypoint['lat']); $waypointlon = floatval($waypoint['lon']); if ($lat === '0' and $lon === '0') { $lat = $waypointlat; $lon = $waypointlon; } if ($north === null or $waypointlat > $north) { $north = $waypointlat; } if ($south === null or $waypointlat < $south) { $south = $waypointlat; } if ($east === null or $waypointlon > $east) { $east = $waypointlon; } if ($west === null or $waypointlon < $west) { $west = $waypointlon; } } $trackNameList = '[' . implode(',', $trackNameList) . ']'; if ($north === null) { $north = 0; } if ($south === null) { $south = 0; } if ($east === null) { $east = 0; } if ($west === null) { $west = 0; } // we filter all segments by distance $distFilteredPointsBySegment = []; foreach ($pointsBySegment as $points) { $distFilteredPointsBySegment[] = $this->getDistanceFilteredPoints($points); } // and we get points with elevation and time for each segment $pointsWithElevationBySegment = []; $pointsWithTimeBySegment = []; foreach ($distFilteredPointsBySegment as $points) { $pointsWithTimeOneSegment = []; $pointsWithElevationOneSegment = []; foreach ($points as $point) { if (!empty($point->ele)) { $pointsWithElevationOneSegment[] = $point; } if (!empty($point->time)) { $pointsWithTimeOneSegment[] = $point; } } $pointsWithElevationBySegment[] = $pointsWithElevationOneSegment; $pointsWithTimeBySegment[] = $pointsWithTimeOneSegment; } // process elevation gain/loss $pos_elevation = 0; $neg_elevation = 0; foreach ($pointsWithElevationBySegment as $points) { $gainLoss = $this->getElevationGainLoss($points); $pos_elevation += $gainLoss[0]; $neg_elevation += $gainLoss[1]; } $pos_elevation = number_format($pos_elevation, 2, '.', ''); $neg_elevation = number_format($neg_elevation, 2, '.', ''); // process max speed from distance filtered points $maxSpeed = 0; foreach ($pointsWithTimeBySegment as $points) { $segmentMaxSpeed = $this->getMaxSpeed($points); if ($segmentMaxSpeed > $maxSpeed) { $maxSpeed = $segmentMaxSpeed; } } $result = sprintf('{"lat":%s, "lng":%s, "name": "%s", "distance": %.3f, "duration": %d, "begin": %d, "end": %d, "posel": %.2f, "negel": %.2f, "minel": %.2f, "maxel": %.2f, "maxspd": %.2f, "avgspd": %.2f, "movtime": %d, "stptime": %d, "movavgspd": %s, "n": %.8f, "s": %.8f, "e": %.8f, "w": %.8f, "trnl": %s, "lnkurl": "%s", "lnktxt": "%s", "movpace": %.2f}', $lat, $lon, str_replace('"', "'", $name), $total_distance, $total_duration, ($date_begin !== null) ? $date_begin->getTimestamp() : -1, ($date_end !== null) ? $date_end->getTimestamp() : -1, $pos_elevation, $neg_elevation, ($min_elevation !== null) ? $min_elevation : -1000, ($max_elevation !== null) ? $max_elevation : -1000, $maxSpeed, $avg_speed, $moving_time, $stopped_time, $moving_avg_speed, $north, $south, $east, $west, (strlen($trackNameList) < 200) ? $trackNameList : '[]', str_replace('"', "'", $linkurl), str_replace('"', "'", $linktext), $moving_pace ); return $result; } private function getDistanceFilteredPoints($points) { $DISTANCE_THRESHOLD = 10; $distFilteredPoints = []; if (count($points) > 0) { array_push($distFilteredPoints, $points[0]); $lastPoint = $points[0]; foreach ($points as $point) { if (distance($lastPoint, $point) >= $DISTANCE_THRESHOLD) { array_push($distFilteredPoints, $point); $lastPoint = $point; } } } return $distFilteredPoints; } private function getMaxSpeed($points) { $maxSpeed = 0; if (count($points) > 0) { $lastPoint = $points[0]; $lastTime = new \DateTime($lastPoint->time); foreach ($points as $point) { $time = new \DateTime($point->time); $timeDelta = abs($lastTime->getTimestamp() - $time->getTimestamp()); if ($timeDelta > 0) { $distance = distance($point, $lastPoint); $speed = $distance / $timeDelta; $speed = $speed / 1000; $speed = $speed * 3600; if ($speed > $maxSpeed) { $maxSpeed = $speed; } } $lastTime = $time; $lastPoint = $point; } } return $maxSpeed; } /** * inspired by https://www.gpsvisualizer.com/tutorials/elevation_gain.html */ private function getElevationGainLoss($points) { $ELEVATION_THRESHOLD = 6; $gain = 0; $loss = 0; // then calculate elevation gain with elevation threshold if (count($points) > 0) { $validPoint = $points[0]; foreach ($points as $point) { $deniv = floatval($point->ele) - floatval($validPoint->ele); if ($deniv >= $ELEVATION_THRESHOLD) { $gain += $deniv; $validPoint = $point; } elseif (-$deniv >= $ELEVATION_THRESHOLD) { $loss -= $deniv; $validPoint = $point; } } } return [$gain, $loss]; } } /* * return distance between these two gpx points in meters */ function distance($p1, $p2) { $lat1 = (float)$p1['lat']; $long1 = (float)$p1['lon']; $lat2 = (float)$p2['lat']; $long2 = (float)$p2['lon']; if ($lat1 === $lat2 and $long1 === $long2) { return 0; } // Convert latitude and longitude to // spherical coordinates in radians. $degrees_to_radians = pi() / 180.0; // phi = 90 - latitude $phi1 = (90.0 - $lat1) * $degrees_to_radians; $phi2 = (90.0 - $lat2) * $degrees_to_radians; // theta = longitude $theta1 = $long1 * $degrees_to_radians; $theta2 = $long2 * $degrees_to_radians; // Compute spherical distance from spherical coordinates. // For two locations in spherical coordinates // (1, theta, phi) and (1, theta, phi) // cosine( arc length ) = // sin phi sin phi' cos(theta-theta') + cos phi cos phi' // distance = rho * arc length $cos = (sin($phi1) * sin($phi2) * cos($theta1 - $theta2) + cos($phi1) * cos($phi2)); // why some cosinus are > than 1 ? if ($cos > 1.0) { $cos = 1.0; } $arc = acos($cos); // Remember to multiply arc by the radius of the earth // in your favorite set of units to get length. return $arc * 6371000; }