2023-02-09 05:55:12 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace OCA\Memories\Db;
|
|
|
|
|
|
|
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
|
|
use OCP\IDBConnection;
|
2023-03-28 00:50:10 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
2023-02-09 05:55:12 +00:00
|
|
|
|
2023-04-14 07:00:19 +00:00
|
|
|
const LAT_KEY = 'GPSLatitude';
|
|
|
|
const LON_KEY = 'GPSLongitude';
|
|
|
|
|
2023-02-09 05:55:12 +00:00
|
|
|
trait TimelineWritePlaces
|
|
|
|
{
|
|
|
|
protected IDBConnection $connection;
|
|
|
|
|
2023-03-28 00:50:10 +00:00
|
|
|
/**
|
|
|
|
* Process the location part of exif data.
|
|
|
|
*
|
|
|
|
* Also update the exif data with the tzid from location (LocationTZID)
|
2023-03-29 02:06:39 +00:00
|
|
|
* Performs an in-place update of the exif data.
|
2023-03-28 00:50:10 +00:00
|
|
|
*
|
|
|
|
* @param int $fileId The file ID
|
2023-03-29 02:06:39 +00:00
|
|
|
* @param array $exif The exif data (will change)
|
2023-03-28 00:50:10 +00:00
|
|
|
* @param array|bool $prevRow The previous row of data
|
|
|
|
*
|
|
|
|
* @return array Update values
|
|
|
|
*/
|
2023-03-29 02:06:39 +00:00
|
|
|
protected function processExifLocation(int $fileId, array &$exif, $prevRow): array
|
2023-03-28 00:50:10 +00:00
|
|
|
{
|
|
|
|
// Store location data
|
2023-04-14 07:00:19 +00:00
|
|
|
[$lat, $lon] = self::readCoord($exif);
|
2023-03-28 00:50:10 +00:00
|
|
|
$oldLat = $prevRow ? (float) $prevRow['lat'] : null;
|
|
|
|
$oldLon = $prevRow ? (float) $prevRow['lon'] : null;
|
|
|
|
$mapCluster = $prevRow ? (int) $prevRow['mapcluster'] : -1;
|
|
|
|
$osmIds = [];
|
|
|
|
|
|
|
|
if ($lat || $lon || $oldLat || $oldLon) {
|
|
|
|
try {
|
|
|
|
$mapCluster = $this->mapGetCluster($mapCluster, $lat, $lon, $oldLat, $oldLon);
|
2023-04-14 06:37:30 +00:00
|
|
|
} catch (\Exception $e) {
|
2023-03-28 00:50:10 +00:00
|
|
|
$logger = \OC::$server->get(LoggerInterface::class);
|
|
|
|
$logger->log(3, 'Error updating map cluster data: '.$e->getMessage(), ['app' => 'memories']);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$osmIds = $this->updatePlacesData($fileId, $lat, $lon);
|
2023-04-14 06:37:30 +00:00
|
|
|
} catch (\Exception $e) {
|
2023-03-28 00:50:10 +00:00
|
|
|
$logger = \OC::$server->get(LoggerInterface::class);
|
|
|
|
$logger->log(3, 'Error updating places data: '.$e->getMessage(), ['app' => 'memories']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NULL if invalid
|
|
|
|
$mapCluster = $mapCluster <= 0 ? null : $mapCluster;
|
|
|
|
|
|
|
|
// Set tzid from location if not present
|
|
|
|
$this->setTzidFromLocation($exif, $osmIds);
|
|
|
|
|
|
|
|
// Return update values
|
|
|
|
return [$lat, $lon, $mapCluster, $osmIds];
|
|
|
|
}
|
|
|
|
|
2023-02-09 05:55:12 +00:00
|
|
|
/**
|
|
|
|
* Add places data for a file.
|
2023-03-08 19:32:57 +00:00
|
|
|
*
|
|
|
|
* @param int $fileId The file ID
|
|
|
|
* @param null|float $lat The latitude of the file
|
|
|
|
* @param null|float $lon The longitude of the file
|
2023-03-28 00:50:10 +00:00
|
|
|
*
|
|
|
|
* @return array The list of osm_id of the places
|
2023-02-09 05:55:12 +00:00
|
|
|
*/
|
2023-03-28 00:50:10 +00:00
|
|
|
protected function updatePlacesData(int $fileId, $lat, $lon): array
|
2023-02-09 05:55:12 +00:00
|
|
|
{
|
|
|
|
// Get GIS type
|
|
|
|
$gisType = \OCA\Memories\Util::placesGISType();
|
|
|
|
|
2023-03-08 19:32:57 +00:00
|
|
|
// Check if valid
|
2023-03-08 19:48:36 +00:00
|
|
|
if ($gisType <= 0) {
|
2023-03-28 00:50:10 +00:00
|
|
|
return [];
|
2023-03-08 19:48:36 +00:00
|
|
|
}
|
2023-03-08 19:32:57 +00:00
|
|
|
|
|
|
|
// Delete previous records
|
|
|
|
$query = $this->connection->getQueryBuilder();
|
|
|
|
$query->delete('memories_places')
|
|
|
|
->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
|
|
|
|
;
|
|
|
|
$query->executeStatement();
|
|
|
|
|
|
|
|
// Just remove from if the point is no longer valid
|
|
|
|
if (null === $lat || null === $lon) {
|
2023-03-28 00:50:10 +00:00
|
|
|
return [];
|
2023-03-08 19:32:57 +00:00
|
|
|
}
|
|
|
|
|
2023-02-09 05:55:12 +00:00
|
|
|
// Construct WHERE clause depending on GIS type
|
|
|
|
$where = null;
|
|
|
|
if (1 === $gisType) {
|
|
|
|
$where = "ST_Contains(geometry, ST_GeomFromText('POINT({$lon} {$lat})'))";
|
|
|
|
} elseif (2 === $gisType) {
|
|
|
|
$where = "POINT('{$lon},{$lat}') <@ geometry";
|
|
|
|
} else {
|
2023-03-28 00:50:10 +00:00
|
|
|
return [];
|
2023-02-09 05:55:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make query to memories_planet table
|
|
|
|
$query = $this->connection->getQueryBuilder();
|
|
|
|
$query->select($query->createFunction('DISTINCT(osm_id)'))
|
|
|
|
->from('memories_planet_geometry')
|
|
|
|
->where($query->createFunction($where))
|
|
|
|
;
|
|
|
|
|
|
|
|
// Cancel out inner rings
|
|
|
|
$query->groupBy('poly_id', 'osm_id');
|
|
|
|
$query->having($query->createFunction('SUM(type_id) > 0'));
|
|
|
|
|
|
|
|
// memories_planet_geometry has no *PREFIX*
|
|
|
|
$sql = str_replace('*PREFIX*memories_planet_geometry', 'memories_planet_geometry', $query->getSQL());
|
|
|
|
|
|
|
|
// Run query
|
2023-03-24 01:12:39 +00:00
|
|
|
$rows = $this->connection->executeQuery($sql)->fetchAll();
|
|
|
|
|
|
|
|
// Insert records in transaction
|
|
|
|
$this->connection->beginTransaction();
|
2023-02-09 05:55:12 +00:00
|
|
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
$query = $this->connection->getQueryBuilder();
|
|
|
|
$query->insert('memories_places')
|
|
|
|
->values([
|
|
|
|
'fileid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
|
|
|
|
'osm_id' => $query->createNamedParameter($row['osm_id'], IQueryBuilder::PARAM_INT),
|
|
|
|
])
|
|
|
|
;
|
|
|
|
$query->executeStatement();
|
|
|
|
}
|
2023-03-24 01:12:39 +00:00
|
|
|
|
|
|
|
$this->connection->commit();
|
2023-03-28 00:50:10 +00:00
|
|
|
|
|
|
|
// Return list of osm_id
|
|
|
|
return array_map(fn ($row) => $row['osm_id'], $rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set timezone offset from location if not present.
|
|
|
|
*
|
|
|
|
* @param array $exif The exif data
|
|
|
|
* @param array $osmIds The list of osm_id of the places
|
|
|
|
*/
|
|
|
|
private function setTzidFromLocation(array &$exif, array $osmIds): void
|
|
|
|
{
|
|
|
|
// Make sure we have some places
|
|
|
|
if (empty($osmIds)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get timezone offset from places
|
|
|
|
$query = $this->connection->getQueryBuilder();
|
|
|
|
$query->select('name')
|
|
|
|
->from('memories_planet')
|
|
|
|
->where($query->expr()->in('osm_id', $query->createNamedParameter($osmIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
|
|
|
->andWhere($query->expr()->eq('admin_level', $query->createNamedParameter(-7, IQueryBuilder::PARAM_INT)))
|
|
|
|
;
|
|
|
|
|
|
|
|
// Get name of timezone
|
|
|
|
$tzName = $query->executeQuery()->fetchOne();
|
|
|
|
if ($tzName) {
|
|
|
|
$exif['LocationTZID'] = $tzName;
|
|
|
|
}
|
2023-02-09 05:55:12 +00:00
|
|
|
}
|
2023-04-14 07:00:19 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Read coordinates from array and round to 6 decimal places.
|
|
|
|
*
|
|
|
|
* Modifies the array to remove invalid coordinates.
|
|
|
|
*/
|
|
|
|
private static function readCoord(array &$exif)
|
|
|
|
{
|
2023-04-14 07:02:16 +00:00
|
|
|
$lat = \array_key_exists(LAT_KEY, $exif) ? round((float) $exif[LAT_KEY], 6) : null;
|
|
|
|
$lon = \array_key_exists(LON_KEY, $exif) ? round((float) $exif[LON_KEY], 6) : null;
|
2023-04-14 07:00:19 +00:00
|
|
|
|
|
|
|
// Make sure we have valid coordinates
|
|
|
|
if (null === $lat || null === $lon || abs($lat) > 90 || abs($lon) > 180 || ($lat < 0.00001 && $lon < 0.00001)) {
|
|
|
|
$lat = $lon = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove invalid coordinates
|
2023-04-14 07:02:16 +00:00
|
|
|
if (null === $lat && \array_key_exists(LAT_KEY, $exif)) {
|
2023-04-14 07:00:19 +00:00
|
|
|
unset($exif[LAT_KEY]);
|
|
|
|
}
|
2023-04-14 07:02:16 +00:00
|
|
|
if (null === $lon && \array_key_exists(LON_KEY, $exif)) {
|
2023-04-14 07:00:19 +00:00
|
|
|
unset($exif[LON_KEY]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return [$lat, $lon];
|
|
|
|
}
|
2023-02-09 05:55:12 +00:00
|
|
|
}
|