Обновление клиента

This commit is contained in:
root
2026-03-05 13:40:40 +00:00
parent 34bcd34979
commit b8905de237
4147 changed files with 748711 additions and 7 deletions
+25
View File
@@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitPhotos::getLoader();
+579
View File
@@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}
+359
View File
@@ -0,0 +1,359 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+90
View File
@@ -0,0 +1,90 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Hexogen\\KDTree\\Exception\\ValidationException' => $vendorDir . '/hexogen/kdtree/src/Exception/ValidationException.php',
'Hexogen\\KDTree\\FSKDTree' => $vendorDir . '/hexogen/kdtree/src/FSKDTree.php',
'Hexogen\\KDTree\\FSNode' => $vendorDir . '/hexogen/kdtree/src/FSNode.php',
'Hexogen\\KDTree\\FSTreePersister' => $vendorDir . '/hexogen/kdtree/src/FSTreePersister.php',
'Hexogen\\KDTree\\Interfaces\\ItemFactoryInterface' => $vendorDir . '/hexogen/kdtree/src/Interfaces/ItemFactoryInterface.php',
'Hexogen\\KDTree\\Interfaces\\ItemInterface' => $vendorDir . '/hexogen/kdtree/src/Interfaces/ItemInterface.php',
'Hexogen\\KDTree\\Interfaces\\ItemListInterface' => $vendorDir . '/hexogen/kdtree/src/Interfaces/ItemListInterface.php',
'Hexogen\\KDTree\\Interfaces\\KDTreeInterface' => $vendorDir . '/hexogen/kdtree/src/Interfaces/KDTreeInterface.php',
'Hexogen\\KDTree\\Interfaces\\NodeInterface' => $vendorDir . '/hexogen/kdtree/src/Interfaces/NodeInterface.php',
'Hexogen\\KDTree\\Interfaces\\PointInterface' => $vendorDir . '/hexogen/kdtree/src/Interfaces/PointInterface.php',
'Hexogen\\KDTree\\Interfaces\\SearchAbstract' => $vendorDir . '/hexogen/kdtree/src/Interfaces/SearchAbstract.php',
'Hexogen\\KDTree\\Interfaces\\TreePersisterInterface' => $vendorDir . '/hexogen/kdtree/src/Interfaces/TreePersisterInterface.php',
'Hexogen\\KDTree\\Item' => $vendorDir . '/hexogen/kdtree/src/Item.php',
'Hexogen\\KDTree\\ItemFactory' => $vendorDir . '/hexogen/kdtree/src/ItemFactory.php',
'Hexogen\\KDTree\\ItemList' => $vendorDir . '/hexogen/kdtree/src/ItemList.php',
'Hexogen\\KDTree\\KDTree' => $vendorDir . '/hexogen/kdtree/src/KDTree.php',
'Hexogen\\KDTree\\NearestSearch' => $vendorDir . '/hexogen/kdtree/src/NearestSearch.php',
'Hexogen\\KDTree\\Node' => $vendorDir . '/hexogen/kdtree/src/Node.php',
'Hexogen\\KDTree\\Point' => $vendorDir . '/hexogen/kdtree/src/Point.php',
'OCA\\Photos\\Album\\AlbumFile' => $baseDir . '/lib/Album/AlbumFile.php',
'OCA\\Photos\\Album\\AlbumInfo' => $baseDir . '/lib/Album/AlbumInfo.php',
'OCA\\Photos\\Album\\AlbumMapper' => $baseDir . '/lib/Album/AlbumMapper.php',
'OCA\\Photos\\Album\\AlbumWithFiles' => $baseDir . '/lib/Album/AlbumWithFiles.php',
'OCA\\Photos\\AppInfo\\Application' => $baseDir . '/lib/AppInfo/Application.php',
'OCA\\Photos\\Command\\AlbumAddCommand' => $baseDir . '/lib/Command/AlbumAddCommand.php',
'OCA\\Photos\\Command\\AlbumCreateCommand' => $baseDir . '/lib/Command/AlbumCreateCommand.php',
'OCA\\Photos\\Command\\UpdateReverseGeocodingFilesCommand' => $baseDir . '/lib/Command/UpdateReverseGeocodingFilesCommand.php',
'OCA\\Photos\\Controller\\AlbumsController' => $baseDir . '/lib/Controller/AlbumsController.php',
'OCA\\Photos\\Controller\\ApiController' => $baseDir . '/lib/Controller/ApiController.php',
'OCA\\Photos\\Controller\\PageController' => $baseDir . '/lib/Controller/PageController.php',
'OCA\\Photos\\Controller\\PreviewController' => $baseDir . '/lib/Controller/PreviewController.php',
'OCA\\Photos\\Controller\\PublicAlbumController' => $baseDir . '/lib/Controller/PublicAlbumController.php',
'OCA\\Photos\\Controller\\PublicPreviewController' => $baseDir . '/lib/Controller/PublicPreviewController.php',
'OCA\\Photos\\DB\\PhotosFile' => $baseDir . '/lib/DB/PhotosFile.php',
'OCA\\Photos\\DB\\Place\\PlaceFile' => $baseDir . '/lib/DB/Place/PlaceFile.php',
'OCA\\Photos\\DB\\Place\\PlaceInfo' => $baseDir . '/lib/DB/Place/PlaceInfo.php',
'OCA\\Photos\\DB\\Place\\PlaceMapper' => $baseDir . '/lib/DB/Place/PlaceMapper.php',
'OCA\\Photos\\Dashboard\\OnThisDay' => $baseDir . '/lib/Dashboard/OnThisDay.php',
'OCA\\Photos\\Exception\\AlreadyInAlbumException' => $baseDir . '/lib/Exception/AlreadyInAlbumException.php',
'OCA\\Photos\\Filters\\DateRangeFilter' => $baseDir . '/lib/Filters/DateRangeFilter.php',
'OCA\\Photos\\Filters\\FiltersManager' => $baseDir . '/lib/Filters/FiltersManager.php',
'OCA\\Photos\\Filters\\IFilter' => $baseDir . '/lib/Filters/IFilter.php',
'OCA\\Photos\\Filters\\PlacesFilter' => $baseDir . '/lib/Filters/PlacesFilter.php',
'OCA\\Photos\\Jobs\\AutomaticPlaceMapperJob' => $baseDir . '/lib/Jobs/AutomaticPlaceMapperJob.php',
'OCA\\Photos\\Listener\\AlbumsManagementEventListener' => $baseDir . '/lib/Listener/AlbumsManagementEventListener.php',
'OCA\\Photos\\Listener\\CSPListener' => $baseDir . '/lib/Listener/CSPListener.php',
'OCA\\Photos\\Listener\\ExifMetadataProvider' => $baseDir . '/lib/Listener/ExifMetadataProvider.php',
'OCA\\Photos\\Listener\\LoadSidebarScripts' => $baseDir . '/lib/Listener/LoadSidebarScripts.php',
'OCA\\Photos\\Listener\\OriginalDateTimeMetadataProvider' => $baseDir . '/lib/Listener/OriginalDateTimeMetadataProvider.php',
'OCA\\Photos\\Listener\\PlaceMetadataProvider' => $baseDir . '/lib/Listener/PlaceMetadataProvider.php',
'OCA\\Photos\\Listener\\SabrePluginAuthInitListener' => $baseDir . '/lib/Listener/SabrePluginAuthInitListener.php',
'OCA\\Photos\\Listener\\SizeMetadataProvider' => $baseDir . '/lib/Listener/SizeMetadataProvider.php',
'OCA\\Photos\\Listener\\TagListener' => $baseDir . '/lib/Listener/TagListener.php',
'OCA\\Photos\\Migration\\Version20000Date20220727125801' => $baseDir . '/lib/Migration/Version20000Date20220727125801.php',
'OCA\\Photos\\Migration\\Version20001Date20220830131446' => $baseDir . '/lib/Migration/Version20001Date20220830131446.php',
'OCA\\Photos\\Migration\\Version20003Date20221102170153' => $baseDir . '/lib/Migration/Version20003Date20221102170153.php',
'OCA\\Photos\\Migration\\Version20003Date20221103094628' => $baseDir . '/lib/Migration/Version20003Date20221103094628.php',
'OCA\\Photos\\Migration\\Version30000Date20240417075405' => $baseDir . '/lib/Migration/Version30000Date20240417075405.php',
'OCA\\Photos\\Migration\\Version32000Date20250507132617' => $baseDir . '/lib/Migration/Version32000Date20250507132617.php',
'OCA\\Photos\\RepairStep\\InitMetadata' => $baseDir . '/lib/RepairStep/InitMetadata.php',
'OCA\\Photos\\Sabre\\Album\\AlbumPhoto' => $baseDir . '/lib/Sabre/Album/AlbumPhoto.php',
'OCA\\Photos\\Sabre\\Album\\AlbumRoot' => $baseDir . '/lib/Sabre/Album/AlbumRoot.php',
'OCA\\Photos\\Sabre\\Album\\AlbumRootBase' => $baseDir . '/lib/Sabre/Album/AlbumRootBase.php',
'OCA\\Photos\\Sabre\\Album\\AlbumsHome' => $baseDir . '/lib/Sabre/Album/AlbumsHome.php',
'OCA\\Photos\\Sabre\\Album\\PublicAlbumPhoto' => $baseDir . '/lib/Sabre/Album/PublicAlbumPhoto.php',
'OCA\\Photos\\Sabre\\Album\\PublicAlbumRoot' => $baseDir . '/lib/Sabre/Album/PublicAlbumRoot.php',
'OCA\\Photos\\Sabre\\Album\\SharedAlbumRoot' => $baseDir . '/lib/Sabre/Album/SharedAlbumRoot.php',
'OCA\\Photos\\Sabre\\Album\\SharedAlbumsHome' => $baseDir . '/lib/Sabre/Album/SharedAlbumsHome.php',
'OCA\\Photos\\Sabre\\CollectionPhoto' => $baseDir . '/lib/Sabre/CollectionPhoto.php',
'OCA\\Photos\\Sabre\\PhotosHome' => $baseDir . '/lib/Sabre/PhotosHome.php',
'OCA\\Photos\\Sabre\\Place\\PlacePhoto' => $baseDir . '/lib/Sabre/Place/PlacePhoto.php',
'OCA\\Photos\\Sabre\\Place\\PlaceRoot' => $baseDir . '/lib/Sabre/Place/PlaceRoot.php',
'OCA\\Photos\\Sabre\\Place\\PlacesHome' => $baseDir . '/lib/Sabre/Place/PlacesHome.php',
'OCA\\Photos\\Sabre\\PropFindPlugin' => $baseDir . '/lib/Sabre/PropFindPlugin.php',
'OCA\\Photos\\Sabre\\PublicAlbumAuthBackend' => $baseDir . '/lib/Sabre/PublicAlbumAuthBackend.php',
'OCA\\Photos\\Sabre\\PublicRootCollection' => $baseDir . '/lib/Sabre/PublicRootCollection.php',
'OCA\\Photos\\Sabre\\RootCollection' => $baseDir . '/lib/Sabre/RootCollection.php',
'OCA\\Photos\\Service\\MediaPlaceManager' => $baseDir . '/lib/Service/MediaPlaceManager.php',
'OCA\\Photos\\Service\\ReverseGeoCoderService' => $baseDir . '/lib/Service/ReverseGeoCoderService.php',
'OCA\\Photos\\Service\\UserConfigService' => $baseDir . '/lib/Service/UserConfigService.php',
);
+9
View File
@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);
+11
View File
@@ -0,0 +1,11 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'OCA\\Photos\\' => array($baseDir . '/lib'),
'Hexogen\\KDTree\\' => array($vendorDir . '/hexogen/kdtree/src'),
);
+39
View File
@@ -0,0 +1,39 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitPhotos
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitPhotos', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitPhotos', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitPhotos::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
return $loader;
}
}
+124
View File
@@ -0,0 +1,124 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitPhotos
{
public static $prefixLengthsPsr4 = array (
'O' =>
array (
'OCA\\Photos\\' => 11,
),
'H' =>
array (
'Hexogen\\KDTree\\' => 15,
),
);
public static $prefixDirsPsr4 = array (
'OCA\\Photos\\' =>
array (
0 => __DIR__ . '/../..' . '/lib',
),
'Hexogen\\KDTree\\' =>
array (
0 => __DIR__ . '/..' . '/hexogen/kdtree/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Hexogen\\KDTree\\Exception\\ValidationException' => __DIR__ . '/..' . '/hexogen/kdtree/src/Exception/ValidationException.php',
'Hexogen\\KDTree\\FSKDTree' => __DIR__ . '/..' . '/hexogen/kdtree/src/FSKDTree.php',
'Hexogen\\KDTree\\FSNode' => __DIR__ . '/..' . '/hexogen/kdtree/src/FSNode.php',
'Hexogen\\KDTree\\FSTreePersister' => __DIR__ . '/..' . '/hexogen/kdtree/src/FSTreePersister.php',
'Hexogen\\KDTree\\Interfaces\\ItemFactoryInterface' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/ItemFactoryInterface.php',
'Hexogen\\KDTree\\Interfaces\\ItemInterface' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/ItemInterface.php',
'Hexogen\\KDTree\\Interfaces\\ItemListInterface' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/ItemListInterface.php',
'Hexogen\\KDTree\\Interfaces\\KDTreeInterface' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/KDTreeInterface.php',
'Hexogen\\KDTree\\Interfaces\\NodeInterface' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/NodeInterface.php',
'Hexogen\\KDTree\\Interfaces\\PointInterface' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/PointInterface.php',
'Hexogen\\KDTree\\Interfaces\\SearchAbstract' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/SearchAbstract.php',
'Hexogen\\KDTree\\Interfaces\\TreePersisterInterface' => __DIR__ . '/..' . '/hexogen/kdtree/src/Interfaces/TreePersisterInterface.php',
'Hexogen\\KDTree\\Item' => __DIR__ . '/..' . '/hexogen/kdtree/src/Item.php',
'Hexogen\\KDTree\\ItemFactory' => __DIR__ . '/..' . '/hexogen/kdtree/src/ItemFactory.php',
'Hexogen\\KDTree\\ItemList' => __DIR__ . '/..' . '/hexogen/kdtree/src/ItemList.php',
'Hexogen\\KDTree\\KDTree' => __DIR__ . '/..' . '/hexogen/kdtree/src/KDTree.php',
'Hexogen\\KDTree\\NearestSearch' => __DIR__ . '/..' . '/hexogen/kdtree/src/NearestSearch.php',
'Hexogen\\KDTree\\Node' => __DIR__ . '/..' . '/hexogen/kdtree/src/Node.php',
'Hexogen\\KDTree\\Point' => __DIR__ . '/..' . '/hexogen/kdtree/src/Point.php',
'OCA\\Photos\\Album\\AlbumFile' => __DIR__ . '/../..' . '/lib/Album/AlbumFile.php',
'OCA\\Photos\\Album\\AlbumInfo' => __DIR__ . '/../..' . '/lib/Album/AlbumInfo.php',
'OCA\\Photos\\Album\\AlbumMapper' => __DIR__ . '/../..' . '/lib/Album/AlbumMapper.php',
'OCA\\Photos\\Album\\AlbumWithFiles' => __DIR__ . '/../..' . '/lib/Album/AlbumWithFiles.php',
'OCA\\Photos\\AppInfo\\Application' => __DIR__ . '/../..' . '/lib/AppInfo/Application.php',
'OCA\\Photos\\Command\\AlbumAddCommand' => __DIR__ . '/../..' . '/lib/Command/AlbumAddCommand.php',
'OCA\\Photos\\Command\\AlbumCreateCommand' => __DIR__ . '/../..' . '/lib/Command/AlbumCreateCommand.php',
'OCA\\Photos\\Command\\UpdateReverseGeocodingFilesCommand' => __DIR__ . '/../..' . '/lib/Command/UpdateReverseGeocodingFilesCommand.php',
'OCA\\Photos\\Controller\\AlbumsController' => __DIR__ . '/../..' . '/lib/Controller/AlbumsController.php',
'OCA\\Photos\\Controller\\ApiController' => __DIR__ . '/../..' . '/lib/Controller/ApiController.php',
'OCA\\Photos\\Controller\\PageController' => __DIR__ . '/../..' . '/lib/Controller/PageController.php',
'OCA\\Photos\\Controller\\PreviewController' => __DIR__ . '/../..' . '/lib/Controller/PreviewController.php',
'OCA\\Photos\\Controller\\PublicAlbumController' => __DIR__ . '/../..' . '/lib/Controller/PublicAlbumController.php',
'OCA\\Photos\\Controller\\PublicPreviewController' => __DIR__ . '/../..' . '/lib/Controller/PublicPreviewController.php',
'OCA\\Photos\\DB\\PhotosFile' => __DIR__ . '/../..' . '/lib/DB/PhotosFile.php',
'OCA\\Photos\\DB\\Place\\PlaceFile' => __DIR__ . '/../..' . '/lib/DB/Place/PlaceFile.php',
'OCA\\Photos\\DB\\Place\\PlaceInfo' => __DIR__ . '/../..' . '/lib/DB/Place/PlaceInfo.php',
'OCA\\Photos\\DB\\Place\\PlaceMapper' => __DIR__ . '/../..' . '/lib/DB/Place/PlaceMapper.php',
'OCA\\Photos\\Dashboard\\OnThisDay' => __DIR__ . '/../..' . '/lib/Dashboard/OnThisDay.php',
'OCA\\Photos\\Exception\\AlreadyInAlbumException' => __DIR__ . '/../..' . '/lib/Exception/AlreadyInAlbumException.php',
'OCA\\Photos\\Filters\\DateRangeFilter' => __DIR__ . '/../..' . '/lib/Filters/DateRangeFilter.php',
'OCA\\Photos\\Filters\\FiltersManager' => __DIR__ . '/../..' . '/lib/Filters/FiltersManager.php',
'OCA\\Photos\\Filters\\IFilter' => __DIR__ . '/../..' . '/lib/Filters/IFilter.php',
'OCA\\Photos\\Filters\\PlacesFilter' => __DIR__ . '/../..' . '/lib/Filters/PlacesFilter.php',
'OCA\\Photos\\Jobs\\AutomaticPlaceMapperJob' => __DIR__ . '/../..' . '/lib/Jobs/AutomaticPlaceMapperJob.php',
'OCA\\Photos\\Listener\\AlbumsManagementEventListener' => __DIR__ . '/../..' . '/lib/Listener/AlbumsManagementEventListener.php',
'OCA\\Photos\\Listener\\CSPListener' => __DIR__ . '/../..' . '/lib/Listener/CSPListener.php',
'OCA\\Photos\\Listener\\ExifMetadataProvider' => __DIR__ . '/../..' . '/lib/Listener/ExifMetadataProvider.php',
'OCA\\Photos\\Listener\\LoadSidebarScripts' => __DIR__ . '/../..' . '/lib/Listener/LoadSidebarScripts.php',
'OCA\\Photos\\Listener\\OriginalDateTimeMetadataProvider' => __DIR__ . '/../..' . '/lib/Listener/OriginalDateTimeMetadataProvider.php',
'OCA\\Photos\\Listener\\PlaceMetadataProvider' => __DIR__ . '/../..' . '/lib/Listener/PlaceMetadataProvider.php',
'OCA\\Photos\\Listener\\SabrePluginAuthInitListener' => __DIR__ . '/../..' . '/lib/Listener/SabrePluginAuthInitListener.php',
'OCA\\Photos\\Listener\\SizeMetadataProvider' => __DIR__ . '/../..' . '/lib/Listener/SizeMetadataProvider.php',
'OCA\\Photos\\Listener\\TagListener' => __DIR__ . '/../..' . '/lib/Listener/TagListener.php',
'OCA\\Photos\\Migration\\Version20000Date20220727125801' => __DIR__ . '/../..' . '/lib/Migration/Version20000Date20220727125801.php',
'OCA\\Photos\\Migration\\Version20001Date20220830131446' => __DIR__ . '/../..' . '/lib/Migration/Version20001Date20220830131446.php',
'OCA\\Photos\\Migration\\Version20003Date20221102170153' => __DIR__ . '/../..' . '/lib/Migration/Version20003Date20221102170153.php',
'OCA\\Photos\\Migration\\Version20003Date20221103094628' => __DIR__ . '/../..' . '/lib/Migration/Version20003Date20221103094628.php',
'OCA\\Photos\\Migration\\Version30000Date20240417075405' => __DIR__ . '/../..' . '/lib/Migration/Version30000Date20240417075405.php',
'OCA\\Photos\\Migration\\Version32000Date20250507132617' => __DIR__ . '/../..' . '/lib/Migration/Version32000Date20250507132617.php',
'OCA\\Photos\\RepairStep\\InitMetadata' => __DIR__ . '/../..' . '/lib/RepairStep/InitMetadata.php',
'OCA\\Photos\\Sabre\\Album\\AlbumPhoto' => __DIR__ . '/../..' . '/lib/Sabre/Album/AlbumPhoto.php',
'OCA\\Photos\\Sabre\\Album\\AlbumRoot' => __DIR__ . '/../..' . '/lib/Sabre/Album/AlbumRoot.php',
'OCA\\Photos\\Sabre\\Album\\AlbumRootBase' => __DIR__ . '/../..' . '/lib/Sabre/Album/AlbumRootBase.php',
'OCA\\Photos\\Sabre\\Album\\AlbumsHome' => __DIR__ . '/../..' . '/lib/Sabre/Album/AlbumsHome.php',
'OCA\\Photos\\Sabre\\Album\\PublicAlbumPhoto' => __DIR__ . '/../..' . '/lib/Sabre/Album/PublicAlbumPhoto.php',
'OCA\\Photos\\Sabre\\Album\\PublicAlbumRoot' => __DIR__ . '/../..' . '/lib/Sabre/Album/PublicAlbumRoot.php',
'OCA\\Photos\\Sabre\\Album\\SharedAlbumRoot' => __DIR__ . '/../..' . '/lib/Sabre/Album/SharedAlbumRoot.php',
'OCA\\Photos\\Sabre\\Album\\SharedAlbumsHome' => __DIR__ . '/../..' . '/lib/Sabre/Album/SharedAlbumsHome.php',
'OCA\\Photos\\Sabre\\CollectionPhoto' => __DIR__ . '/../..' . '/lib/Sabre/CollectionPhoto.php',
'OCA\\Photos\\Sabre\\PhotosHome' => __DIR__ . '/../..' . '/lib/Sabre/PhotosHome.php',
'OCA\\Photos\\Sabre\\Place\\PlacePhoto' => __DIR__ . '/../..' . '/lib/Sabre/Place/PlacePhoto.php',
'OCA\\Photos\\Sabre\\Place\\PlaceRoot' => __DIR__ . '/../..' . '/lib/Sabre/Place/PlaceRoot.php',
'OCA\\Photos\\Sabre\\Place\\PlacesHome' => __DIR__ . '/../..' . '/lib/Sabre/Place/PlacesHome.php',
'OCA\\Photos\\Sabre\\PropFindPlugin' => __DIR__ . '/../..' . '/lib/Sabre/PropFindPlugin.php',
'OCA\\Photos\\Sabre\\PublicAlbumAuthBackend' => __DIR__ . '/../..' . '/lib/Sabre/PublicAlbumAuthBackend.php',
'OCA\\Photos\\Sabre\\PublicRootCollection' => __DIR__ . '/../..' . '/lib/Sabre/PublicRootCollection.php',
'OCA\\Photos\\Sabre\\RootCollection' => __DIR__ . '/../..' . '/lib/Sabre/RootCollection.php',
'OCA\\Photos\\Service\\MediaPlaceManager' => __DIR__ . '/../..' . '/lib/Service/MediaPlaceManager.php',
'OCA\\Photos\\Service\\ReverseGeoCoderService' => __DIR__ . '/../..' . '/lib/Service/ReverseGeoCoderService.php',
'OCA\\Photos\\Service\\UserConfigService' => __DIR__ . '/../..' . '/lib/Service/UserConfigService.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitPhotos::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitPhotos::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitPhotos::$classMap;
}, null, ClassLoader::class);
}
}
+70
View File
@@ -0,0 +1,70 @@
{
"packages": [
{
"name": "hexogen/kdtree",
"version": "v0.2.6",
"version_normalized": "0.2.6.0",
"source": {
"type": "git",
"url": "https://github.com/hexogen/kdtree.git",
"reference": "fbcd2b472c1bb3401929b38144ddd66f8fd14c65"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hexogen/kdtree/zipball/fbcd2b472c1bb3401929b38144ddd66f8fd14c65",
"reference": "fbcd2b472c1bb3401929b38144ddd66f8fd14c65",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"league/csv": "^9.7.0",
"mockery/mockery": "dev-main",
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.5.0"
},
"time": "2024-09-15T21:06:32+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Hexogen\\KDTree\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Volodymyr Basarab",
"email": "volodymyrbas@gmail.com",
"homepage": "https://github.com/hexogen",
"role": "Developer"
}
],
"description": "file system KDTree index",
"homepage": "https://github.com/hexogen/kdtree",
"keywords": [
"algorithms",
"data structures",
"hexogen",
"kdtree",
"search"
],
"support": {
"issues": "https://github.com/hexogen/kdtree/issues",
"source": "https://github.com/hexogen/kdtree/tree/v0.2.6"
},
"install-path": "../hexogen/kdtree"
}
],
"dev": false,
"dev-package-names": []
}
+32
View File
@@ -0,0 +1,32 @@
<?php return array(
'root' => array(
'name' => 'nextcloud/photos',
'pretty_version' => 'dev-stable32',
'version' => 'dev-stable32',
'reference' => 'ad01c539752e44dd356639b4193a9132feb002e1',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => false,
),
'versions' => array(
'hexogen/kdtree' => array(
'pretty_version' => 'v0.2.6',
'version' => '0.2.6.0',
'reference' => 'fbcd2b472c1bb3401929b38144ddd66f8fd14c65',
'type' => 'library',
'install_path' => __DIR__ . '/../hexogen/kdtree',
'aliases' => array(),
'dev_requirement' => false,
),
'nextcloud/photos' => array(
'pretty_version' => 'dev-stable32',
'version' => 'dev-stable32',
'reference' => 'ad01c539752e44dd356639b4193a9132feb002e1',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
),
);
+26
View File
@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}
+15
View File
@@ -0,0 +1,15 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at volodymyrbas@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,32 @@
# Contributing
Contributions are **welcome** and will be fully **credited**.
We accept contributions via Pull Requests on [Github](https://github.com/hexogen/kdtree).
## Pull Requests
- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``.
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
- **Create feature branches** - Don't ask us to pull from your master branch.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
## Running Tests
``` bash
$ composer test
```
**Happy coding**!
@@ -0,0 +1,17 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the bug.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,43 @@
name: Tests
on:
push:
pull_request:
jobs:
tests:
name: PHP ${{ matrix.php }}
runs-on: ubuntu-22.04
strategy:
matrix:
php: ['7.4', '8.0', '8.1', '8.2', '8.3']
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 10
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- name: Setup Problem Matchers
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install PHP Dependencies
uses: nick-invision/retry@v2
with:
timeout_minutes: 5
max_attempts: 5
command: composer update --no-interaction --no-progress
- name: Execute PHPcs
run: vendor/bin/phpcs --standard=psr2 src/
- name: Execute PHPUnit
run: vendor/bin/phpunit
+21
View File
@@ -0,0 +1,21 @@
# Changelog
All Notable changes to `hexogen/kdtree` will be documented in this file.
Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## v0.1.1- 2018-09-04
### Added
- Support phpunit v7.x
### Deprecated
- PHP 7.0 support
## v0.2.0- 2018-12-23
### Changed
- PHP 7.0 support removed
- Interfaces now returns types according to documentation (where it was not possible accoridng
php 7.0 limitations)
+21
View File
@@ -0,0 +1,21 @@
# The MIT License (MIT)
Copyright (c) 2016 Volodymyr Basarab <volodymyrbas@gmail.com>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.
+126
View File
@@ -0,0 +1,126 @@
# K-D Tree
[![Latest Version on Packagist][ico-version]][link-packagist]
[![Build Status][ico-tests]][link-tests]
[![Software License][ico-license]](LICENSE.md)
[![Total Downloads][ico-downloads]][link-downloads]
PHP multidimensional K-D Tree implementation.
To receive all benefits from K-D Tree, use file system implementation(FSKDTree). FSKDTree stores tree in binary format and uses lazy loading while traversing through nodes. Current approach provides much higher performance compared
to deserialization.
## Install
Via Composer
``` bash
$ composer require hexogen/kdtree
```
## Usage
### Tree creation
``` php
//Item container with 2 dimensional points
$itemList = new ItemList(2);
//Adding 2 - dimension items to the list
$itemList->addItem(new Item(1, [1.2, 4.3]));
$itemList->addItem(new Item(2, [1.3, 3.4]));
$itemList->addItem(new Item(3, [4.5, 1.2]));
$itemList->addItem(new Item(4, [5.2, 3.5]));
$itemList->addItem(new Item(5, [2.1, 3.6]));
//Building tree with given item list
$tree = new KDTree($itemList);
```
### Searching nearest items to the given point
``` php
//Creating search engine with custom algorithm (currently Nearest Search)
$searcher = new NearestSearch($tree);
//Retrieving a result ItemInterface[] array with given size (currently 2)
$result = $searcher->search(new Point([1.25, 3.5]), 2);
echo $result[0]->getId(); // 2
echo $result[0]->getNthDimension(0); // 1.3
echo $result[0]->getNthDimension(1); // 3.4
echo $result[1]->getId(); // 1
echo $result[1]->getNthDimension(0); // 1.2
echo $result[1]->getNthDimension(1); // 4.3
```
### Persist tree to a binary file
``` php
//Init tree writer
$persister = new FSTreePersister('/path/to/dir');
//Save the tree to /path/to/dir/treeName.bin
$persister->convert($tree, 'treeName.bin');
```
### File system version of the tree
``` php
//ItemInterface factory
$itemFactory = new ItemFactory();
//Then init new instance of file system version of the tree
$fsTree = new FSKDTree('/path/to/dir/treeName.bin', $itemFactory);
//Now use fs kdtree to search
$fsSearcher = new NearestSearch($fsTree);
//Retrieving a result ItemInterface[] array with given size (currently 2)
$result = $fsSearcher->search(new Point([1.25, 3.5]), 2);
echo $result[0]->getId(); // 2
echo $result[1]->getId(); // 1
```
## Change log
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
## Testing
``` bash
$ composer test
```
## Contributing
Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details.
## Security
If you discover any security related issues, please email volodymyrbas@gmail.com instead of using the issue tracker.
## Credits
- [Volodymyr Basarab][link-author]
- [All Contributors][link-contributors]
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
[ico-version]: https://img.shields.io/packagist/v/hexogen/kdtree.svg?style=flat-square
[ico-tests]: https://img.shields.io/github/actions/workflow/status/hexogen/kdtree/tests.yml?branch=master
[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
[ico-downloads]: https://img.shields.io/packagist/dt/hexogen/kdtree.svg?style=flat-square
[link-packagist]: https://packagist.org/packages/hexogen/kdtree
[link-tests]: https://github.com/hexogen/kdtree/actions?query=workflow%3ATests
[link-downloads]: https://packagist.org/packages/hexogen/kdtree
[link-author]: https://github.com/hexogen
[link-contributors]: ../../contributors
+54
View File
@@ -0,0 +1,54 @@
{
"name": "hexogen/kdtree",
"type": "library",
"description": "file system KDTree index",
"keywords": [
"hexogen",
"kdtree",
"data structures",
"algorithms",
"search"
],
"homepage": "https://github.com/hexogen/kdtree",
"license": "MIT",
"authors": [
{
"name": "Volodymyr Basarab",
"email": "volodymyrbas@gmail.com",
"homepage": "https://github.com/hexogen",
"role": "Developer"
}
],
"require": {
"php" : "^7.1|^8.0"
},
"require-dev": {
"league/csv": "^9.7.0",
"mockery/mockery": "dev-main",
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.5.0"
},
"autoload": {
"psr-4": {
"Hexogen\\KDTree\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Hexogen\\KDTree\\Tests\\": "tests"
}
},
"scripts": {
"test": "phpunit",
"check-style": "phpcs -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests",
"fix-style": "phpcbf -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests"
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"config": {
"sort-packages": true
}
}
@@ -0,0 +1,8 @@
<?php
namespace Hexogen\KDTree\Exception;
class ValidationException extends \Exception
{
}
+185
View File
@@ -0,0 +1,185 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Interfaces\ItemFactoryInterface;
use Hexogen\KDTree\Interfaces\KDTreeInterface;
use Hexogen\KDTree\Interfaces\NodeInterface;
class FSKDTree implements KDTreeInterface
{
/**
* @const int integer size in bytes
*/
const INT_LENGTH = 4;
/**
* @const int float size in bytes
*/
const FLOAT_LENGTH = 8;
/**
* @var NodeInterface
*/
private $root;
/**
* @var array
*/
private $maxBoundary;
/**
* @var array
*/
private $minBoundary;
/**
* @var int number of items in the tree
*/
private $length;
/**
* @var int
*/
private $dimensions;
/**
* @var
*/
private $handler;
/**
* @var ItemFactoryInterface
*/
private $factory;
/**
* FSKDTree constructor.
* @param $path
* @param ItemFactoryInterface $factory
*/
public function __construct($path, ItemFactoryInterface $factory)
{
$this->factory = $factory;
$this->handler = fopen($path, 'rb');
$this->readInitData();
}
/**
* FSKDTree destructor
*/
public function __destruct()
{
fclose($this->handler);
}
/**
* @return int
*/
public function getItemCount(): int
{
return $this->length;
}
/**
* @return NodeInterface
*/
public function getRoot(): ?NodeInterface
{
return $this->root;
}
/**
* @return array
*/
public function getMinBoundary(): array
{
return $this->minBoundary;
}
/**
* @return array
*/
public function getMaxBoundary(): array
{
return $this->maxBoundary;
}
/**
* @return int
*/
public function getDimensionCount(): int
{
return $this->dimensions;
}
/**
* Read binary data and convert it to an object
*/
private function readInitData()
{
$this->readDimensionsCount();
$this->readItemsCount();
$this->readUpperBound();
$this->readLowerBound();
$this->setRoot();
}
/**
* read num of dimensions in array
*/
private function readDimensionsCount()
{
$binData = fread($this->handler, FSKDTree::INT_LENGTH);
$this->dimensions = unpack('V', $binData)[1];
}
/**
* read number of items in the tree
*/
private function readItemsCount()
{
$binData = fread($this->handler, FSKDTree::INT_LENGTH);
$this->length = unpack('V', $binData)[1];
}
/**
* read upper boundary point
*/
private function readUpperBound()
{
$this->maxBoundary = $this->readPoint();
}
/**
* read lower boundary point
*/
private function readLowerBound()
{
$this->minBoundary = $this->readPoint();
}
/**
* set tree root
*/
private function setRoot()
{
if ($this->length == 0) {
return;
}
$position = ftell($this->handler);
$this->root = new FSNode($this->factory, $this->handler, $position, $this->dimensions);
}
/**
* Read point
* @return array
*/
private function readPoint(): array
{
$dataLength = FSKDTree::FLOAT_LENGTH * $this->dimensions;
$binData = fread($this->handler, $dataLength);
$dValues = unpack('d' . $this->dimensions, $binData);
return array_values($dValues);
}
}
+157
View File
@@ -0,0 +1,157 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Interfaces\ItemFactoryInterface;
use Hexogen\KDTree\Interfaces\ItemInterface;
use Hexogen\KDTree\Interfaces\NodeInterface;
class FSNode implements NodeInterface
{
/**
* @var ItemInterface item that belongs to the node
*/
private $item;
/**
* @var NodeInterface|null link to the left node
*/
private $left;
/**
* @var int left node offset in file
*/
private $leftPosition;
/**
* @var NodeInterface|null right node link
*/
private $right;
/**
* @var int right node offset in the file
*/
private $rightPosition;
/**
* @var resource file handler
*/
private $handler;
/**
* @var int node start position in the file
*/
private $position;
/**
* @var ItemFactoryInterface item factory
*/
private $factory;
/**
* @var int num of dimensions it item
*/
private $dimensions;
/**
* FSNode constructor.
* @param ItemFactoryInterface $factory
* @param resource $handler file handler
* @param int $position node start position in the file
* @param int $dimensions number of dimensions in item
*/
public function __construct(ItemFactoryInterface $factory, $handler, int $position, int $dimensions)
{
$this->item = null;
$this->left = null;
$this->right = null;
$this->handler = $handler;
$this->position = $position;
$this->factory = $factory;
$this->dimensions = $dimensions;
}
/**
* @return ItemInterface get item from the node
*/
public function getItem() : ItemInterface
{
if ($this->item == null) {
$this->readNode();
}
return $this->item;
}
/**
* @param NodeInterface $node set right node
*/
public function setRight(NodeInterface $node): void
{
$this->right = $node;
}
/**
* @param NodeInterface $node set left node
*/
public function setLeft(NodeInterface $node): void
{
$this->left = $node;
}
/**
* Returns right node if it exists, null otherwise
* @return NodeInterface|null get right node
*/
public function getRight(): ?NodeInterface
{
if ($this->rightPosition === null) {
$this->readNode();
}
if ($this->right === null && $this->rightPosition !== 0) {
$rightNode = new FSNode($this->factory, $this->handler, $this->rightPosition, $this->dimensions);
$this->setRight($rightNode);
}
return $this->right;
}
/**
* Returns left node if it exists, null otherwise
* @return NodeInterface|null left node
*/
public function getLeft(): ?NodeInterface
{
if ($this->leftPosition === null) {
$this->readNode();
}
if ($this->left === null && $this->leftPosition !== 0) {
$leftNode = new FSNode($this->factory, $this->handler, $this->leftPosition, $this->dimensions);
$this->setLeft($leftNode);
}
return $this->left;
}
/**
* Read node data from the file
*/
private function readNode()
{
fseek($this->handler, $this->position);
$dataLength = FSKDTree::FLOAT_LENGTH * $this->dimensions;
$binData = fread($this->handler, FSKDTree::INT_LENGTH);
$itemId = unpack('V', $binData)[1];
$binData = fread($this->handler, FSKDTree::INT_LENGTH);
$this->leftPosition = unpack('V', $binData)[1];
$binData = fread($this->handler, FSKDTree::INT_LENGTH);
$this->rightPosition = unpack('V', $binData)[1];
$binData = fread($this->handler, $dataLength);
$dValues = unpack('d'.$this->dimensions, $binData);
$dValues = array_values($dValues);
$this->item = $this->factory->make($itemId, $dValues);
}
}
@@ -0,0 +1,183 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Interfaces\ItemInterface;
use Hexogen\KDTree\Interfaces\KDTreeInterface;
use Hexogen\KDTree\Interfaces\NodeInterface;
use Hexogen\KDTree\Interfaces\TreePersisterInterface;
class FSTreePersister implements TreePersisterInterface
{
/**
* @var string path to the file
*/
private $path;
/**
* @var resource file handler
*/
private $handler;
/**
* @var int
*/
private $dimensions;
/**
* @var int
*/
private $nodeMemorySize;
public function __construct(string $path)
{
$this->path = $path;
}
/**
* @api
* @param KDTreeInterface $tree
* @param string $identifier that identifies persisted tree(may be a filename, database name etc.)
* @return mixed
*/
public function convert(KDTreeInterface $tree, string $identifier)
{
$this->initTree($identifier);
$this->dimensions = $tree->getDimensionCount();
$this->calculateNodeSize();
$this->specifyNumberOfDimensions();
$this->specifyNumberOfItems($tree);
$upperBound = $tree->getMaxBoundary();
$this->writeCoordinate($upperBound);
$lowerBound = $tree->getMinBoundary();
$this->writeCoordinate($lowerBound);
$root = $tree->getRoot();
if ($root) {
$this->writeNode($root);
}
fclose($this->handler);
}
/**
* @param NodeInterface $node
*/
private function writeNode(NodeInterface $node)
{
$position = ftell($this->handler);
$item = $node->getItem();
$this->writeItemId($item);
$dataChunk = pack('V', 0); // left position currently unknown so it equal 0/null
fwrite($this->handler, $dataChunk);
$rightNode = $node->getRight();
$rightPosition = 0;
if ($rightNode) {
$rightPosition = $position + $this->nodeMemorySize;
}
$dataChunk = pack('V', $rightPosition);
fwrite($this->handler, $dataChunk);
$this->saveItemCoordinate($item);
if ($rightNode) {
$this->writeNode($rightNode);
}
$leftNode = $node->getLeft();
if ($leftNode == null) {
return;
}
$this->persistLeftLink($position);
$this->writeNode($leftNode);
}
/**
* @param array $coordinate
*/
private function writeCoordinate(array $coordinate)
{
$dataChunk = pack('d'.$this->dimensions, ...$coordinate);
fwrite($this->handler, $dataChunk);
}
/**
* @param string $identifier
*/
private function initTree(string $identifier)
{
$this->handler = fopen($this->path . '/' . $identifier, 'wb');
}
/**
* Calculate memory size in file needed for single node
*/
private function calculateNodeSize()
{
$this->nodeMemorySize = 3 * FSKDTree::INT_LENGTH + $this->dimensions * FSKDTree::FLOAT_LENGTH;
}
/**
* Specify number of dimensions according to file format
*/
private function specifyNumberOfDimensions()
{
$dataChunk = pack('V', $this->dimensions);
fwrite($this->handler, $dataChunk);
}
/**
* @param KDTreeInterface $tree
*/
private function specifyNumberOfItems(KDTreeInterface $tree)
{
$itemCount = $tree->getItemCount();
$dataChunk = pack('V', $itemCount);
fwrite($this->handler, $dataChunk);
}
/**
* @param $item
*/
private function saveItemCoordinate(ItemInterface $item)
{
$coordinate = [];
for ($i = 0; $i < $this->dimensions; $i++) {
$coordinate[] = $item->getNthDimension($i);
}
$this->writeCoordinate($coordinate);
}
/**
* Persist current position before writing left node
* @param int $position
*/
private function persistLeftLink(int $position)
{
$leftPosition = ftell($this->handler);
fseek($this->handler, $position + FSKDTree::INT_LENGTH);
$dataChunk = pack('V', $leftPosition);
fwrite($this->handler, $dataChunk);
fseek($this->handler, $leftPosition);
}
/**
* @param $item
*/
private function writeItemId(ItemInterface $item)
{
$itemId = $item->getId();
$dataChunk = pack('V', $itemId);
fwrite($this->handler, $dataChunk);
}
}
@@ -0,0 +1,16 @@
<?php
namespace Hexogen\KDTree\Interfaces;
interface ItemFactoryInterface
{
/**
* Create an instance of ItemInterface
* @api
* @param int $id
* @param array $dValues
* @return ItemInterface
*/
public function make(int $id, array $dValues) : ItemInterface;
}
@@ -0,0 +1,14 @@
<?php
namespace Hexogen\KDTree\Interfaces;
interface ItemInterface extends PointInterface
{
/**
* get item id
*
* @api
* @return int item id
*/
public function getId() : int;
}
@@ -0,0 +1,23 @@
<?php
namespace Hexogen\KDTree\Interfaces;
interface ItemListInterface
{
/**
* Add item to the list
* @api
* @param ItemInterface $item
*/
public function addItem(ItemInterface $item);
/**
* @return ItemInterface[] list of all items in the list
*/
public function getItems(): array;
/**
* @return int number of dimensions in items(points)
*/
public function getDimensionCount(): int;
}
@@ -0,0 +1,33 @@
<?php
namespace Hexogen\KDTree\Interfaces;
interface KDTreeInterface
{
/**
* @return int number of items in the set
*/
public function getItemCount() : int;
/**
* @return int number of point dimensions
*/
public function getDimensionCount() : int;
/**
* @return NodeInterface|null root node of the tree
*/
public function getRoot() : ?NodeInterface;
/**
* @return array lower corner coordinate of the virtual multidimensional
* orthogon that fits all points of the kd tree
*/
public function getMinBoundary() : array;
/**
* @return array upper corner coordinate of the virtual multidimensional
* orthogon that fits all points of the kd tree
*/
public function getMaxBoundary() : array;
}
@@ -0,0 +1,31 @@
<?php
namespace Hexogen\KDTree\Interfaces;
interface NodeInterface
{
/**
* @return ItemInterface
*/
public function getItem(): ItemInterface;
/**
* @param NodeInterface $node
*/
public function setRight(NodeInterface $node): void;
/**
* @param NodeInterface $node
*/
public function setLeft(NodeInterface $node): void;
/**
* @return NodeInterface|null
*/
public function getRight(): ?NodeInterface;
/**
* @return NodeInterface|null
*/
public function getLeft(): ?NodeInterface;
}
@@ -0,0 +1,21 @@
<?php
namespace Hexogen\KDTree\Interfaces;
interface PointInterface
{
/**
* get nth dimension value from vector
*
* @api
* @param int $d dimension of the point
* @return float
*/
public function getNthDimension(int $d): float;
/**
* @api
* @return int number of dimensions in array
*/
public function getDimensionsCount(): int;
}
@@ -0,0 +1,36 @@
<?php
namespace Hexogen\KDTree\Interfaces;
abstract class SearchAbstract
{
/**
* @var KDTreeInterface
*/
protected $tree;
/**
* @var int
*/
protected $dimensions;
/**
* SearchAbstract constructor.
* @param KDTreeInterface $tree
*/
public function __construct(KDTreeInterface $tree)
{
$this->tree = $tree;
$this->dimensions = $tree->getDimensionCount();
}
/**
* Search items it the tree by given algorithm
*
* @api
* @param PointInterface $point
* @param int $resultLength
* @return array
*/
abstract public function search(PointInterface $point, int $resultLength = 1) : array;
}
@@ -0,0 +1,14 @@
<?php
namespace Hexogen\KDTree\Interfaces;
interface TreePersisterInterface
{
/**
* @api
* @param KDTreeInterface $tree
* @param string $identifier that identifies persisted tree(may be a filename, database name etc.)
* @return mixed
*/
public function convert(KDTreeInterface $tree, string $identifier);
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Interfaces\ItemInterface;
class Item extends Point implements ItemInterface
{
private $id;
/**
* Item constructor.
* @param int $id
* @param array $dValues
*/
public function __construct(int $id, array $dValues)
{
parent::__construct($dValues);
$this->id = $id;
}
/**
* get item id
*
* @api
* @return int item id
*/
public function getId() : int
{
return $this->id;
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Interfaces\ItemFactoryInterface;
use Hexogen\KDTree\Interfaces\ItemInterface;
class ItemFactory implements ItemFactoryInterface
{
/**
* @api
* @param int $id
* @param array $dValues
* @return ItemInterface
*/
public function make(int $id, array $dValues) : ItemInterface
{
return new Item($id, $dValues);
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Exception\ValidationException;
use Hexogen\KDTree\Interfaces\ItemInterface;
use Hexogen\KDTree\Interfaces\ItemListInterface;
class ItemList implements ItemListInterface
{
private $dimensions;
private $items;
private $ids;
private $lastPosition;
/**
* ItemList constructor.
* @param int $dimensions
* @throws ValidationException
*/
public function __construct(int $dimensions)
{
if ($dimensions <= 0) {
throw new ValidationException('$dimensions should be bigger than 0');
}
$this->lastPosition = 0;
$this->dimensions = $dimensions;
$this->items = [];
$this->ids = [];
}
/**
* Add or replace an item in the item list
*
* @api
* @param ItemInterface $item
*/
public function addItem(ItemInterface $item)
{
$this->validateItem($item);
$id = $item->getId();
if (isset($this->ids[$id])) {
$index = $this->ids[$id];
$this->items[$index] = $item;
} else {
$this->items[] = $item;
$this->ids[$id] = $this->lastPosition++;
}
}
/**
* Get all items in the list
* @return ItemInterface[]
*/
public function getItems(): array
{
return $this->items;
}
/**
* @return int number of dimensions in item
*/
public function getDimensionCount(): int
{
return $this->dimensions;
}
/**
* @param ItemInterface $item
* @throws ValidationException
*/
private function validateItem(ItemInterface $item)
{
if ($item->getDimensionsCount() !== $this->dimensions) {
throw new ValidationException('$dValues number dimensions should be equal to ' . $this->dimensions);
}
}
}
+231
View File
@@ -0,0 +1,231 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Interfaces\ItemInterface;
use Hexogen\KDTree\Interfaces\ItemListInterface;
use Hexogen\KDTree\Interfaces\KDTreeInterface;
use Hexogen\KDTree\Interfaces\NodeInterface;
class KDTree implements KDTreeInterface
{
/**
* @var NodeInterface
*/
private $root;
/**
* @var ItemInterface[]|null array of items or null after tree has been built
*/
private $items;
/**
* @var array
*/
private $maxBoundary;
/**
* @var array
*/
private $minBoundary;
/**
* @var int number of items in the tree
*/
private $length;
/**
* @var int
*/
private $dimensions;
/**
* KDTree constructor.
* @param ItemListInterface $itemList
*/
public function __construct(ItemListInterface $itemList)
{
$this->dimensions = $itemList->getDimensionCount();
$this->items = $itemList->getItems();
$this->length = count($this->items);
$this->setBoundaries($this->items);
$this->buildTree();
unset($this->items);
}
/**
* Get number of items in the tree
* @return int
*/
public function getItemCount(): int
{
return $this->length;
}
/**
* Get root node
* @return NodeInterface|null return node or null if there is no nodes in the tree
*/
public function getRoot(): ?NodeInterface
{
return $this->root;
}
/**
* Get lower boundary coordinate
* @return array
*/
public function getMinBoundary(): array
{
return $this->minBoundary;
}
/**
* Get upper boundary coordinate
* @return array
*/
public function getMaxBoundary(): array
{
return $this->maxBoundary;
}
/**
* Get number of dimensions in the tree
* @return int
*/
public function getDimensionCount(): int
{
return $this->dimensions;
}
/**
* @param int $lo
* @param int $hi
* @param int $d
* @return Node
*/
private function buildSubTree(int $lo, int $hi, int $d): Node
{
$mid = (int)(($hi - $lo) / 2) + $lo;
$item = $this->select($mid, $lo, $hi, $d);
$node = new Node($item);
$d++;
$d = $d % $this->dimensions;
if ($mid > $lo) {
$node->setLeft($this->buildSubTree($lo, $mid - 1, $d));
}
if ($mid < $hi) {
$node->setRight($this->buildSubTree($mid + 1, $hi, $d));
}
return $node;
}
private function exch(int $i, int $j)
{
$tmp = $this->items[$i];
$this->items[$i] = $this->items[$j];
$this->items[$j] = $tmp;
}
/**
* @param int $k
* @param int $lo
* @param int $hi
* @param int $d
* @return ItemInterface
*/
private function select(int $k, int $lo, int $hi, int $d)
{
while ($hi > $lo) {
$j = $this->partition($lo, $hi, $d);
if ($j > $k) {
$hi = $j - 1;
} elseif ($j < $k) {
$lo = $j + 1;
} else {
return $this->items[$k];
}
}
return $this->items[$k];
}
/**
* @param int $lo
* @param int $hi
* @param int $d
* @return int
*/
private function partition(int $lo, int $hi, int $d)
{
$i = $lo;
$j = $hi + 1;
$v = $this->items[$lo];
$val = $v->getNthDimension($d);
do {
while ($this->items[++$i]->getNthDimension($d) < $val && $i != $hi) {
}
while ($this->items[--$j]->getNthDimension($d) > $val) {
}
if ($i < $j) {
$this->exch($i, $j);
}
} while ($i < $j);
$this->exch($lo, $j);
return $j;
}
/**
* Set boundaries for given item list
* @param ItemInterface[] $items
*/
private function setBoundaries(array $items)
{
$this->maxBoundary = [];
$this->minBoundary = [];
for ($i = 0; $i < $this->dimensions; $i++) {
$this->maxBoundary[$i] = -INF;
$this->minBoundary[$i] = INF;
}
foreach ($items as $item) {
for ($i = 0; $i < $this->dimensions; $i++) {
$this->maxBoundary[$i] = max($this->maxBoundary[$i], $item->getNthDimension($i));
$this->minBoundary[$i] = min($this->minBoundary[$i], $item->getNthDimension($i));
}
}
}
/**
* Build kd tree
*/
private function buildTree()
{
if ($this->length > 0) {
$hi = $this->length - 1;
$mid = (int)($hi / 2);
$item = $this->select($mid, 0, $hi, 0);
$this->root = new Node($item);
$nextDimension = 1 % $this->dimensions;
if ($mid > 0) {
$this->root->setLeft($this->buildSubTree(0, $mid - 1, $nextDimension));
}
if ($mid < $hi) {
$this->root->setRight($this->buildSubTree($mid + 1, $hi, $nextDimension));
}
}
}
}
+446
View File
@@ -0,0 +1,446 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Exception\ValidationException;
use Hexogen\KDTree\Interfaces\ItemInterface;
use Hexogen\KDTree\Interfaces\NodeInterface;
use Hexogen\KDTree\Interfaces\PointInterface;
use Hexogen\KDTree\Interfaces\SearchAbstract;
use SplPriorityQueue;
class NearestSearch extends SearchAbstract
{
/**
* @var SplPriorityQueue
*/
private $queue;
/**
* @var float
*/
private $maxQueuedDistance;
/**
* @var PointInterface
*/
private $point;
/**
* Search items it the tree by given algorithm
*
* @api
* @param PointInterface $point
* @param int $resultLength
* @return ItemInterface[]
*/
public function search(PointInterface $point, int $resultLength = 1) : array
{
$this->validatePoint($point);
$this->point = $point;
$upperBound = $this->tree->getMaxBoundary();
$lowerBound = $this->tree->getMinBoundary();
$root = $this->tree->getRoot();
if ($root == null) {
return [];
}
/**
* @var array orthogonal square distances to the point
*/
$orthogonalDistances = $this->getOrthogonalDistances($point, $upperBound, $lowerBound);
/**
* @var float possible Euclidean distance
*/
$possibleDistance = $this->getPossibleDistance($orthogonalDistances);
$this->prepareQueue($resultLength);
$this->searchNearest($root, 0, $upperBound, $lowerBound, $orthogonalDistances, $possibleDistance);
return $this->getItemsFromQueue();
}
/**
* Check that point has same number of dimensions that all items in the tree
*
* @param PointInterface $point
* @throws ValidationException
*/
private function validatePoint(PointInterface $point)
{
if ($point->getDimensionsCount() !== $this->tree->getDimensionCount()) {
throw new ValidationException(
'point dimensions count should be equal to ' . $this->tree->getDimensionCount()
);
}
}
/**
* Get orthogonal distances array from the point to multidimensional space that holds all the items in the tree
*
* @param PointInterface $point
* @param array $upperBound
* @param array $lowerBound
* @return array
*/
private function getOrthogonalDistances(PointInterface $point, array $upperBound, array $lowerBound): array
{
$orthogonalDistances = [];
for ($i = 0; $i < $this->dimensions; $i++) {
$coordinate = $point->getNthDimension($i);
$orthogonalDistances[$i] = $this->getPossibleOrthogonalDistance(
$coordinate,
$upperBound[$i],
$lowerBound[$i]
);
}
return $orthogonalDistances;
}
/**
* Calculate minimal possible Euclidean distance from the point to an item
*
* @param $orthogonalDistances
* @return float
*/
private function getPossibleDistance(array $orthogonalDistances) : float
{
$possibleDistance = 0.;
foreach ($orthogonalDistances as $orthogonalDistance) {
$possibleDistance += $orthogonalDistance;
}
return $possibleDistance;
}
/**
* Prepare queue for collecting nearest items.
* Queue size sets to be equal to result length or to tree size,
* if request result length is bigger then tree size
*
* @param int $resultLength
* @return SplPriorityQueue
*/
private function prepareQueue(int $resultLength)
{
$this->queue = new SplPriorityQueue();
$this->queue->setExtractFlags(SplPriorityQueue::EXTR_PRIORITY);
$itemsInTree = $this->tree->getItemCount();
if ($itemsInTree < $resultLength) {
$resultLength = $itemsInTree;
}
for ($i = 0; $i < $resultLength; $i++) {
$this->queue->insert(null, INF);
}
$this->maxQueuedDistance = INF;
}
/**
* Add an item to the queue if distance to the point is less than max queued,
* after it removes an item with the biggest distance, to keep queue size constant
*
* @param ItemInterface $item
* @param float $distance
*/
private function addToQueue(ItemInterface $item, float $distance)
{
if ($distance >= $this->maxQueuedDistance) {
return;
}
$this->queue->insert($item, $distance);
$this->queue->extract();
$this->maxQueuedDistance = $this->queue->current();
}
/**
* Calculate Euclidean distance between point and item
*
* @param ItemInterface $item
* @param PointInterface $point
* @return float
*/
private function calculateDistance(ItemInterface $item, PointInterface $point) : float
{
$distance = 0.;
for ($i = 0; $i < $this->dimensions; $i++) {
$distance += pow($item->getNthDimension($i) - $point->getNthDimension($i), 2);
}
return $distance;
}
/**
* Recursive search of N closest item in the tree to the given point
*
* @param NodeInterface $node
* @param int $dimension
* @param array $upperBound
* @param array $lowerBound
* @param array $orthogonalDistances
* @param float $possibleDistance
*/
private function searchNearest(
NodeInterface $node,
int $dimension,
array $upperBound,
array $lowerBound,
array $orthogonalDistances,
float $possibleDistance
) {
$item = $node->getItem();
$distance = $this->calculateDistance($item, $this->point);
$this->addToQueue($item, $distance);
$rightLowerBound = $lowerBound;
$leftUpperBound = $upperBound;
$rightLowerBound[$dimension] = $item->getNthDimension($dimension);
$leftUpperBound[$dimension] = $item->getNthDimension($dimension);
$rightNode = $node->getRight();
$leftNode = $node->getLeft();
if ($rightNode && $leftNode) {
$this->smartBranchesSearch(
$rightNode,
$leftNode,
$dimension,
$upperBound,
$rightLowerBound,
$leftUpperBound,
$lowerBound,
$orthogonalDistances,
$possibleDistance
);
return;
}
if ($rightNode) {
$this->branchSearch(
$rightNode,
$dimension,
$upperBound,
$rightLowerBound,
$orthogonalDistances,
$possibleDistance
);
}
if ($leftNode) {
$this->branchSearch(
$leftNode,
$dimension,
$leftUpperBound,
$lowerBound,
$orthogonalDistances,
$possibleDistance
);
}
}
/**
* Get Euclidean distance between point and an item in given dimension
*
* @param $pointCoordinate
* @param $upperBound
* @param $lowerBound
* @return float|number
*/
private function getPossibleOrthogonalDistance($pointCoordinate, $upperBound, $lowerBound)
{
if ($pointCoordinate > $upperBound) {
return pow($pointCoordinate - $upperBound, 2);
} elseif ($pointCoordinate < $lowerBound) {
return pow($lowerBound - $pointCoordinate, 2);
}
return 0.;
}
/**
* Recursive search in the given branch
*
* @param NodeInterface $branchNode
* @param int $dimension
* @param array $upperBound
* @param array $lowerBound
* @param array $orthogonalDistances
* @param float $possibleDistance
*/
private function branchSearch(
NodeInterface $branchNode,
int $dimension,
array $upperBound,
array $lowerBound,
array $orthogonalDistances,
float $possibleDistance
) {
// possible orthogonal distances to the right node
$branchOrthogonalDistances = $orthogonalDistances;
$branchPossibleDistance = $possibleDistance;
$nextDimension = ($dimension + 1) % $this->dimensions;
$branchOrthogonalDistances[$dimension] = $this->getPossibleOrthogonalDistance(
$this->point->getNthDimension($dimension),
$upperBound[$dimension],
$lowerBound[$dimension]
);
if ($orthogonalDistances[$dimension] != $branchOrthogonalDistances[$dimension]) {
$branchPossibleDistance += $branchOrthogonalDistances[$dimension] - $orthogonalDistances[$dimension];
}
if ($branchPossibleDistance <= $this->maxQueuedDistance) {
$this->searchNearest(
$branchNode,
$nextDimension,
$upperBound,
$lowerBound,
$branchOrthogonalDistances,
$branchPossibleDistance
);
}
}
/**
* extract all items from queue
*/
private function getItemsFromQueue() : array
{
$items = [];
$this->queue->setExtractFlags(SplPriorityQueue::EXTR_DATA);
while (!$this->queue->isEmpty()) {
$items[] = $this->queue->extract();
}
return array_reverse($items);
}
/**
* Nearest branch first approach
*
* @param NodeInterface $rightNode
* @param NodeInterface $leftNode
* @param int $dimension
* @param array $upperBound
* @param array $rightLowerBound
* @param array $leftUpperBound
* @param array $lowerBound
* @param array $orthogonalDistances
* @param float $possibleDistance
*/
private function smartBranchesSearch(
NodeInterface $rightNode,
NodeInterface $leftNode,
int $dimension,
array $upperBound,
array $rightLowerBound,
array $leftUpperBound,
array $lowerBound,
array $orthogonalDistances,
float $possibleDistance
) {
// possible orthogonal distances to the right node
$leftOrthogonalDistances = $rightOrthogonalDistances = $orthogonalDistances;
$leftPossibleDistance = $rightPossibleDistance = $possibleDistance;
$nextDimension = ($dimension + 1) % $this->dimensions;
$leftOrthogonalDistances[$dimension] = $this->getPossibleOrthogonalDistance(
$this->point->getNthDimension($dimension),
$leftUpperBound[$dimension],
$lowerBound[$dimension]
);
$rightOrthogonalDistances[$dimension] = $this->getPossibleOrthogonalDistance(
$this->point->getNthDimension($dimension),
$upperBound[$dimension],
$rightLowerBound[$dimension]
);
if ($orthogonalDistances[$dimension] != $leftOrthogonalDistances[$dimension]) {
$leftPossibleDistance += $leftOrthogonalDistances[$dimension] - $orthogonalDistances[$dimension];
}
if ($orthogonalDistances[$dimension] != $rightOrthogonalDistances[$dimension]) {
$rightPossibleDistance += $rightOrthogonalDistances[$dimension] - $orthogonalDistances[$dimension];
}
if ($leftPossibleDistance < $rightPossibleDistance) {
$this->prioritySearch(
$leftNode,
$rightNode,
$leftUpperBound,
$lowerBound,
$upperBound,
$rightLowerBound,
$leftOrthogonalDistances,
$rightOrthogonalDistances,
$leftPossibleDistance,
$rightPossibleDistance,
$nextDimension
);
return;
}
$this->prioritySearch(
$rightNode,
$leftNode,
$upperBound,
$rightLowerBound,
$leftUpperBound,
$lowerBound,
$rightOrthogonalDistances,
$leftOrthogonalDistances,
$rightPossibleDistance,
$leftPossibleDistance,
$nextDimension
);
}
public function prioritySearch(
NodeInterface $firstNode,
NodeInterface $secondNode,
array $firstUpperBound,
array $firstLowerBound,
array $secondUpperBound,
array $secondLowerBound,
array $firstOrthogonalDistances,
array $secondOrthogonalDistances,
float $firstPossibleDistance,
float $secondPossibleDistance,
int $nextDimension
) {
if ($firstPossibleDistance < $this->maxQueuedDistance) {
$this->searchNearest(
$firstNode,
$nextDimension,
$firstUpperBound,
$firstLowerBound,
$firstOrthogonalDistances,
$firstPossibleDistance
);
if ($secondPossibleDistance < $this->maxQueuedDistance) {
$this->searchNearest(
$secondNode,
$nextDimension,
$secondUpperBound,
$secondLowerBound,
$secondOrthogonalDistances,
$secondPossibleDistance
);
}
}
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Interfaces\NodeInterface;
use Hexogen\KDTree\Interfaces\ItemInterface;
class Node implements NodeInterface
{
/**
* @var ItemInterface
*/
private $item;
/**
* @var NodeInterface|null
*/
private $left;
/**
* @var NodeInterface|null
*/
private $right;
/**
* Node constructor.
* @param ItemInterface $item
*/
public function __construct(ItemInterface $item)
{
$this->item = $item;
$this->left = null;
$this->right = null;
}
/**
* @return ItemInterface get item from the node
*/
public function getItem() : ItemInterface
{
return $this->item;
}
/**
* @param NodeInterface $node set right node
*/
public function setRight(NodeInterface $node): void
{
$this->right = $node;
}
/**
* @param NodeInterface $node set left node
*/
public function setLeft(NodeInterface $node): void
{
$this->left = $node;
}
/**
* @return NodeInterface|null get right node
*/
public function getRight(): ?NodeInterface
{
return $this->right;
}
/**
* @return NodeInterface|null get left node
*/
public function getLeft(): ?NodeInterface
{
return $this->left;
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace Hexogen\KDTree;
use Hexogen\KDTree\Exception\ValidationException;
use Hexogen\KDTree\Interfaces\PointInterface;
class Point implements PointInterface
{
private $dValues;
private $length;
/**
* Item constructor.
* @param array $dValues
*/
public function __construct(array $dValues)
{
$this->length = count($dValues);
$this->validateDValues($dValues);
$this->dValues = $dValues;
}
/**
* get nth dimension value from vector
*
* @api
* @param int $d
* @return float
*/
public function getNthDimension(int $d): float
{
if ($d < 0 || $d >= $this->length) {
throw new \OutOfRangeException('$d = ' . $d . ' should be between 0 and number of ' . $this->length);
}
return (float)$this->dValues[$d];
}
/**
* validate multi dimension vector
*
* @param array $dValues
* @throws ValidationException
*/
private function validateDValues(array $dValues)
{
if ($this->length == 0) {
throw new ValidationException('$dValues should be not empty');
}
for ($i = 0; $i < $this->length; $i++) {
if (!isset($dValues[$i]) || !is_numeric($dValues[$i])) {
throw new ValidationException('$dValues is not a simple array list');
}
}
}
/**
* @api
* @return int number of dimensions in the point
*/
public function getDimensionsCount(): int
{
return $this->length;
}
}