Merge branch 'tracks'

This commit is contained in:
Krivchikov Dmitry 2016-06-16 19:04:36 +03:00
commit 6299bec1a6
12 changed files with 423 additions and 120 deletions

View File

@ -16,7 +16,14 @@ class Geo
$delta_lat = ($lat2 - $lat1);
$delta_lng = ($lng2 - $lng1);
return (6378137 * acos(cos($lat1) * cos($lat2) * cos($lng1 - $lng2) + sin($lat1) * sin($lat2)));
$distance = (6378137 * acos(cos($lat1) * cos($lat2) * cos($lng1 - $lng2) + sin($lat1) * sin($lat2)));
if (isset($latlng1['alt']) && isset($latlng2['alt']) && $latlng1['alt'] > 0 && $latlng1['alt'] <> $latlng2['alt']) {
$altDelta = abs($latlng1['alt'] - $latlng2['alt']);
$distance = sqrt(pow($distance, 2) + pow($altDelta, 2));
}
return $distance;
}
static function formatedDistance($distance)

View File

@ -85,6 +85,9 @@ abstract class Model
return $model::model()->getOne($targetField, $this->$localField);
break;
case self::TO_MANY:
if ($order) {
return $model::model()->getAll(array('where' => "`$targetField` = '" . $this->$localField . "'", 'order' => $order));
}
return $model::model()->getAll(array('where' => "`$targetField` = '" . $this->$localField . "'"));
break;
default:
@ -115,13 +118,14 @@ abstract class Model
* @param const $relationType - тип связи (к одному / ко многим).
* @return obkect/array
*/
public function setRelation($nameOfRelation, $localField, $targetModel, $targetField, $relationType)
public function setRelation($nameOfRelation, $localField, $targetModel, $targetField, $relationType, $order = NULL)
{
return $this->_relations_[$nameOfRelation] = array(
'localField' => $localField,
'targetModel' => $targetModel,
'targetField' => $targetField,
'relationType' => $relationType,
'order' => $order,
);
}

View File

