memories/lib/Db/TimelineWritePlaces.php

187 lines
6.1 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\Memories\Db;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;
const LAT_KEY = 'GPSLatitude';
const LON_KEY = 'GPSLongitude';
trait TimelineWritePlaces
{
protected IDBConnection $connection;
/**
* Add places data for a file.
*
* @param int $fileId The file ID
* @param null|float $lat The latitude of the file
* @param null|float $lon The longitude of the file
*
* @return array The list of osm_id of the places
*/
public function updatePlacesData(int $fileId, $lat, $lon): array
{
// Get GIS type
$gisType = \OCA\Memories\Util::placesGISType();
// Check if valid
if ($gisType <= 0) {
return [];
}
// 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) {
return [];
}
// Get places
$rows = \OC::$server->get(\OCA\Memories\Service\Places::class)->queryPoint($lat, $lon);
// Get last ID, i.e. the ID with highest admin_level but <= 8
$crows = array_filter($rows, static fn ($row) => $row['admin_level'] <= 8);
$markRow = array_pop($crows);
// Insert records in transaction
$this->connection->beginTransaction();
foreach ($rows as $row) {
$isMark = $markRow && $row['osm_id'] === $markRow['osm_id'];
// Insert the place
$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),
'mark' => $query->createNamedParameter($isMark, IQueryBuilder::PARAM_BOOL),
])
;
$query->executeStatement();
}
$this->connection->commit();
// Return list of osm_id
return array_map(static fn ($row) => $row['osm_id'], $rows);
}
/**
* Process the location part of exif data.
*
* Also update the exif data with the tzid from location (LocationTZID)
* Performs an in-place update of the exif data.
*
* @param int $fileId The file ID
* @param array $exif The exif data (will change)
* @param ?array $prevRow The previous row of data
*
* @return array Update values
*/
protected function processExifLocation(int $fileId, array &$exif, ?array $prevRow): array
{
// Store location data
[$lat, $lon] = self::readCoord($exif);
$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);
} catch (\Exception $e) {
$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);
} catch (\Exception $e) {
$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];
}
/**
* 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->expr()->literal(-7, IQueryBuilder::PARAM_INT)))
;
// Get name of timezone
$tzName = $query->executeQuery()->fetchOne();
if ($tzName) {
$exif['LocationTZID'] = $tzName;
}
}
/**
* Read coordinates from array and round to 6 decimal places.
*
* Modifies the array to remove invalid coordinates.
*
* @return (null|float)[]
*
* @psalm-return list{float|null, float|null}
*/
private static function readCoord(array &$exif): array
{
$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;
// Make sure we have valid coordinates
if (null === $lat || null === $lon
|| abs($lat) > 90 || abs($lon) > 180
|| (abs($lat) < 0.00001 && abs($lon) < 0.00001)) {
$lat = $lon = null;
}
// Remove invalid coordinates
if (null === $lat && \array_key_exists(LAT_KEY, $exif)) {
unset($exif[LAT_KEY]);
}
if (null === $lon && \array_key_exists(LON_KEY, $exif)) {
unset($exif[LON_KEY]);
}
return [$lat, $lon];
}
}