mirror of
https://github.com/nextcloud/maps.git
synced 2026-01-12 15:46:09 +00:00
790 lines
25 KiB
PHP
790 lines
25 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Nextcloud - maps
|
|
*
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
* later. See the COPYING file.
|
|
*
|
|
* @author Julien Veyssier
|
|
* @copyright Julien Veyssier 2019
|
|
*/
|
|
|
|
namespace OCA\Maps\Service;
|
|
|
|
use OC\Archive\ZIP;
|
|
use OCP\DB\Exception;
|
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
use OCP\Files\NotFoundException;
|
|
use OCP\IDBConnection;
|
|
use OCP\IL10N;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class DevicesService {
|
|
|
|
private $importUserId;
|
|
private $currentXmlTag;
|
|
private $importDevName;
|
|
private $importFileName;
|
|
private $currentPoint;
|
|
private $currentPointList;
|
|
private $trackIndex;
|
|
private $pointIndex;
|
|
private $insideTrk;
|
|
|
|
public function __construct(
|
|
private LoggerInterface $logger,
|
|
private IL10N $l10n,
|
|
private IDBConnection $dbconnection,
|
|
) {
|
|
}
|
|
|
|
private function db_quote_escape_string($str) {
|
|
return $this->dbconnection->quote($str);
|
|
}
|
|
|
|
/**
|
|
* @param string $userId
|
|
* @param int $pruneBefore
|
|
* @return array with devices
|
|
*/
|
|
public function getDevicesFromDB($userId) {
|
|
$devices = [];
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
$qb->select('id', 'user_agent', 'color')
|
|
->from('maps_devices', 'd')
|
|
->where(
|
|
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
|
|
);
|
|
$req = $qb->executeQuery();
|
|
|
|
while ($row = $req->fetch()) {
|
|
$devices[intval($row['id'])] = [
|
|
'id' => intval($row['id']),
|
|
'user_agent' => $row['user_agent'],
|
|
'color' => $row['color'],
|
|
'isShareable' => true,
|
|
'isDeleteable' => true,
|
|
'isUpdateable' => true,
|
|
'isReadable' => true,
|
|
'shares' => []
|
|
];
|
|
}
|
|
$req->closeCursor();
|
|
return $devices;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $tokens
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function getDevicesByTokens(array $tokens) {
|
|
$devices = [];
|
|
$qb = $this->dbconnection->getquerybuilder();
|
|
$qb->select('d.id', 'd.user_agent', 'd.color', 's.token')
|
|
->from('maps_devices', 'd')
|
|
->innerJoin('d', 'maps_device_shares', 's', $qb->expr()->eq('d.id', 's.device_id'))
|
|
->where(
|
|
$qb->expr()->in('s.token', $qb->createNamedParameter($tokens, IQueryBuilder::PARAM_STR_ARRAY))
|
|
);
|
|
$req = $qb->executeQuery();
|
|
|
|
while ($row = $req->fetch()) {
|
|
if (array_key_exists(intval($row['id']), $devices)) {
|
|
$devices[intval($row['id'])]['tokens'][] = $row['token'];
|
|
} else {
|
|
$devices[intval($row['id'])] = [
|
|
'id' => intval($row['id']),
|
|
'user_agent' => $row['user_agent'],
|
|
'color' => $row['color'],
|
|
'isShareable' => false,
|
|
'isDeleteable' => true,
|
|
'isUpdateable' => false,
|
|
'isReadable' => true,
|
|
'shares' => [],
|
|
'tokens' => [$row['token']]
|
|
];
|
|
}
|
|
}
|
|
$req->closeCursor();
|
|
return $devices;
|
|
}
|
|
|
|
/**
|
|
* @param $userId
|
|
* @param $deviceId
|
|
* @param int|null $pruneBefore
|
|
* @param int|null $limit
|
|
* @param int|null $offset
|
|
* @return array
|
|
* @throws \OCP\DB\Exception
|
|
*/
|
|
public function getDevicePointsFromDB($userId, $deviceId, ?int $pruneBefore = 0, ?int $limit = null, ?int $offset = null) {
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
// get coordinates
|
|
$qb->selectDistinct(['p.id', 'lat', 'lng', 'timestamp', 'altitude', 'accuracy', 'battery'])
|
|
->from('maps_device_points', 'p')
|
|
->innerJoin('p', 'maps_devices', 'd', $qb->expr()->eq('d.id', 'p.device_id'))
|
|
->where(
|
|
$qb->expr()->eq('p.device_id', $qb->createNamedParameter($deviceId, IQueryBuilder::PARAM_INT))
|
|
)
|
|
->andWhere(
|
|
$qb->expr()->eq('d.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
|
|
);
|
|
if (intval($pruneBefore) > 0) {
|
|
$qb->andWhere(
|
|
$qb->expr()->gt('timestamp', $qb->createNamedParameter(intval($pruneBefore), IQueryBuilder::PARAM_INT))
|
|
);
|
|
}
|
|
if (!is_null($offset)) {
|
|
$qb->setFirstResult($offset);
|
|
}
|
|
if (!is_null($limit)) {
|
|
$qb->setMaxResults($limit);
|
|
}
|
|
$qb->orderBy('timestamp', 'DESC');
|
|
$req = $qb->executeQuery();
|
|
|
|
$points = [];
|
|
while ($row = $req->fetch()) {
|
|
$points[] = [
|
|
'id' => intval($row['id']),
|
|
'lat' => floatval($row['lat']),
|
|
'lng' => floatval($row['lng']),
|
|
'timestamp' => intval($row['timestamp']),
|
|
'altitude' => is_numeric($row['altitude']) ? floatval($row['altitude']) : null,
|
|
'accuracy' => is_numeric($row['accuracy']) ? floatval($row['accuracy']) : null,
|
|
'battery' => is_numeric($row['battery']) ? floatval($row['battery']) : null
|
|
];
|
|
}
|
|
$req->closeCursor();
|
|
|
|
return array_reverse($points);
|
|
}
|
|
|
|
/**
|
|
* @param string[] $token
|
|
* @param int|null $pruneBefore
|
|
* @param int|null $limit
|
|
* @param int|null $offset
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function getDevicePointsByTokens(array $tokens, ?int $pruneBefore = 0, ?int $limit = 10000, ?int $offset = 0) {
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
// get coordinates
|
|
$or = [];
|
|
foreach ($tokens as $token) {
|
|
$or[] = $qb->expr()->andX(
|
|
$qb->expr()->eq('s.token', $qb->createNamedParameter($token, IQueryBuilder::PARAM_STR)),
|
|
$qb->expr()->lte('p.timestamp', 's.timestamp_to'),
|
|
$qb->expr()->gte('p.timestamp', 's.timestamp_from')
|
|
);
|
|
}
|
|
$qb->select('p.id', 'lat', 'lng', 'timestamp', 'altitude', 'accuracy', 'battery')
|
|
->from('maps_device_points', 'p')
|
|
->innerJoin('p', 'maps_device_shares', 's', $qb->expr()->eq('p.device_id', 's.device_id'))
|
|
->where($qb->expr()->orX(...$or));
|
|
|
|
if (intval($pruneBefore) > 0) {
|
|
$qb->andWhere(
|
|
$qb->expr()->gt('timestamp', $qb->createNamedParameter(intval($pruneBefore), IQueryBuilder::PARAM_INT))
|
|
);
|
|
}
|
|
if (!is_null($offset)) {
|
|
$qb->setFirstResult($offset);
|
|
}
|
|
if (!is_null($limit)) {
|
|
$qb->setMaxResults($limit);
|
|
}
|
|
$qb->orderBy('timestamp', 'DESC');
|
|
$req = $qb->executeQuery();
|
|
|
|
$points = [];
|
|
while ($row = $req->fetch()) {
|
|
$points[] = [
|
|
'id' => intval($row['id']),
|
|
'lat' => floatval($row['lat']),
|
|
'lng' => floatval($row['lng']),
|
|
'timestamp' => intval($row['timestamp']),
|
|
'altitude' => is_numeric($row['altitude']) ? floatval($row['altitude']) : null,
|
|
'accuracy' => is_numeric($row['accuracy']) ? floatval($row['accuracy']) : null,
|
|
'battery' => is_numeric($row['battery']) ? floatval($row['battery']) : null
|
|
];
|
|
}
|
|
$req->closeCursor();
|
|
|
|
return array_reverse($points);
|
|
}
|
|
|
|
/**
|
|
* @param $userId
|
|
* @param $deviceId
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function getDeviceTimePointsFromDb($userId, $deviceId) {
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
// get coordinates
|
|
$qb->select('lat', 'lng', 'timestamp')
|
|
->from('maps_device_points', 'p')
|
|
->innerJoin('p', 'maps_devices', 'd', $qb->expr()->eq('d.id', 'p.device_id'))
|
|
->where(
|
|
$qb->expr()->eq('p.device_id', $qb->createNamedParameter($deviceId, IQueryBuilder::PARAM_INT))
|
|
)
|
|
->andWhere(
|
|
$qb->expr()->eq('d.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
|
|
);
|
|
$qb->orderBy('timestamp', 'ASC');
|
|
$req = $qb->executeQuery();
|
|
|
|
$points = [];
|
|
while ($row = $req->fetch()) {
|
|
$points[intval($row['timestamp'])] = [floatval($row['lat']), floatval($row['lng'])];
|
|
}
|
|
$req->closeCursor();
|
|
return $points;
|
|
}
|
|
|
|
public function getOrCreateDeviceFromDB($userId, $userAgent) {
|
|
$deviceId = null;
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
$qb->select('id')
|
|
->from('maps_devices', 'd')
|
|
->where(
|
|
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
|
|
)
|
|
->andWhere(
|
|
$qb->expr()->eq('user_agent', $qb->createNamedParameter($userAgent, IQueryBuilder::PARAM_STR))
|
|
);
|
|
$req = $qb->executeQuery();
|
|
|
|
while ($row = $req->fetch()) {
|
|
$deviceId = intval($row['id']);
|
|
break;
|
|
}
|
|
$req->closeCursor();
|
|
|
|
if ($deviceId === null) {
|
|
$qb->insert('maps_devices')
|
|
->values([
|
|
'user_agent' => $qb->createNamedParameter($userAgent, IQueryBuilder::PARAM_STR),
|
|
'user_id' => $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)
|
|
]);
|
|
$qb->executeStatement();
|
|
$deviceId = $qb->getLastInsertId();
|
|
}
|
|
return $deviceId;
|
|
}
|
|
|
|
public function addPointToDB($deviceId, $lat, $lng, $ts, $altitude, $battery, $accuracy) {
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
$qb->insert('maps_device_points')
|
|
->values([
|
|
'device_id' => $qb->createNamedParameter($deviceId, IQueryBuilder::PARAM_STR),
|
|
'lat' => $qb->createNamedParameter($lat, IQueryBuilder::PARAM_STR),
|
|
'lng' => $qb->createNamedParameter($lng, IQueryBuilder::PARAM_STR),
|
|
'timestamp' => $qb->createNamedParameter(intval($ts), IQueryBuilder::PARAM_INT),
|
|
'altitude' => $qb->createNamedParameter(is_numeric($altitude) ? $altitude : null, IQueryBuilder::PARAM_STR),
|
|
'battery' => $qb->createNamedParameter(is_numeric($battery) ? $battery : null, IQueryBuilder::PARAM_STR),
|
|
'accuracy' => $qb->createNamedParameter(is_numeric($accuracy) ? $accuracy : null, IQueryBuilder::PARAM_STR)
|
|
]);
|
|
$qb->executeStatement();
|
|
$pointId = $qb->getLastInsertId();
|
|
return $pointId;
|
|
}
|
|
|
|
public function addPointsToDB($deviceId, $points) {
|
|
$values = [];
|
|
foreach ($points as $p) {
|
|
$value = '(' .
|
|
$this->db_quote_escape_string($deviceId) . ', ' .
|
|
$this->db_quote_escape_string($p['lat']) . ', ' .
|
|
$this->db_quote_escape_string($p['lng']) . ', ' .
|
|
$this->db_quote_escape_string($p['date']) . ', ' .
|
|
((isset($p['altitude']) and is_numeric($p['altitude'])) ? $this->db_quote_escape_string(floatval($p['altitude'])) : 'NULL') . ', ' .
|
|
((isset($p['battery']) and is_numeric($p['battery'])) ? $this->db_quote_escape_string(floatval($p['battery'])) : 'NULL') . ', ' .
|
|
((isset($p['accuracy']) and is_numeric($p['accuracy'])) ? $this->db_quote_escape_string(floatval($p['accuracy'])) : 'NULL') . ')';
|
|
array_push($values, $value);
|
|
}
|
|
$valuesStr = implode(', ', $values);
|
|
$sql = '
|
|
INSERT INTO *PREFIX*maps_device_points
|
|
(device_id, lat, lng, timestamp,
|
|
altitude, battery, accuracy)
|
|
VALUES ' . $valuesStr . ' ;';
|
|
$req = $this->dbconnection->prepare($sql);
|
|
$req->execute();
|
|
$req->closeCursor();
|
|
}
|
|
|
|
public function getDeviceFromDB($id, $userId) {
|
|
$device = null;
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
$qb->select('id', 'user_agent', 'color')
|
|
->from('maps_devices', 'd')
|
|
->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()) {
|
|
$device = [
|
|
'id' => intval($row['id']),
|
|
'user_agent' => $row['user_agent'],
|
|
'color' => $row['color']
|
|
];
|
|
break;
|
|
}
|
|
$req->closeCursor();
|
|
return $device;
|
|
}
|
|
|
|
public function editDeviceInDB($id, $color, $name) {
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
$qb->update('maps_devices');
|
|
if (is_string($color) && strlen($color) > 0) {
|
|
$qb->set('color', $qb->createNamedParameter($color, IQueryBuilder::PARAM_STR));
|
|
}
|
|
if (is_string($name) && strlen($name) > 0) {
|
|
$qb->set('user_agent', $qb->createNamedParameter($name, IQueryBuilder::PARAM_STR));
|
|
}
|
|
$qb->where(
|
|
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
|
|
);
|
|
$qb->executeStatement();
|
|
}
|
|
|
|
public function deleteDeviceFromDB($id) {
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
$qb->delete('maps_devices')
|
|
->where(
|
|
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
|
|
);
|
|
$qb->executeStatement();
|
|
|
|
$qb->delete('maps_device_points')
|
|
->where(
|
|
$qb->expr()->eq('device_id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
|
|
);
|
|
$qb->executeStatement();
|
|
}
|
|
|
|
public function countPoints($userId, $deviceIdList, $begin, $end) {
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
$qb->select($qb->createFunction('COUNT(*) AS co'))
|
|
->from('maps_devices', 'd')
|
|
->innerJoin('d', 'maps_device_points', 'p', $qb->expr()->eq('d.id', 'p.device_id'))
|
|
->where(
|
|
$qb->expr()->eq('d.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
|
|
);
|
|
if (is_array($deviceIdList) and count($deviceIdList) > 0) {
|
|
$or = $qb->expr()->orx();
|
|
foreach ($deviceIdList as $deviceId) {
|
|
$or->add($qb->expr()->eq('d.id', $qb->createNamedParameter($deviceId, IQueryBuilder::PARAM_INT)));
|
|
}
|
|
$qb->andWhere($or);
|
|
} else {
|
|
return 0;
|
|
}
|
|
if ($begin !== null && is_numeric($begin)) {
|
|
$qb->andWhere(
|
|
$qb->expr()->gt('p.timestamp', $qb->createNamedParameter(intval($begin), IQueryBuilder::PARAM_INT))
|
|
);
|
|
}
|
|
if ($end !== null && is_numeric($end)) {
|
|
$qb->andWhere(
|
|
$qb->expr()->lt('p.timestamp', $qb->createNamedParameter(intval($end), IQueryBuilder::PARAM_INT))
|
|
);
|
|
}
|
|
$req = $qb->executeQuery();
|
|
$count = 0;
|
|
while ($row = $req->fetch()) {
|
|
$count = intval($row['co']);
|
|
break;
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
public function exportDevices($userId, $handler, $deviceIdList, $begin, $end, $appVersion, $filename) {
|
|
$gpxHeader = $this->generateGpxHeader($filename, $appVersion, count($deviceIdList));
|
|
fwrite($handler, $gpxHeader);
|
|
|
|
foreach ($deviceIdList as $devid) {
|
|
$nbPoints = $this->countPoints($userId, [$devid], $begin, $end);
|
|
if ($nbPoints > 0) {
|
|
$this->getAndWriteDevicePoints($devid, $begin, $end, $handler, $nbPoints, $userId);
|
|
}
|
|
}
|
|
fwrite($handler, '</gpx>');
|
|
}
|
|
|
|
private function generateGpxHeader($name, $appVersion, $nbdev = 0) {
|
|
date_default_timezone_set('UTC');
|
|
$dt = new \DateTime();
|
|
$date = $dt->format('Y-m-d\TH:i:s\Z');
|
|
$gpxText = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' . "\n";
|
|
$gpxText .= '<gpx xmlns="http://www.topografix.com/GPX/1/1"' .
|
|
' xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"' .
|
|
' xmlns:wptx1="http://www.garmin.com/xmlschemas/WaypointExtension/v1"' .
|
|
' xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"' .
|
|
' creator="Nextcloud Maps v' .
|
|
$appVersion . '" version="1.1"' .
|
|
' 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' .
|
|
' http://www.garmin.com/xmlschemas/GpxExtensions/v3' .
|
|
' http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd' .
|
|
' http://www.garmin.com/xmlschemas/WaypointExtension/v1' .
|
|
' http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd' .
|
|
' http://www.garmin.com/xmlschemas/TrackPointExtension/v1' .
|
|
' http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">' . "\n";
|
|
$gpxText .= '<metadata>' . "\n" . ' <time>' . $date . '</time>' . "\n";
|
|
$gpxText .= ' <name>' . $name . '</name>' . "\n";
|
|
if ($nbdev > 0) {
|
|
$gpxText .= ' <desc>' . $nbdev . ' device' . ($nbdev > 1 ? 's' : '') . '</desc>' . "\n";
|
|
}
|
|
$gpxText .= '</metadata>' . "\n";
|
|
return $gpxText;
|
|
}
|
|
|
|
private function getAndWriteDevicePoints($devid, $begin, $end, $fd, $nbPoints, $userId) {
|
|
$device = $this->getDeviceFromDB($devid, $userId);
|
|
$devname = $device['user_agent'];
|
|
$qb = $this->dbconnection->getQueryBuilder();
|
|
|
|
$gpxText = '<trk>' . "\n" . ' <name>' . $devname . '</name>' . "\n";
|
|
$gpxText .= ' <trkseg>' . "\n";
|
|
fwrite($fd, $gpxText);
|
|
|
|
$chunkSize = 10000;
|
|
$pointIndex = 0;
|
|
|
|
while ($pointIndex < $nbPoints) {
|
|
$gpxText = '';
|
|
$qb->select('id', 'lat', 'lng', 'timestamp', 'altitude', 'accuracy', 'battery')
|
|
->from('maps_device_points', 'p')
|
|
->where(
|
|
$qb->expr()->eq('device_id', $qb->createNamedParameter($devid, IQueryBuilder::PARAM_INT))
|
|
);
|
|
if (intval($begin) > 0) {
|
|
$qb->andWhere(
|
|
$qb->expr()->gt('timestamp', $qb->createNamedParameter(intval($begin), IQueryBuilder::PARAM_INT))
|
|
);
|
|
}
|
|
if (intval($end) > 0) {
|
|
$qb->andWhere(
|
|
$qb->expr()->lt('timestamp', $qb->createNamedParameter(intval($end), IQueryBuilder::PARAM_INT))
|
|
);
|
|
}
|
|
$qb->setFirstResult($pointIndex);
|
|
$qb->setMaxResults($chunkSize);
|
|
$qb->orderBy('timestamp', 'ASC');
|
|
$req = $qb->executeQuery();
|
|
|
|
while ($row = $req->fetch()) {
|
|
$id = intval($row['id']);
|
|
$lat = floatval($row['lat']);
|
|
$lng = floatval($row['lng']);
|
|
$epoch = $row['timestamp'];
|
|
$date = '';
|
|
if (is_numeric($epoch)) {
|
|
$epoch = intval($epoch);
|
|
$dt = new \DateTime("@$epoch");
|
|
$date = $dt->format('Y-m-d\TH:i:s\Z');
|
|
}
|
|
$alt = $row['altitude'];
|
|
$acc = $row['accuracy'];
|
|
$bat = $row['battery'];
|
|
|
|
$gpxExtension = '';
|
|
$gpxText .= ' <trkpt lat="' . $lat . '" lon="' . $lng . '">' . "\n";
|
|
$gpxText .= ' <time>' . $date . '</time>' . "\n";
|
|
if (is_numeric($alt)) {
|
|
$gpxText .= ' <ele>' . sprintf('%.2f', floatval($alt)) . '</ele>' . "\n";
|
|
}
|
|
if (is_numeric($acc) && intval($acc) >= 0) {
|
|
$gpxExtension .= ' <accuracy>' . sprintf('%.2f', floatval($acc)) . '</accuracy>' . "\n";
|
|
}
|
|
if (is_numeric($bat) && intval($bat) >= 0) {
|
|
$gpxExtension .= ' <batterylevel>' . sprintf('%.2f', floatval($bat)) . '</batterylevel>' . "\n";
|
|
}
|
|
if ($gpxExtension !== '') {
|
|
$gpxText .= ' <extensions>' . "\n" . $gpxExtension;
|
|
$gpxText .= ' </extensions>' . "\n";
|
|
}
|
|
$gpxText .= ' </trkpt>' . "\n";
|
|
}
|
|
$req->closeCursor();
|
|
|
|
// write the chunk
|
|
fwrite($fd, $gpxText);
|
|
$pointIndex = $pointIndex + $chunkSize;
|
|
}
|
|
$gpxText = ' </trkseg>' . "\n";
|
|
$gpxText .= '</trk>' . "\n";
|
|
fwrite($fd, $gpxText);
|
|
}
|
|
|
|
public function importDevices($userId, $file) {
|
|
$lowerFileName = strtolower($file->getName());
|
|
if ($this->endswith($lowerFileName, '.gpx')) {
|
|
return $this->importDevicesFromGpx($userId, $file);
|
|
} elseif ($this->endswith($lowerFileName, '.kml')) {
|
|
$fp = $file->fopen('r');
|
|
$name = $file->getName();
|
|
return $this->importDevicesFromKml($userId, $fp, $name);
|
|
} elseif ($this->endswith($lowerFileName, '.kmz')) {
|
|
return $this->importDevicesFromKmz($userId, $file);
|
|
}
|
|
}
|
|
|
|
public function importDevicesFromGpx($userId, $file) {
|
|
$this->currentPointList = [];
|
|
$this->importUserId = $userId;
|
|
$this->importFileName = $file->getName();
|
|
$this->trackIndex = 1;
|
|
$this->insideTrk = false;
|
|
|
|
$xml_parser = xml_parser_create();
|
|
xml_set_object($xml_parser, $this);
|
|
xml_set_element_handler($xml_parser, 'gpxStartElement', 'gpxEndElement');
|
|
xml_set_character_data_handler($xml_parser, 'gpxDataElement');
|
|
|
|
$fp = $file->fopen('r');
|
|
|
|
// using xml_parse to be able to parse file chunks in case it's too big
|
|
while ($data = fread($fp, 4096000)) {
|
|
if (!xml_parse($xml_parser, $data, feof($fp))) {
|
|
$this->logger->error(
|
|
'Exception in ' . $file->getName() . ' parsing at line ' .
|
|
xml_get_current_line_number($xml_parser) . ' : ' .
|
|
xml_error_string(xml_get_error_code($xml_parser)),
|
|
['app' => 'maps']
|
|
);
|
|
return 0;
|
|
}
|
|
}
|
|
fclose($fp);
|
|
xml_parser_free($xml_parser);
|
|
|
|
return ($this->trackIndex - 1);
|
|
}
|
|
|
|
private function gpxStartElement($parser, $name, $attrs) {
|
|
//$points, array($lat, $lon, $ele, $timestamp, $acc, $bat, $sat, $ua, $speed, $bearing)
|
|
$this->currentXmlTag = $name;
|
|
if ($name === 'TRK') {
|
|
$this->importDevName = '';
|
|
$this->pointIndex = 1;
|
|
$this->currentPointList = [];
|
|
$this->insideTrk = true;
|
|
} elseif ($name === 'TRKPT') {
|
|
$this->currentPoint = [];
|
|
if (isset($attrs['LAT'])) {
|
|
$this->currentPoint['lat'] = floatval($attrs['LAT']);
|
|
}
|
|
if (isset($attrs['LON'])) {
|
|
$this->currentPoint['lng'] = floatval($attrs['LON']);
|
|
}
|
|
}
|
|
//var_dump($attrs);
|
|
}
|
|
|
|
private function gpxEndElement($parser, $name) {
|
|
if ($name === 'TRK') {
|
|
$this->insideTrk = false;
|
|
// log last track points
|
|
if (count($this->currentPointList) > 0) {
|
|
if ($this->importDevName === '') {
|
|
$this->importDevName = $this->importFileName . ' ' . $this->trackIndex;
|
|
}
|
|
$devid = $this->getOrCreateDeviceFromDB($this->importUserId, $this->importDevName);
|
|
$this->addPointsToDB($devid, $this->currentPointList);
|
|
}
|
|
$this->trackIndex++;
|
|
unset($this->currentPointList);
|
|
} elseif ($name === 'TRKPT') {
|
|
// store track point
|
|
|
|
// convert date
|
|
if (isset($this->currentPoint['date'])) {
|
|
$time = new \DateTime($this->currentPoint['date']);
|
|
$timestamp = $time->getTimestamp();
|
|
$this->currentPoint['date'] = $timestamp;
|
|
}
|
|
array_push($this->currentPointList, $this->currentPoint);
|
|
// if we have enough points, we log them and clean the points array
|
|
if (count($this->currentPointList) >= 500) {
|
|
if ($this->importDevName === '') {
|
|
$this->importDevName = 'device' . $this->trackIndex;
|
|
}
|
|
$devid = $this->getOrCreateDeviceFromDB($this->importUserId, $this->importDevName);
|
|
$this->addPointsToDB($devid, $this->currentPointList);
|
|
unset($this->currentPointList);
|
|
$this->currentPointList = [];
|
|
}
|
|
$this->pointIndex++;
|
|
}
|
|
}
|
|
|
|
private function gpxDataElement($parser, $data) {
|
|
$d = trim($data);
|
|
if (!empty($d)) {
|
|
if ($this->currentXmlTag === 'ELE') {
|
|
$this->currentPoint['altitude'] = (isset($this->currentPoint['altitude'])) ? $this->currentPoint['altitude'] . $d : $d;
|
|
} elseif ($this->currentXmlTag === 'BATTERYLEVEL') {
|
|
$this->currentPoint['battery'] = (isset($this->currentPoint['battery'])) ? $this->currentPoint['battery'] . $d : $d;
|
|
} elseif ($this->currentXmlTag === 'ACCURACY') {
|
|
$this->currentPoint['accuracy'] = (isset($this->currentPoint['accuracy'])) ? $this->currentPoint['accuracy'] . $d : $d;
|
|
} elseif ($this->insideTrk and $this->currentXmlTag === 'TIME') {
|
|
$this->currentPoint['date'] = (isset($this->currentPoint['date'])) ? $this->currentPoint['date'] . $d : $d;
|
|
} elseif ($this->insideTrk and $this->currentXmlTag === 'NAME') {
|
|
$this->importDevName = $this->importDevName . $d;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function importDevicesFromKmz($userId, $file) {
|
|
$path = $file->getStorage()->getLocalFile($file->getInternalPath());
|
|
$name = $file->getName();
|
|
$zf = new ZIP($path);
|
|
if (count($zf->getFiles()) > 0) {
|
|
$zippedFilePath = $zf->getFiles()[0];
|
|
$fstream = $zf->getStream($zippedFilePath, 'r');
|
|
|
|
$nbImported = $this->importDevicesFromKml($userId, $fstream, $name);
|
|
} else {
|
|
$nbImported = 0;
|
|
}
|
|
return $nbImported;
|
|
}
|
|
|
|
public function importDevicesFromKml($userId, $fp, $name) {
|
|
$this->trackIndex = 1;
|
|
$this->importUserId = $userId;
|
|
$this->importFileName = $name;
|
|
$xml_parser = xml_parser_create();
|
|
xml_set_object($xml_parser, $this);
|
|
xml_set_element_handler($xml_parser, 'kmlStartElement', 'kmlEndElement');
|
|
xml_set_character_data_handler($xml_parser, 'kmlDataElement');
|
|
|
|
while ($data = fread($fp, 4096000)) {
|
|
if (!xml_parse($xml_parser, $data, feof($fp))) {
|
|
$this->logger->error(
|
|
'Exception in ' . $name . ' parsing at line ' .
|
|
xml_get_current_line_number($xml_parser) . ' : ' .
|
|
xml_error_string(xml_get_error_code($xml_parser)),
|
|
);
|
|
return 0;
|
|
}
|
|
}
|
|
fclose($fp);
|
|
xml_parser_free($xml_parser);
|
|
return ($this->trackIndex - 1);
|
|
}
|
|
|
|
private function kmlStartElement($parser, $name, $attrs) {
|
|
$this->currentXmlTag = $name;
|
|
if ($name === 'GX:TRACK') {
|
|
if (isset($attrs['ID'])) {
|
|
$this->importDevName = $attrs['ID'];
|
|
} else {
|
|
$this->importDevName = $this->importFileName . ' ' . $this->trackIndex;
|
|
}
|
|
$this->pointIndex = 1;
|
|
$this->currentPointList = [];
|
|
} elseif ($name === 'WHEN') {
|
|
$this->currentPoint = [];
|
|
}
|
|
//var_dump($attrs);
|
|
}
|
|
|
|
private function kmlEndElement($parser, $name) {
|
|
if ($name === 'GX:TRACK') {
|
|
// log last track points
|
|
if (count($this->currentPointList) > 0) {
|
|
$devid = $this->getOrCreateDeviceFromDB($this->importUserId, $this->importDevName);
|
|
$this->addPointsToDB($devid, $this->currentPointList);
|
|
}
|
|
$this->trackIndex++;
|
|
unset($this->currentPointList);
|
|
} elseif ($name === 'GX:COORD') {
|
|
// convert date
|
|
if (isset($this->currentPoint['date'])) {
|
|
$time = new \DateTime($this->currentPoint['date']);
|
|
$timestamp = $time->getTimestamp();
|
|
$this->currentPoint['date'] = $timestamp;
|
|
}
|
|
// get latlng
|
|
if (isset($this->currentPoint['coords'])) {
|
|
$spl = explode(' ', $this->currentPoint['coords']);
|
|
if (count($spl) > 1) {
|
|
$this->currentPoint['lat'] = floatval($spl[1]);
|
|
$this->currentPoint['lng'] = floatval($spl[0]);
|
|
if (count($spl) > 2) {
|
|
$this->currentPoint['altitude'] = floatval($spl[2]);
|
|
}
|
|
}
|
|
}
|
|
// store track point
|
|
array_push($this->currentPointList, $this->currentPoint);
|
|
// if we have enough points, we log them and clean the points array
|
|
if (count($this->currentPointList) >= 500) {
|
|
$devid = $this->getOrCreateDeviceFromDB($this->importUserId, $this->importDevName);
|
|
$this->addPointsToDB($devid, $this->currentPointList);
|
|
unset($this->currentPointList);
|
|
$this->currentPointList = [];
|
|
}
|
|
$this->pointIndex++;
|
|
}
|
|
}
|
|
|
|
private function kmlDataElement($parser, $data) {
|
|
$d = trim($data);
|
|
if (!empty($d)) {
|
|
if ($this->currentXmlTag === 'WHEN') {
|
|
$this->currentPoint['date'] = (isset($this->currentPoint['date'])) ? $this->currentPoint['date'] . $d : $d;
|
|
} elseif ($this->currentXmlTag === 'GX:COORD') {
|
|
$this->currentPoint['coords'] = (isset($this->currentPoint['coords'])) ? $this->currentPoint['coords'] . $d : $d;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function endswith($string, $test) {
|
|
$strlen = strlen($string);
|
|
$testlen = strlen($test);
|
|
if ($testlen > $strlen) {
|
|
return false;
|
|
}
|
|
return substr_compare($string, $test, $strlen - $testlen, $testlen) === 0;
|
|
}
|
|
|
|
/**
|
|
* @param $folder
|
|
* @param bool $isCreatable
|
|
* @return mixed
|
|
* @throws NotFoundException
|
|
*/
|
|
public function getSharedDevicesFromFolder($folder, bool $isCreatable = true) {
|
|
try {
|
|
$file = $folder->get('.device_shares.json');
|
|
} catch (NotFoundException $e) {
|
|
if ($isCreatable) {
|
|
$file = $folder->newFile('.device_shares.json', $content = '[]');
|
|
} else {
|
|
throw new NotFoundException();
|
|
}
|
|
}
|
|
return json_decode($file->getContent(), true);
|
|
}
|
|
|
|
}
|