@ -81,7 +81,7 @@ class EdittrackController extends Controller
$trip->json = '';
$trip->save();
App::redirect('/tracks/edit/id/'.$trip->id);
App::redirect('/tracks/edit/id/' . $trip->id);
}
function actionMergetrack()
@ -99,7 +99,7 @@ class EdittrackController extends Controller
}
$takeIt = $mergetId = $merget = FALSE;
$tracks = Tracks::model()->getAll(array('where'=>'`tripId` = ?'), array($trip->id));
$tracks = Tracks::model()->getAll(array('where' => '`tripId` = ?'), array($trip->id));
foreach ($tracks as $key => $value) {
if ($takeIt) {
$mergetId = $key;
@ -129,7 +129,7 @@ class EdittrackController extends Controller
$trip->json = '';
$trip->save();
App::redirect('/tracks/edit/id/'.$trip->id);
App::redirect('/tracks/edit/id/' . $trip->id);
}
function actionWaypoint()
@ -170,7 +170,7 @@ class EdittrackController extends Controller
$trip->json = '';
$trip->save();
App::redirect('/tracks/edit/id/'.$trip->id);
App::redirect('/tracks/edit/id/' . $trip->id);
}
function actionGettrackdetails()
@ -187,8 +187,105 @@ class EdittrackController extends Controller
App::error404();
}
$trackpoints = TrackPoints::model()->getAll(array('where' => '`trackId` = '.$track->id, 'asArray' => TRUE, 'noKeys' => TRUE,));
$trackpoints = TrackPoints::model()->getAll(array('where' => '`trackId` = ' . $track->id, 'asArray' => TRUE, 'noKeys' => TRUE, 'order' => '`id`'));
self::renderPartial('json.php', $trackpoints);
}
function actionCut()
{
$id = intval(App::getParam('id'));
$trackPoint = TrackPoints::model()->getByPK($id);
if ($trackPoint->isEmpty()) {
App::error404();
}
$track = Tracks::model()->getByPK($trackPoint->trackId);
if ($track->isEmpty()) {
App::error404();
}
$trip = Trips::model()->getByPK($track->tripId);
if ($trip->isEmpty() || $trip->owner != App::$user->id) {
App::error404();
}
$tracks = Tracks::model()->getAll(array('where' => '`tripId` = ?'), array($trip->id));
foreach ($tracks as $trk) {
$newTrack = Tracks::model()->add(
array(
'tripId' => $trip->id,
'name' => $trk->name,
'distance' => $trk->distance,
'description' => $trk->description,
)
);
if ($trk->id == $track->id) {
$trackPoints = TrackPoints::model()->getAll(array('where'=>'`trackId` = ? AND `id` <= ?', 'order' => 'id'), array($trk->id, $trackPoint->id));
foreach ($trackPoints as $tpt) {
TrackPoints::model()->add(
array(
'trackId' => $newTrack->id,
'lat' => floatval($tpt->lat),
'lng' => floatval($tpt->lng),
'alt' => $tpt->alt,
'createdAt' => $tpt->createdAt,
'speed' => $tpt->speed,
)
);
}
$newTrack = Tracks::model()->add(
array(
'tripId' => $trip->id,
'name' => $trk->name . ' / #2',
'distance' => $trk->distance,
'description' => $trk->description,
)
);
$trackPoints = TrackPoints::model()->getAll(array('where'=>'`trackId` = ? AND `id` >= ?', 'order' => 'id'), array($trk->id, $trackPoint->id));
foreach ($trackPoints as $tpt) {
TrackPoints::model()->add(
array(
'trackId' => $newTrack->id,
'lat' => floatval($tpt->lat),
'lng' => floatval($tpt->lng),
'alt' => $tpt->alt,
'createdAt' => $tpt->createdAt,
'speed' => $tpt->speed,
)
);
}
} else {
$trackPoints = TrackPoints::model()->getAll(array('where'=>'`trackId` = ?', 'order' => 'id'), array($trk->id));
foreach ($trackPoints as $tpt) {
TrackPoints::model()->add(
array(
'trackId' => $newTrack->id,
'lat' => floatval($tpt->lat),
'lng' => floatval($tpt->lng),
'alt' => $tpt->alt,
'createdAt' => $tpt->createdAt,
'speed' => $tpt->speed,
)
);
}
}
App::DB()->query("DELETE FROM `trackPoints` WHERE `trackId` = ".$trk->id);
$trk->delete($trk->id);
}
$trip->json = '';
$trip->calculateDist();
$trip->save();
App::redirect('/tracks/edit/id/' . $trip->id);
}
}

View File

