Skip to content

Commit

Permalink
Add CLI to reverse geocode pictures' coordinates
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <louis@chmn.me>
  • Loading branch information
artonge committed Oct 5, 2022
1 parent 1cc3625 commit 3d80638
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 8 deletions.
15 changes: 9 additions & 6 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
<?xml version="1.0"?>
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>photos</id>
<name>Photos</name>
<summary>Your memories under your control</summary>
<description>Your memories under your control</description>
<version>1.8.0</version>
<licence>agpl</licence>
<author mail="skjnldsv@protonmail.com">John Molakvoæ</author>
<author mail="skjnldsv@protonmail.com">John Molakvoæ</author>
<namespace>Photos</namespace>
<category>multimedia</category>
<types>
<dav />
<authentication />
</types>

<website>https://github.com/nextcloud/photos</website>
<bugs>https://github.com/nextcloud/photos/issues</bugs>
<website>https://github.com/nextcloud/photos</website>
<bugs>https://github.com/nextcloud/photos/issues</bugs>
<repository>https://github.com/nextcloud/photos.git</repository>
<default_enable />
<dependencies>
Expand All @@ -30,6 +29,10 @@
</navigation>
</navigations>

<commands>
<command>OCA\Photos\Command\ReverseGeoCodeMedia</command>
</commands>

<sabre>
<collections>
<collection>OCA\Photos\Sabre\RootCollection</collection>
Expand All @@ -39,4 +42,4 @@
<plugin>OCA\Photos\Sabre\Album\PropFindPlugin</plugin>
</plugins>
</sabre>
</info>
</info>
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@
"vimeo/psalm": "^4.22",
"sabre/dav": "^4.2.1",
"nextcloud/ocp": "dev-master"
},
"require": {
"hexogen/kdtree": "^0.2.0"
}
}
66 changes: 64 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use OCA\Photos\Listener\SabrePluginAuthInitListener;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\Photos\Listener\CacheEntryRemovedListener;
use OCA\Photos\Listener\ReverseGeoCodeOnNodeUpdateListener;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
Expand Down Expand Up @@ -66,7 +67,9 @@ public function register(IRegistrationContext $context): void {
/** Register $principalBackend for the DAV collection */
$context->registerServiceAlias('principalBackend', Principal::class);
$context->registerEventListener(CacheEntryRemovedEvent::class, CacheEntryRemovedListener::class);
$context->registerEventListener(CacheEntryUpdatedEvent::class, ReverseGeoCodeOnNodeUpdateListener::class);
$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);
require_once __DIR__ . '/../../vendor/autoload.php';
}

public function boot(IBootContext $context): void {
Expand Down
71 changes: 71 additions & 0 deletions lib/Command/DownloadReverseGeocodingFiles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Louis Chemineau <louis@chmn.me>
*
* @author Louis Chemineau <louis@chmn.me>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Photos\Service;

use OCA\Photos\Service\ReverseGeoCoderService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class DownloadReverseGeocodingFiles extends Command {
private ReverseGeoCoderService $rgcService;

public function __construct(
ReverseGeoCoderService $rgcService
) {
parent::__construct();
$this->rgcService = $rgcService;
}

/**
* Configure the command
*
* @return void
*/
protected function configure() {
$this->setName('photos:update-locations-files')
->setDescription('Update the necessary reverse geocoding files');
}

/**
* Execute the command
*
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
try {
$this->rgcService->initCities1000KdTree(true);
} catch (\Exception $ex) {
$output->writeln('<error>Failed to update reverse geocoding files</error>');
$output->writeln($ex->getMessage());
return 1;
}

return 0;
}
}
141 changes: 141 additions & 0 deletions lib/Command/ReverseGeoCodeMedia.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Louis Chemineau <louis@chmn.me>
*
* @author Louis Chemineau <louis@chmn.me>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Photos\Command;

use OC\Metadata\MetadataManager;
use OCP\IUser;
use OCP\IConfig;
use OCP\IUserManager;
use OCP\Files\IRootFolder;
use OCP\Files\Folder;
use OCP\BackgroundJob\IJobList;
use OCA\Photos\Service\ReverseGeoCoderService;
use OCA\Photos\Service\LocationTagManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ReverseGeoCodeMedia extends Command {
private ReverseGeoCoderService $rgcService;
private IRootFolder $rootFolder;
private MetadataManager $metadataManager;
private LocationTagManager $locationTagManager;
private IConfig $config;
private IUserManager $userManager;

public function __construct(
ReverseGeoCoderService $rgcService,
IJobList $jobList,
IRootFolder $rootFolder,
MetadataManager $metadataManager,
LocationTagManager $locationTagManager,
IConfig $config,
IUserManager $userManager
) {
parent::__construct();
$this->rgcService = $rgcService;
$this->config = $config;
$this->jobList = $jobList;
$this->rootFolder = $rootFolder;
$this->metadataManager = $metadataManager;
$this->locationTagManager = $locationTagManager;
$this->userManager = $userManager;
}

/**
* Configure the command
*
* @return void
*/
protected function configure() {
$this->setName('photos:reverse-geocode-media')
->setDescription('Reverse geocode coordinates of users\' media')
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Limit the geocoding to the given user.', null);
}

/**
* Execute the command
*
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
if (!$this->config->getSystemValueBool('enable_file_metadata', true)) {
throw new \Exception('File metadata is not enabled.');
}

$this->rgcService->initCities1000KdTree();

$userId = $input->getOption('user');
if ($userId === null) {
$this->scanForAllUsers();
} else {
$this->scanFilesForUser($userId);
}

return 0;
}

private function scanForAllUsers() {
$users = $this->userManager->search('');

foreach ($users as $user) {
$this->scanFilesForUser($user->getUID());
}
}

private function scanFilesForUser(string $userId) {
$userFolder = $this->rootFolder->getUserFolder($userId);
$this->scanFolder($userFolder);
}

private function scanFolder(Folder $folder) {
foreach ($folder->getDirectoryListing() as $node) {
if ($node instanceof Folder) {
$this->scanFolder($node);
continue;
}

if (!str_starts_with($node->getMimeType(), 'image')) {
continue;
}

$gpsMetadata = $this->metadataManager->fetchMetadataFor('gps', [$node->getId()])[$node->getId()];
$metadata = $gpsMetadata->getMetadata();
$latitude = $metadata['latitude'];
$longitude = $metadata['longitude'];

if ($latitude === null || $longitude === null) {
continue;
}

$locationId = $this->rgcService->getLocationIdForCoordinates($latitude, $longitude);
$this->locationTagManager->tagFileWithLocationId($node, $locationId);
}
}
}
Loading

0 comments on commit 3d80638

Please sign in to comment.