GEOS  3.13.0dev
TemplateSTRtreeDistance.h
1 /**********************************************************************
2  *
3  * GEOS - Geometry Engine Open Source
4  * http://geos.osgeo.org
5  *
6  * Copyright (C) 2020-2021 Daniel Baston
7  *
8  * This is free software; you can redistribute and/or modify it under
9  * the terms of the GNU Lesser General Public Licence as published
10  * by the Free Software Foundation.
11  * See the COPYING file for more information.
12  *
13  **********************************************************************/
14 
15 #pragma once
16 
17 #include <geos/constants.h>
18 #include <geos/index/strtree/TemplateSTRNode.h>
19 #include <geos/index/strtree/TemplateSTRNodePair.h>
20 #include <geos/util/IllegalArgumentException.h>
21 #include <geos/util.h>
22 
23 #include <queue>
24 #include <memory>
25 #include <vector>
26 
27 namespace geos {
28 namespace index {
29 namespace strtree {
30 
31 template<typename ItemType, typename BoundsType, typename ItemDistance>
32 class TemplateSTRtreeDistance {
33  using Node = TemplateSTRNode<ItemType, BoundsType>;
34  using NodePair = TemplateSTRNodePair<ItemType, BoundsType, ItemDistance>;
35  using ItemPair = std::pair<ItemType, ItemType>;
36 
37  struct PairQueueCompare {
38  bool operator()(const NodePair& a, const NodePair& b) {
39  return a.getDistance() > b.getDistance();
40  }
41  };
42 
43  using PairQueue = std::priority_queue<NodePair, std::vector<NodePair>, PairQueueCompare>;
44 
45 public:
46  explicit TemplateSTRtreeDistance(ItemDistance& id) : m_id(id) {}
47 
48  ItemPair nearestNeighbour(const Node& root1, const Node& root2) {
49  NodePair initPair(root1, root2, m_id);
50  return nearestNeighbour(initPair);
51  }
52 
53  ItemPair nearestNeighbour(NodePair& initPair) {
54  return nearestNeighbour(initPair, DoubleInfinity);
55  }
56 
57  bool isWithinDistance(const Node& root1, const Node& root2, double maxDistance) {
58  NodePair initPair(root1, root2, m_id);
59  return isWithinDistance(initPair, maxDistance);
60  }
61 
62 private:
63 
64  ItemPair nearestNeighbour(NodePair& initPair, double maxDistance) {
65  double distanceLowerBound = maxDistance;
66  std::unique_ptr<NodePair> minPair;
67 
68  PairQueue priQ;
69  priQ.push(initPair);
70 
71  while (!priQ.empty() && distanceLowerBound > 0) {
72  NodePair pair = priQ.top();
73  priQ.pop();
74  double currentDistance = pair.getDistance();
75 
76  /*
77  * If the distance for the first node in the queue
78  * is >= the current minimum distance, all other nodes
79  * in the queue must also have a greater distance.
80  * So the current minDistance must be the true minimum,
81  * and we are done.
82  */
83  if (minPair && currentDistance >= distanceLowerBound) {
84  break;
85  }
86 
87  /*
88  * If the pair members are leaves
89  * then their distance is the exact lower bound.
90  * Update the distanceLowerBound to reflect this
91  * (which must be smaller, due to the test
92  * immediately prior to this).
93  */
94  if (pair.isLeaves()) {
95  distanceLowerBound = currentDistance;
96  if (minPair) {
97  *minPair = pair;
98  } else {
99  minPair = detail::make_unique<NodePair>(pair);
100  }
101  } else {
102  /*
103  * Otherwise, expand one side of the pair,
104  * (the choice of which side to expand is heuristically determined)
105  * and insert the new expanded pairs into the queue
106  */
107  expandToQueue(pair, priQ, distanceLowerBound);
108  }
109  }
110 
111  if (!minPair) {
112  throw util::GEOSException("Error computing nearest neighbor");
113  }
114 
115  return minPair->getItems();
116  }
117 
118  void expandToQueue(const NodePair& pair, PairQueue& priQ, double minDistance) {
119  const Node& node1 = pair.getFirst();
120  const Node& node2 = pair.getSecond();
121 
122  bool isComp1 = node1.isComposite();
123  bool isComp2 = node2.isComposite();
124 
130  if (isComp1 && isComp2) {
131  if (node1.getSize() > node2.getSize()) {
132  expand(node1, node2, false, priQ, minDistance);
133  return;
134  } else {
135  expand(node2, node1, true, priQ, minDistance);
136  return;
137  }
138  } else if (isComp1) {
139  expand(node1, node2, false, priQ, minDistance);
140  return;
141  } else if (isComp2) {
142  expand(node2, node1, true, priQ, minDistance);
143  return;
144  }
145 
146  throw util::IllegalArgumentException("neither boundable is composite");
147 
148  }
149 
150  void expand(const Node &nodeComposite, const Node &nodeOther, bool isFlipped, PairQueue& priQ,
151  double minDistance) {
152  for (const auto *child = nodeComposite.beginChildren();
153  child < nodeComposite.endChildren(); ++child) {
154  NodePair sp = isFlipped ? NodePair(nodeOther, *child, m_id) : NodePair(*child, nodeOther, m_id);
155 
156  // only add to queue if this pair might contain the closest points
157  if (minDistance == DoubleInfinity || sp.getDistance() < minDistance) {
158  priQ.push(sp);
159  }
160  }
161  }
162 
163  bool isWithinDistance(const NodePair& initPair, double maxDistance) {
164  double distanceUpperBound = DoubleInfinity;
165 
166  // initialize search queue
167  PairQueue priQ;
168  priQ.push(initPair);
169 
170  while (! priQ.empty()) {
171  // pop head of queue and expand one side of pair
172  NodePair pair = priQ.top();
173  double pairDistance = pair.getDistance();
174 
182  if (pairDistance > maxDistance)
183  return false;
184 
185  priQ.pop();
186 
198  if (pair.maximumDistance() <= maxDistance)
199  return true;
205  if (pair.isLeaves()) {
206  // assert: currentDistance < minimumDistanceFound
207  distanceUpperBound = pairDistance;
212  if (distanceUpperBound <= maxDistance)
213  return true;
214  }
215  else {
221  expandToQueue(pair, priQ, distanceUpperBound);
222  }
223  }
224  return false;
225 
226  }
227 
228 
229  ItemDistance& m_id;
230 };
231 }
232 }
233 }
Basic namespace for all GEOS functionalities.
Definition: Angle.h:25