@ -621,11 +621,11 @@ class JsonController extends Controller
}
$tracks = Tracks::model()->getAll(array('where' => '`tripId` = ?'), array($trip->id));
$tracks = Tracks::model()->getAll(array('where' => '`tripId` = ?', 'order' => 'id'), array($trip->id));
if ($tracks) {
foreach ($tracks as $track) {
$resLatlngs = array();
$trackPoints = TrackPoints::model()->getAll(array('where' => '`trackId` = ?'), array($track->id));
$trackPoints = TrackPoints::model()->getAll(array('where' => '`trackId` = ?', 'order'=>'id'), array($track->id));
if ($trackPoints) {
foreach ($trackPoints as $trackPoint) {
$resLatlngs[] = array(

View File

@ -30,6 +30,12 @@ class TracksController extends Controller
function actionEdit()
{
self::addScript('/js/leaflet.js');
self::addStyle('/css/leaflet.css');
self::addScript('/js/editTrack.js');
self::addStyle('/css/editTrack.css');
$id = intval(App::getParam('id'));
if (!$id) {
@ -108,6 +114,22 @@ class TracksController extends Controller
));
}
function actionExport()
{
$id = intval(App::getParam('id'));
$trip = Trips::model()->getByPK($id);
if ($trip->isEmpty() || $trip->owner != App::$user->id) {
App::error404();
}
self::renderPartial('/tracks/gpx.php', array(
'trip' => $trip,
'waypoints' => WayPoints::model()->getAll(array('where' => '`tripId` = ?'), array($trip->id)),
'tracks' => Tracks::model()->getAll(array('where' => '`tripId` = ?'), array($trip->id)),
));
}
function actionImport()
{
$id = (int) App::getParam('id');
@ -206,25 +228,29 @@ class TracksController extends Controller
foreach ($trkpt as $tpt) {
$speed = isset($tpt->extensions->geotrackerMeta['s']) ? floatval($tpt->extensions->geotrackerMeta['s']) : 0;
$alt = isset($tpt->ele) ? floatval($tpt->ele) : 0;
$trackPoint = TrackPoints::model()->add(
array(
'trackId' => $track->id,
'lat' => floatval($tpt['lat']),
'lng' => floatval($tpt['lon']),
'alt' => isset($tpt->ele) ? floatval($tpt->ele) : 0,
'alt' => $alt,
'createdAt' => isset($tpt->time) ? $tpt->time : date('c'),
'speed' => $speed,
)
);
if ($prevPoint) {
$distanceTmp += Geo::distance($prevPoint, array('lat' => floatval($tpt['lat']), 'lng' => floatval($tpt['lon'])));
$distanceTmp += Geo::distance($prevPoint, array('lat' => floatval($tpt['lat']), 'lng' => floatval($tpt['lon']), 'alt' => $alt));
}
$prevPoint = array(
'lat' => floatval($tpt['lat']),
'lng' => floatval($tpt['lon']),
);
if (isset($tpt->ele)) {
$prevPoint['alt'] = floatval($tpt->ele);
}
$tptCount++;
}

View File

@ -7,6 +7,7 @@ class Tracks extends Model
{
$this->_tableName_ = 'tracks';
parent::__construct($fromArray);
$this->setRelation('points', 'id', 'TrackPoints', 'trackId', self::TO_MANY, '`id` ASC');
}
static function model()
@ -18,4 +19,32 @@ class Tracks extends Model
{
return Geo::formatedDistance($this->distance);
}
/**
* Считает/пересчитывает длину фрагмента трека и сохраняет значение в БД.
*/
function calculateDist()
{
$points = $this->points;
$distance = 0;
$prevPoint = array();
foreach ($points as $point) {
if ($prevPoint) {
$distance += Geo::distance(
$prevPoint,
array('lat' => floatval($point->lat), 'lng' => floatval($point->lng), 'alt' => floatval($point->alt)));
}
$prevPoint = array(
'lat' => floatval($point->lat),
'lng' => floatval($point->lng),
'alt' => floatval($point->alt),
);
}
$this->distance = $distance;
$this->save();
return $distance;
}
}

View File

@ -11,6 +11,7 @@ class Trips extends Model
{
$this->_tableName_ = 'trips';
parent::__construct($fromArray);
$this->setRelation('tracks', 'id', 'Tracks', 'tripId', self::TO_MANY, '`id` ASC');
}
static function model()
@ -22,4 +23,22 @@ class Trips extends Model
{
return Geo::formatedDistance($this->distance);
}
/**
* Считает/пересчитывает длину трека и всех его фрагментов
*/
function calculateDist()
{
$tracks = $this->tracks;
$distance = 0;
foreach ($tracks as $track) {
$distance += $track->calculateDist();
}
$this->distance = $distance;
$this->save();
return $distance;
}
}

11
public/css/editTrack.css Normal file
View File

@ -0,0 +1,11 @@
.savedOK {color: #999; display: none;}
.savedError {color: #900; display: none;}
.trackpointsContainer {display: none;}
.trackpointsContainer table {height: 500px; width: 100%; margin-bottom: 20px;}
.trackpointsContainer table td {width: 50%;}
.trackPoint:hover {background-color: #ddd;}
.trackpointsList {overflow-y: scroll; height: 500px;}
.trackpointsMap {height: 500px;}

140
public/js/editTrack.js Normal file
View File

@ -0,0 +1,140 @@
var tracks = new Array;
var maps = new Array;
var markers = new Array;
var icon = L.icon({
iconUrl: '/ico/marker.png',
iconSize: [32, 37],
iconAnchor: [16, 37],
popupAnchor: [0, 1]
});
function saveChangesTrip()
{
$('#tripSavedOK').hide();
$('#tripSavedError').hide();
$.ajax({
url: "/edittrack/trip/id/<?= $trip->id ?>",
method: "POST",
data: {
name: $('#tripName').val(),
description: $('#tripDescription').val(),
isPublic: $('#tripIsPublic').prop('checked')
},
success: function (data) {
$('#tripSavedOK').show();
setTimeout(function () {
$('#tripSavedOK').fadeOut(300);
}, 2000);
},
error: function (data) {
$('#tripSavedError').show();
}
});
}
function saveChangesTrack(id)
{
$('#trackSavedOK').hide();
$('#trackSavedError').hide();
$.ajax({
url: "/edittrack/track/id/" + id,
method: "POST",
data: {
name: $('#trackName' + id).val(),
description: $('#trackDescription' + id).val()
},
success: function (data) {
$('#trackSavedOK').show();
setTimeout(function () {
$('#trackSavedOK').fadeOut(300);
}, 2000);
},
error: function (data) {
$('#trackSavedError').show();
}
});
}
function saveChangesWaypoint(id)
{
$('#waypointSavedOK').hide();
$('#waypointSavedError').hide();
$.ajax({
url: "/edittrack/waypoint/id/" + id,
method: "POST",
data: {
name: $('#waypointName' + id).val(),
description: $('#waypointDescription' + id).val()
},
success: function (data) {
$('#waypointSavedOK').show();
setTimeout(function () {
$('#waypointSavedOK').fadeOut(300);
}, 2000);
},
error: function (data) {
$('#waypointSavedError').show();
}
});
}
function loadTrackDetails(id)
{
$('#trackpointsContainer' + id).toggle();
if ($('#trackpointsMap' + id).text() != '') {
return true;
}
$.ajax({
url: "/edittrack/gettrackdetails/id/" + id,
method: "POST",
success: function (data) {
$('#trackpointsList' + id).html('');
if (typeof (data) != 'undefined') {
latlngs = new Array();
for (var i = 0; i < data.length; i++) {
$(makeRow(data[i])).appendTo('#trackpointsList' + id);
latlngs.push({lat: data[i]['lat'], lng: data[i]['lng']})
}
maps[id] = L.map('trackpointsMap' + id, {zoomControl: true, scrollWheelZoom: false}).setView([latlngs[0]['lat'], latlngs[0]['lng']], 13);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(maps[id]);
tracks[id] = L.polyline(latlngs, {color: '#F00', width: 4, opacity: 0.8});
maps[id].addLayer(tracks[id]);
markers[id] = L.marker([latlngs[1]['lat'], latlngs[1]['lng']], {icon: icon});
maps[id].addLayer(markers[id])
maps[id].fitBounds(tracks[id].getBounds());
}
},
error: function (data) {
console.log(data);
}
});
}
function makeRow(data)
{
row = '<div class="trackPoint" onmouseover="moveMarker(' + data.trackId + ', ' + data.lat + ', ' + data.lng + ')">';
row += data.lat + ' / ' + data.lng;
// row += data.id + ' - ' + data.lat + ' / ' + data.lng;
row += ' <a href="/edittrack/cut/id/' + data.id + '" class="btn btn-default btn-sm" title="Разрезать трек в этой точке"><span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span></a> ';
row += '</div>';
return row;
}
function moveMarker(mapId, lat, lng)
{
markers[mapId].setLatLng([lat, lng]);
maps[mapId].setView([lat, lng], maps[mapId].getZoom());
}

View File

@ -1,5 +1,5 @@
<?php
$tracksCounter = 1;
$tracksCounter = 1;
?>
<a href="/tracks/" class="btn btn-success pull-right">
<span class="glyphicon glyphicon-home" aria-hidden="true"></span>
@ -26,8 +26,8 @@
</label>
</div>
<span id="tripSavedOK" style="color: #999; display: none;">изменения сохранены...</span>
<span id="tripSavedError" style="color: #900; display: none;">при сохранении возникла ошибка!</span>
<span id="tripSavedOK" class="savedOK">изменения сохранены...</span>
<span id="tripSavedError" class="savedError">при сохранении возникла ошибка!</span>
<br/>
</form>
@ -36,6 +36,7 @@
<div id="tracksBlock" style="display: none;">
<!--<hr/>-->
<?php foreach ($tracks as $track): ?>
<a name="trackpointsList<?= $track->id ?>"/>
<form onfocusout="saveChangesTrack(<?= $track->id ?>)" class="form-horizontal">
<div class="form-group">
<div style="font-size: 20px" class="col-sm-1"><?= $track->distance() ?></div>
@ -47,17 +48,26 @@
</div>
<div class="col-sm-2">
<a href="/edittrack/deletetrack/id/<?= $track->id ?>" class="btn btn-danger" onclick="return confirm('Вы уверены, что хотите удалить фрагмент трека?')" title="Удалить фрагмент трека"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></a>
<a href="#" class="btn btn-default" onclick="loadTrackDetails(<?= $track->id ?>)" title="Просмотреть точки"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></a>
<?php if ($tracksCounter < count($tracks)):?>
<a href="/edittrack/mergetrack/id/<?= $track->id ?>" class="btn btn-default" onclick="return confirm('Вы уверены, что хотите совместить фрагмент трека с идущим ниже?')" title="Слить с идущим ниже"><span class="glyphicon glyphicon-save" aria-hidden="true"></span></a>
<a href="#trackpointsList<?= $track->id ?>" class="btn btn-default" onclick="loadTrackDetails(<?= $track->id ?>)" title="Просмотреть точки"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></a>
<?php if ($tracksCounter < count($tracks)): ?>
<a href="/edittrack/mergetrack/id/<?= $track->id ?>" class="btn btn-default" onclick="return confirm('Вы уверены, что хотите совместить фрагмент трека с идущим ниже?')" title="Слить с идущим ниже"><span class="glyphicon glyphicon-resize-small" aria-hidden="true"></span></a>
<?php endif; ?>
</div>
</div>
</form>
<div id="trackpointsList<?= $track->id ?>"></div>
<?php $tracksCounter++; endforeach; ?>
<span id="trackSavedOK" style="color: #999; display: none;">изменения сохранены...</span>
<span id="trackSavedError" style="color: #900; display: none;">при сохранении возникла ошибка!</span>
<div id="trackpointsContainer<?= $track->id ?>" class="trackpointsContainer">
<table>
<tr>
<td><div id="trackpointsList<?= $track->id ?>" class="trackpointsList"></div></td>
<td><div id="trackpointsMap<?= $track->id ?>" class="trackpointsMap"></div></td>
</tr>
</table>
</div>
<?php $tracksCounter++;
endforeach;
?>
<span id="trackSavedOK" class="savedOK">изменения сохранены...</span>
<span id="trackSavedError" class="savedError">при сохранении возникла ошибка!</span>
<br/>
</div>
<?php endif; ?>
@ -66,7 +76,7 @@
<h3 onclick="$('#waypointsBlock').toggle()" style="cursor: pointer;">Путевые точки (<?= count($waypoints) ?>)</h3>
<div id="waypointsBlock" style="display: none;">
<!--<hr/>-->
<?php foreach ($waypoints as $waypoint): ?>
<?php foreach ($waypoints as $waypoint): ?>
<form onfocusout="saveChangesWaypoint(<?= $waypoint->id ?>)" class="form-horizontal">
<div class="form-group">
<div class="col-sm-3">
@ -81,102 +91,8 @@
</div>
</form>
<!--<hr/>-->
<?php endforeach; ?>
<span id="waypointSavedOK" style="color: #999; display: none;">изменения сохранены...</span>
<span id="waypointSavedError" style="color: #900; display: none;">при сохранении возникла ошибка!</span>
<?php endforeach; ?>
<span id="waypointSavedOK" class="savedOK">изменения сохранены...</span>
<span id="waypointSavedError" class="savedError">при сохранении возникла ошибка!</span>
</div>
<?php endif; ?>
<script lang="javascript">
function saveChangesTrip()
{
$('#tripSavedOK').hide();
$('#tripSavedError').hide();
$.ajax({
url: "/edittrack/trip/id/<?= $trip->id ?>",
method: "POST",
data: {
name: $('#tripName').val(),
description: $('#tripDescription').val(),
isPublic: $('#tripIsPublic').prop('checked')
},
success: function (data) {
$('#tripSavedOK').show();
setTimeout(function () {
$('#tripSavedOK').fadeOut(300);
}, 2000);
},
error: function (data) {
$('#tripSavedError').show();
}
});
}
function saveChangesTrack(id)
{
$('#trackSavedOK').hide();
$('#trackSavedError').hide();
$.ajax({
url: "/edittrack/track/id/" + id,
method: "POST",
data: {
name: $('#trackName' + id).val(),
description: $('#trackDescription' + id).val()
},
success: function (data) {
$('#trackSavedOK').show();
setTimeout(function () {
$('#trackSavedOK').fadeOut(300);
}, 2000);
},
error: function (data) {
$('#trackSavedError').show();
}
});
}
function saveChangesWaypoint(id)
{
$('#waypointSavedOK').hide();
$('#waypointSavedError').hide();
$.ajax({
url: "/edittrack/waypoint/id/" + id,
method: "POST",
data: {
name: $('#waypointName' + id).val(),
description: $('#waypointDescription' + id).val()
},
success: function (data) {
$('#waypointSavedOK').show();
setTimeout(function () {
$('#waypointSavedOK').fadeOut(300);
}, 2000);
},
error: function (data) {
$('#waypointSavedError').show();
}
});
}
function loadTrackDetails(id)
{
$.ajax({
url: "/edittrack/gettrackdetails/id/" + id,
method: "POST",
success: function (data) {
$('#trackpointsList'+id).html('');
if (typeof (data) != 'undefined') {
for (var i = 0; i < data.length; i++) {
$('<div>' + data[i].id + ' - ' + data[i].lat + ' / '+ data[i].lng+'</div>').appendTo('#trackpointsList'+id);
}
}
},
error: function (data) {
console.log(data);
}
});
}
</script>

51
views/tracks/gpx.php Normal file
View File

@ -0,0 +1,51 @@
<?php
header('Content-type: text/xml');
header('Content-Disposition: attachment; filename="text.gpx"');
?><?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<gpx version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:geotracker="http://ilyabogdanovich.com/gpx/extensions/geotracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" creator="Find your road! https://wikipoints.ru">
<metadata>
<name><?= $trip->name ?></name>
<desc><?= $trip->description ?></desc>
<link href="https://wikipoints.ru" />
<time><?= $trip->createdAt ?></time>
<author>
<name><?= App::$user->nick ?></name>
<link href="https://wikipoints.ru/user/<?= App::$user->id ?>" />
</author>
</metadata>
<?php if ($waypoints): ?>
<?php foreach ($waypoints as $wpt): ?>
<wpt lat="<?= $wpt->lat ?>" lon="<?= $wpt->lng ?>">
<ele><?= $wpt->alt ?></ele>
<time><?= $wpt->createdAt ?></time>
<name><?= $wpt->name ?></name>
<desc><?= $wpt->description ?></desc>
</wpt>
<?php endforeach; ?>
<?php endif; ?>
<?php if ($tracks): ?>
<trk>
<name><?= $trip->name ?></name>
<extensions>
<geotracker:meta>
<length><?= $trip->distance ?></length>
<creationtime><?= $trip->createdAt ?></creationtime>
</geotracker:meta>
</extensions>
<?php foreach ($tracks as $track): ?>
<?php $trackPoints = TrackPoints::model()->getAll(array('where' => '`trackId` = ?', 'order'=>'id'), array($track->id)); ?>
<trkseg>
<?php foreach ($trackPoints as $tpt): ?>
<trkpt lat="<?= $tpt->lat ?>" lon="<?= $tpt->lng ?>">
<ele><?= $tpt->alt ?></ele>
<time><?= $tpt->createdAt ?></time>
<extensions>
<geotracker:meta s="<?= $tpt->speed ?>" />
</extensions>
</trkpt>
<?php endforeach; ?>
</trkseg>
<?php endforeach; ?>
</trk>
<?php endif; ?>
</gpx>

View File

@ -25,6 +25,9 @@ if ($trips) {
<a href="/tracks/edit/id/<?= $trip->id ?>" class="btn btn-default">
<span class="glyphicon glyphicon-pencil" aria-hidden="true" title="Редактировать трек"></span>
</a>
<a href="/tracks/export/id/<?= $trip->id ?>" class="btn btn-default">
<span class="glyphicon glyphicon-download-alt" aria-hidden="true" title="Скачать трек в формате GPX"></span>
</a>
<?= $trip->id ?>
<a href="/?track=<?= $trip->id ?>"><?= $trip->name ?></a> (<?= $trip->createdAt ?>)
<?php if($trip->status == Trips::STATUS_PUBLIC):?>