Passed
Push — master ( 9e65a7...a0aac2 )
by Andrii
01:58
created

NearestSearch::findNearest()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 12
c 6
b 0
f 0
dl 0
loc 22
rs 8.8333
cc 7
nc 4
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace KDTree\Search;
6
7
use KDTree\Interfaces\{KDTreeInterface, NearestSearchInterface, NodeInterface, PointInterface};
8
9
final class NearestSearch implements NearestSearchInterface
10
{
11
    /**
12
     * @var KDTreeInterface
13
     */
14
    private $tree;
15
16
    /**
17
     * @var int
18
     */
19
    private $visited = 0;
20
21
    /**
22
     * @var PointInterface|null
23
     */
24
    private $closestPoint;
25
26
    /**
27
     * @var float
28
     */
29
    private $bestDistance;
30
31
    /**
32
     * @param KDTreeInterface $tree
33
     */
34
    public function __construct(KDTreeInterface $tree)
35
    {
36
        $this->tree = $tree;
37
        $this->reset();
38
    }
39
40
    /**
41
     * @param PointInterface $point
42
     *
43
     * @return PointInterface|null
44
     */
45
    public function nearest(PointInterface $point): ?PointInterface
46
    {
47
        $this->reset();
48
        $this->findNearest($this->tree->getRoot(), $point);
49
50
        return $this->closestPoint;
51
    }
52
53
    /**
54
     * @return float
55
     */
56
    public function getNearestDistance(): float
57
    {
58
        return $this->bestDistance;
59
    }
60
61
    /**
62
     * @param NodeInterface|null $node
63
     * @param PointInterface $searchingPoint
64
     * @param int $cuttingDimension
65
     */
66
    private function findNearest(?NodeInterface $node, PointInterface $searchingPoint, int $cuttingDimension = 0): void
67
    {
68
        if (null === $node || null === $node->getPoint()) {
69
            return;
70
        }
71
72
        ++$this->visited;
73
        $this->chooseBestDistance($node, $searchingPoint);
74
75
        if (0.0 >= $this->bestDistance) {
76
            return;
77
        }
78
79
        $dx = $node->getPoint()->getDAxis($cuttingDimension) - $searchingPoint->getDAxis($cuttingDimension);
80
        $cuttingDimension = ($cuttingDimension + 1) % $this->tree->getDimensions();
81
82
        $this->findNearest($dx > 0 ? $node->getLeft() : $node->getRight(), $searchingPoint, $cuttingDimension);
83
        if ($dx * $dx >= $this->bestDistance) {
84
            return;
85
        }
86
87
        $this->findNearest($dx > 0 ? $node->getRight() : $node->getLeft(), $searchingPoint, $cuttingDimension);
88
    }
89
90
    private function reset(): void
91
    {
92
        $this->bestDistance = (float) PHP_INT_MAX;
93
        $this->closestPoint = null;
94
        $this->visited = 0;
95
    }
96
97
    /**
98
     * @param NodeInterface $pretenderNode
99
     * @param PointInterface $searchingPoint
100
     */
101
    private function chooseBestDistance(NodeInterface $pretenderNode, PointInterface $searchingPoint): void
102
    {
103
        if (null === $pretenderNode->getPoint()) {
104
            return;
105
        }
106
107
        $pretenderDistance = $pretenderNode->getPoint()->distance($searchingPoint);
108
109
        if (null === $this->closestPoint || $pretenderDistance < $this->bestDistance) {
110
            $this->bestDistance = $pretenderDistance;
111
            $this->closestPoint = $pretenderNode->getPoint();
112
        }
113
    }
114
}
115