MATSIM
LinkQuadTree.java
Go to the documentation of this file.
1 
2 /* *********************************************************************** *
3  * project: org.matsim.*
4  * LinkQuadTree.java
5  * *
6  * *********************************************************************** *
7  * *
8  * copyright : (C) 2019 by the members listed in the COPYING, *
9  * LICENSE and WARRANTY file. *
10  * email : info at matsim dot org *
11  * *
12  * *********************************************************************** *
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * See also COPYING, LICENSE and WARRANTY file *
19  * *
20  * *********************************************************************** */
21 
22  package org.matsim.core.network;
23 
25 
26 import java.util.ArrayList;
27 import java.util.List;
28 
53 public final class LinkQuadTree {
54 
55  private final QuadTreeNode top;
56 
57  public LinkQuadTree(final double minX, final double minY, final double maxX, final double maxY) {
58  this.top = new QuadTreeNode(minX, minY, maxX, maxY);
59  }
60 
61  public void put(final Link link) {
62  this.top.put(new LinkWrapper(link));
63  }
64 
65  public Link getNearest(final double x, final double y) {
66  LinkWrapper w = this.top.getNearest(x, y, new MutableDouble(Double.POSITIVE_INFINITY));
67  if (w == null) {
68  return null;
69  }
70  return w.link;
71  }
72 
73  public void remove(final Link link) {
74  this.top.remove(new LinkWrapper(link));
75  }
76 
77  public double getMinEasting() {
78  return this.top.minX;
79  }
80 
81  public double getMaxEasting() {
82  return this.top.maxX;
83  }
84 
85  public double getMinNorthing() {
86  return this.top.minY;
87  }
88 
89  public double getMaxNorthing() {
90  return this.top.maxY;
91  }
92 
93  private static class QuadTreeNode {
94 
95 // private final static int NO_CHILD = -1;
96 // private final static int CHILD_NW = 0;
97 // private final static int CHILD_NE = 1;
98 // private final static int CHILD_SE = 2;
99 // private final static int CHILD_SW = 3;
100 
101  private enum ChildPosition {CHILD_NW, CHILD_NE, CHILD_SE, CHILD_SW,NO_CHILD }
102 
103  // I replaced the "int" by an enum since I find it easier to read, and I needed/wanted to understand the code. If this causes
104  // computational performance losses, we need to change it back. kai, sep'16
105 
106  public final double minX;
107  public final double minY;
108  public final double maxX;
109  public final double maxY;
110 
111  private final ArrayList<LinkWrapper> links = new ArrayList<>(3);
112  private QuadTreeNode[] children = null;
113 
114  public QuadTreeNode(final double minX, final double minY, final double maxX, final double maxY) {
115  this.minX = Math.min(minX, maxX);
116  this.minY = Math.min(minY, maxY);
117  this.maxX = Math.max(minX, maxX);
118  this.maxY = Math.max(minY, maxY);
119  }
120 
121  public void put(final LinkWrapper w) {
122  if (this.children == null && this.links.isEmpty()) {
123  // (means quadtree node neither has children nor contains a link yet)
124 
125  this.links.add(w);
126  // (node now contains this link)
127  } else {
129  if (pos == ChildPosition.NO_CHILD) {
130  // (i.e. link extends over more than one child node, so the current node is the smallest one that fully contains the link.
131  this.links.add(w);
132  } else {
133  if (this.children == null) {
134  split();
135  }
136  this.children[pos.ordinal()].put(w);
137  }
138  }
139  }
140 
141  public boolean remove(final LinkWrapper w) {
143  if (pos == ChildPosition.NO_CHILD || this.children == null) {
144  for (int i = 0, n = this.links.size(); i < n; i++) {
145  LinkWrapper w2 = this.links.get(i);
146  if (w2.link.equals(w.link)) {
147  this.links.remove(i);
148  return true;
149  }
150  }
151  } else {
152  return this.children[pos.ordinal()].remove(w);
153  }
154  return false;
155  }
156 
164  public LinkWrapper getNearest(final double x, final double y, final MutableDouble bestDistanceIndicator) {
165  LinkWrapper closest = null;
166 
167  // go through all links in current quadtree node (= box):
168  for (LinkWrapper w : this.links) {
169  double tmp = calcLineSegmentDistanceIndicator(x, y, w.link);
170  if (tmp < bestDistanceIndicator.value) {
171  bestDistanceIndicator.value = tmp;
172  closest = w;
173  }
174  }
175 
176  // if we have child nodes (= child boxes) ...
177  if (this.children != null) {
178 
179  // ... find the correct one ...
180  ChildPosition childPos = this.getChildPosition(x, y);
181 
182  if (childPos != ChildPosition.NO_CHILD) {
183  // (not sure we can have children, but none in the correct "direction"; depends on exact behavior of "split")
184 
185  // Find child in correct "direction" and do recursive call:
186  LinkWrapper tmp = this.children[childPos.ordinal()].getNearest(x, y, bestDistanceIndicator);
187  if (tmp != null) {
188  closest = tmp;
189  }
190 
191  // now also go through all _other_ children ...
192  for ( ChildPosition c : ChildPosition.values() ) {
193  if (c != childPos && c != ChildPosition.NO_CHILD ) {
194  QuadTreeNode child = this.children[c.ordinal()];
195 
196  // For those other children, check if their bounding box is so close that we also need to look at them:
197  if (child.calcDistanceIndicator(x, y) < bestDistanceIndicator.value) {
198 
199  // Only if the answer is yes, do a recursive call for actual LinkWrapper instances:
200  tmp = child.getNearest(x, y, bestDistanceIndicator);
201  if (tmp != null) {
202  closest = tmp;
203  }
204  }
205  }
206  }
207  }
208  }
209 
210  return closest;
211  }
212 
213  private void split() {
214  double centerX = (minX + maxX) / 2;
215  double centerY = (minY + maxY) / 2;
216  this.children = new QuadTreeNode[4];
217  this.children[ChildPosition.CHILD_NW.ordinal()] = new QuadTreeNode(this.minX, centerY, centerX, this.maxY);
218  this.children[ChildPosition.CHILD_NE.ordinal()] = new QuadTreeNode(centerX, centerY, this.maxX, this.maxY);
219  this.children[ChildPosition.CHILD_SE.ordinal()] = new QuadTreeNode(centerX, this.minY, this.maxX, centerY);
220  this.children[ChildPosition.CHILD_SW.ordinal()] = new QuadTreeNode(this.minX, this.minY, centerX, centerY);
221 
222  List<LinkWrapper> keep = new ArrayList<>(this.links.size() / 2);
223  for (LinkWrapper w : this.links) {
225  if (pos == ChildPosition.NO_CHILD) {
226  keep.add(w);
227  } else {
228  this.children[pos.ordinal()].put(w);
229  // (seems to me that this cannot happen to more than one link since the quad tree node will split
230  // as soon as a second link is added that can be assigned to a child node. kai, jul'12)
231  // (hä? kai, aug'16)
232  }
233  }
234  this.links.clear();
235  this.links.ensureCapacity(keep.size() + 5);
236  this.links.addAll(keep);
237  }
238 
240  // center of the bounding box of this quad tree node:
241  double centerX = (minX + maxX) / 2;
242  double centerY = (minY + maxY) / 2;
243 
244  if (w.maxX < centerX && w.minY >= centerY) {
245  // (bounding box of link lies fully to left and above the center of the quadtree node)
246  return ChildPosition.CHILD_NW;
247  }
248  if (w.minX >= centerX && w.minY >= centerY) {
249  return ChildPosition.CHILD_NE;
250  }
251  if (w.minX >= centerX && w.maxY < centerY) {
252  return ChildPosition.CHILD_SE;
253  }
254  if (w.maxX < centerX && w.maxY < centerY) {
255  return ChildPosition.CHILD_SW;
256  }
257  return ChildPosition.NO_CHILD;
258  // (happens when bounding box of link overlaps more than one child node.. i.e. in particular with long links)
259  }
260 
266  private ChildPosition getChildPosition(final double x, final double y) {
267  double centerX = (minX + maxX) / 2;
268  double centerY = (minY + maxY) / 2;
269  if (x < centerX && y >= centerY) {
270  return ChildPosition.CHILD_NW;
271  }
272  if (x >= centerX && y >= centerY) {
273  return ChildPosition.CHILD_NE;
274  }
275  if (x >= centerX/* && y < centerY // this is always true */) {
276  return ChildPosition.CHILD_SE;
277  }
278 // if (x < centerX && y < centerY) { // this should now always be true
279  return ChildPosition.CHILD_SW;
280 // }
281 // throw new RuntimeException("should never get here since (x,y) has to be _somewhere_ with respect to centerX and centerY") ;
282  }
283 
293  private double calcDistanceIndicator(final double x, final double y) {
294  double distanceX;
295  double distanceY;
296 
297  if (this.minX <= x && x <= this.maxX) {
298  distanceX = 0;
299  } else {
300  distanceX = Math.min(Math.abs(this.minX - x), Math.abs(this.maxX - x));
301  }
302  if (this.minY <= y && y <= this.maxY) {
303  distanceY = 0;
304  } else {
305  distanceY = Math.min(Math.abs(this.minY - y), Math.abs(this.maxY - y));
306  }
307 
308  return distanceX * distanceX + distanceY * distanceY;
309  // (no Math.sqrt(), as it's only used to compare to each other, thus distance "indicator")
310  }
311 
312  }
313 
314  private static double calcLineSegmentDistanceIndicator(final double x, final double y, final Link link) {
315 
316  double fx = link.getFromNode().getCoord().getX();
317  double fy = link.getFromNode().getCoord().getY();
318  double lineDX = link.getToNode().getCoord().getX() - fx;
319  double lineDY = link.getToNode().getCoord().getY() - fy;
320 
321  if ((lineDX == 0.0) && (lineDY == 0.0)) {
322  // the line segment is a point without dimension
323  return calcDistanceIndicator(fx, fy, x, y);
324  }
325 
326  double u = ((x - fx)*lineDX + (y - fy)*lineDY) / (lineDX*lineDX + lineDY*lineDY);
327 
328  if (u <= 0) {
329  // (x | y) is not on the line segment, but before lineFrom
330  return calcDistanceIndicator(fx, fy, x, y);
331  }
332  if (u >= 1) {
333  // (x | y) is not on the line segment, but after lineTo
334  return calcDistanceIndicator(fx + lineDX, fy + lineDY, x, y);
335  }
336  return calcDistanceIndicator(fx + u*lineDX, fy + u*lineDY, x, y);
337 
338  }
339 
340  private static double calcDistanceIndicator(final double fromX, final double fromY, final double toX, final double toY) {
341  double xDiff = toX - fromX;
342  double yDiff = toY - fromY;
343  return (xDiff*xDiff) + (yDiff*yDiff);
344  // (no Math.sqrt(), as it's only used to compare to each other, thus distance "indicator")
345  }
346 
347  private static class LinkWrapper {
348 
349  /*package*/ final double minX;
350  /*package*/ final double minY;
351  /*package*/ final double maxX;
352  /*package*/ final double maxY;
353 
354  /*package*/ final Link link;
355 
356  public LinkWrapper(final Link link) {
357  double fx = link.getFromNode().getCoord().getX();
358  double fy = link.getFromNode().getCoord().getY();
359  double tx = link.getToNode().getCoord().getX();
360  double ty = link.getToNode().getCoord().getY();
361 
362  if (fx == tx) {
363  // enforce minimal extent
364  this.minX = fx - Math.abs(fx)*1e-8; // make it adaptive within the number of significant digits
365  this.maxX = fx + Math.abs(fx)*1e-8; // make it adaptive within the number of significant digits
366  } else {
367  this.minX = Math.min(fx, tx);
368  this.maxX = Math.max(fx, tx);
369  }
370  if (fy == ty) {
371  this.minY = fy - Math.abs(fy)*1e-8; // make it adaptive within the number of significant digits
372  this.maxY = fy + Math.abs(fy)*1e-8; // make it adaptive within the number of significant digits
373  } else {
374  this.minY = Math.min(fy, ty);
375  this.maxY = Math.max(fy, ty);
376  }
377 
378  this.link = link;
379  }
380  }
381 
382  private static class MutableDouble {
383  public double value;
384 
385  public MutableDouble(final double value) {
386  this.value = value;
387  }
388  }
389 
390 }