[2165] | 1 | package org.openstreetmap.josm.data.osm;
|
---|
[2427] | 2 |
|
---|
[2263] | 3 | import java.lang.reflect.Array;
|
---|
[2165] | 4 | import java.util.ArrayList;
|
---|
[2420] | 5 | import java.util.Arrays;
|
---|
[2165] | 6 | import java.util.Collection;
|
---|
| 7 | import java.util.Collections;
|
---|
[2388] | 8 | import java.util.Iterator;
|
---|
| 9 | import java.util.LinkedList;
|
---|
| 10 | import java.util.List;
|
---|
[2165] | 11 |
|
---|
[2388] | 12 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[2165] | 13 | import org.openstreetmap.josm.data.coor.QuadTiling;
|
---|
| 14 |
|
---|
[2263] | 15 | public class QuadBuckets<T extends OsmPrimitive> implements Collection<T>
|
---|
[2165] | 16 | {
|
---|
| 17 | public static boolean debug = false;
|
---|
[2424] | 18 | static boolean consistency_testing = false;
|
---|
[2440] | 19 | /*
|
---|
| 20 | * Functions prefixed with __ need locking before
|
---|
| 21 | * being called.
|
---|
| 22 | */
|
---|
| 23 | private Object split_lock = new Object();
|
---|
[2165] | 24 |
|
---|
| 25 | static void abort(String s)
|
---|
| 26 | {
|
---|
[2420] | 27 | throw new AssertionError(s);
|
---|
[2165] | 28 | }
|
---|
| 29 | static void out(String s)
|
---|
| 30 | {
|
---|
| 31 | System.out.println(s);
|
---|
| 32 | }
|
---|
| 33 | // periodic output
|
---|
| 34 | long last_out = -1;
|
---|
| 35 | void pout(String s)
|
---|
| 36 | {
|
---|
| 37 | long now = System.currentTimeMillis();
|
---|
| 38 | if (now - last_out < 300)
|
---|
| 39 | return;
|
---|
| 40 | last_out = now;
|
---|
| 41 | System.out.print(s + "\r");
|
---|
| 42 | }
|
---|
| 43 | void pout(String s, int i, int total)
|
---|
| 44 | {
|
---|
| 45 | long now = System.currentTimeMillis();
|
---|
| 46 | if ((now - last_out < 300) &&
|
---|
[2388] | 47 | ((i+1) < total))
|
---|
[2165] | 48 | return;
|
---|
| 49 | last_out = now;
|
---|
| 50 | // cast to float to keep the output size down
|
---|
| 51 | System.out.print(s + " " + (float)((i+1)*100.0/total) + "% done \r");
|
---|
| 52 | }
|
---|
| 53 | // 24 levels of buckets gives us bottom-level
|
---|
| 54 | // buckets that are about 2 meters on a side.
|
---|
| 55 | // That should do. :)
|
---|
| 56 | public static int NR_LEVELS = 24;
|
---|
| 57 | public static double WORLD_PARTS = (1 << NR_LEVELS);
|
---|
| 58 |
|
---|
[2454] | 59 | public static int MAX_OBJECTS_PER_LEVEL = 16;
|
---|
[2165] | 60 | class QBLevel
|
---|
| 61 | {
|
---|
| 62 | int level;
|
---|
| 63 | long quad;
|
---|
| 64 | QBLevel parent;
|
---|
| 65 |
|
---|
[2263] | 66 | public List<T> content;
|
---|
[2165] | 67 | public QBLevel children[];
|
---|
[2388] | 68 | @Override
|
---|
[2263] | 69 | public String toString()
|
---|
| 70 | {
|
---|
| 71 | return super.toString()+ "["+level+"]: " + bbox;
|
---|
| 72 | }
|
---|
[2165] | 73 | public QBLevel(QBLevel parent)
|
---|
| 74 | {
|
---|
| 75 | init(parent);
|
---|
| 76 | }
|
---|
[2425] | 77 | synchronized boolean remove_content(T o)
|
---|
[2263] | 78 | {
|
---|
| 79 | boolean ret = this.content.remove(o);
|
---|
[2388] | 80 | if (this.content.size() == 0) {
|
---|
[2263] | 81 | this.content = null;
|
---|
[2388] | 82 | }
|
---|
[2427] | 83 | if (this.canRemove()) {
|
---|
[2423] | 84 | this.remove_from_parent();
|
---|
[2427] | 85 | }
|
---|
[2263] | 86 | return ret;
|
---|
| 87 | }
|
---|
[2353] | 88 | QBLevel[] newChildren()
|
---|
| 89 | {
|
---|
| 90 | // This is ugly and hackish. But, it seems to work,
|
---|
| 91 | // and using an ArrayList here seems to cost us
|
---|
| 92 | // a significant performance penalty -- 50% in my
|
---|
| 93 | // testing. Child access is one of the single
|
---|
| 94 | // hottest code paths in this entire class.
|
---|
| 95 | return (QBLevel[])Array.newInstance(this.getClass(), QuadTiling.TILES_PER_LEVEL);
|
---|
| 96 | }
|
---|
[2263] | 97 | // Get the correct index for the given primitive
|
---|
| 98 | // at the given level. If the primitive can not
|
---|
| 99 | // fit into a single quad at this level, return -1
|
---|
| 100 | int get_index(T o, int level)
|
---|
| 101 | {
|
---|
[2388] | 102 | if (debug) {
|
---|
[2263] | 103 | out("getting index for " + o + " at level: " + level);
|
---|
[2388] | 104 | }
|
---|
[2427] | 105 | int index = -1;
|
---|
| 106 | for (LatLon c : o.getBBox().points()) {
|
---|
| 107 | if (debug) {
|
---|
| 108 | out("getting index for point: " + c);
|
---|
| 109 | }
|
---|
| 110 | if (index == -1) {
|
---|
| 111 | index = QuadTiling.index(c, level);
|
---|
[2388] | 112 | if (debug) {
|
---|
[2427] | 113 | out("set initial index to: " + index);
|
---|
[2388] | 114 | }
|
---|
[2427] | 115 | continue;
|
---|
| 116 | }
|
---|
| 117 | int another_index = QuadTiling.index(c, level);
|
---|
| 118 | if (debug) {
|
---|
| 119 | out("other point index: " + another_index);
|
---|
| 120 | }
|
---|
| 121 | if (another_index != index) {
|
---|
| 122 | // This happens at level 0 sometimes when a way has no
|
---|
| 123 | // nodes present.
|
---|
[2388] | 124 | if (debug) {
|
---|
[2427] | 125 | out("primitive ("+o.getId()+") would have gone across two quads "
|
---|
| 126 | + another_index + "/" + index + " at level: " + level + " ");
|
---|
[2388] | 127 | }
|
---|
[2427] | 128 | return -1;
|
---|
[2263] | 129 | }
|
---|
| 130 | }
|
---|
[2427] | 131 | return index;
|
---|
[2263] | 132 | }
|
---|
[2440] | 133 | /*
|
---|
| 134 | * There is a race between this and qb.nextContentNode().
|
---|
| 135 | * If nextContentNode() runs into this bucket, it may
|
---|
| 136 | * attempt to null out 'children' because it thinks this
|
---|
| 137 | * is a dead end.
|
---|
| 138 | */
|
---|
| 139 | void __split()
|
---|
[2165] | 140 | {
|
---|
[2388] | 141 | if (debug) {
|
---|
[2165] | 142 | out("splitting "+this.bbox()+" level "+level+" with "
|
---|
| 143 | + content.size() + " entries (my dimensions: "
|
---|
| 144 | + this.bbox.width()+", "+this.bbox.height()+")");
|
---|
[2388] | 145 | }
|
---|
[2165] | 146 | // deferring allocation of children until use
|
---|
| 147 | // seems a bit faster
|
---|
| 148 | //for (int i = 0; i < TILES_PER_LEVEL; i++)
|
---|
| 149 | // children[i] = new QBLevel(this, i);
|
---|
[2263] | 150 | List<T> tmpcontent = content;
|
---|
[2165] | 151 | content = null;
|
---|
[2263] | 152 | for (T o : tmpcontent) {
|
---|
| 153 | int new_index = get_index(o, level);
|
---|
| 154 | if (new_index == -1) {
|
---|
[2440] | 155 | this.__add_content(o);
|
---|
[2263] | 156 | //out("adding content to branch: " + this);
|
---|
| 157 | continue;
|
---|
| 158 | }
|
---|
[2450] | 159 | if (children == null) {
|
---|
[2442] | 160 | children = newChildren();
|
---|
[2450] | 161 | }
|
---|
[2388] | 162 | if (children[new_index] == null) {
|
---|
[2165] | 163 | children[new_index] = new QBLevel(this, new_index);
|
---|
[2388] | 164 | }
|
---|
[2165] | 165 | QBLevel child = children[new_index];
|
---|
[2388] | 166 | if (debug) {
|
---|
[2437] | 167 | out("putting "+o+"(q:"+Long.toHexString(QuadTiling.quadTile(o.getBBox().points().get(0)))+") into ["+new_index+"] " + child.bbox());
|
---|
[2388] | 168 | }
|
---|
[2263] | 169 | child.add(o);
|
---|
[2165] | 170 | }
|
---|
| 171 | }
|
---|
[2440] | 172 | void split() {
|
---|
| 173 | synchronized(split_lock) {
|
---|
| 174 | __split();
|
---|
| 175 | }
|
---|
| 176 | }
|
---|
| 177 | boolean __add_content(T o)
|
---|
[2165] | 178 | {
|
---|
[2263] | 179 | boolean ret = false;
|
---|
[2440] | 180 | // The split_lock will keep two concurrent
|
---|
| 181 | // calls from overwriting content
|
---|
[2388] | 182 | if (content == null) {
|
---|
[2380] | 183 | content = new ArrayList<T>();
|
---|
[2388] | 184 | }
|
---|
[2263] | 185 | ret = content.add(o);
|
---|
[2388] | 186 | if (debug && !this.isLeaf()) {
|
---|
[2263] | 187 | pout("added "+o.getClass().getName()+" to non-leaf with size: " + content.size());
|
---|
[2388] | 188 | }
|
---|
[2263] | 189 | return ret;
|
---|
| 190 | }
|
---|
[2440] | 191 | void __add_to_leaf(T o)
|
---|
[2263] | 192 | {
|
---|
[2440] | 193 | __add_content(o);
|
---|
[2165] | 194 | if (content.size() > MAX_OBJECTS_PER_LEVEL) {
|
---|
[2388] | 195 | if (debug) {
|
---|
[2165] | 196 | out("["+level+"] deciding to split");
|
---|
[2388] | 197 | }
|
---|
[2165] | 198 | if (level >= NR_LEVELS-1) {
|
---|
[2427] | 199 | if (debug) {
|
---|
[2423] | 200 | out("can not split, but too deep: " + level + " size: " + content.size());
|
---|
[2427] | 201 | }
|
---|
[2165] | 202 | return;
|
---|
| 203 | }
|
---|
| 204 | int before_size = -1;
|
---|
[2388] | 205 | if (consistency_testing) {
|
---|
[2165] | 206 | before_size = this.size();
|
---|
[2388] | 207 | }
|
---|
[2165] | 208 | this.split();
|
---|
| 209 | if (consistency_testing) {
|
---|
| 210 | int after_size = this.size();
|
---|
| 211 | if (before_size != after_size) {
|
---|
| 212 | abort("["+level+"] split done before: " + before_size + " after: " + after_size);
|
---|
| 213 | }
|
---|
| 214 | }
|
---|
| 215 | return;
|
---|
| 216 | }
|
---|
[2263] | 217 | }
|
---|
[2165] | 218 |
|
---|
[2263] | 219 | boolean matches(T o, BBox search_bbox)
|
---|
[2165] | 220 | {
|
---|
[2442] | 221 | // This can be optimized when o is a node
|
---|
| 222 | //return search_bbox.bounds(coor));
|
---|
[2427] | 223 | return o.getBBox().intersects(search_bbox);
|
---|
[2165] | 224 | }
|
---|
[2263] | 225 | private List<T> search_contents(BBox search_bbox)
|
---|
[2165] | 226 | {
|
---|
[2388] | 227 | if (debug) {
|
---|
[2441] | 228 | out("searching contents (size: " + content == null?"<null>":content.size() + ") for " + search_bbox);
|
---|
[2388] | 229 | }
|
---|
[2165] | 230 | /*
|
---|
| 231 | * It is possible that this was created in a split
|
---|
| 232 | * but never got any content populated.
|
---|
| 233 | */
|
---|
| 234 | if (content == null)
|
---|
| 235 | return null;
|
---|
| 236 | // We could delay allocating this. But, the way it is currently
|
---|
| 237 | // used, we almost always have a node in the area that we're
|
---|
| 238 | // searching since we're searching around other nodes.
|
---|
| 239 | //
|
---|
| 240 | // the iterator's calls to ArrayList.size() were showing up in
|
---|
| 241 | // some profiles, so I'm trying a LinkedList instead
|
---|
[2263] | 242 | List<T> ret = new LinkedList<T>();
|
---|
| 243 | for (T o : content) {
|
---|
[2388] | 244 | if (matches(o, search_bbox)) {
|
---|
[2263] | 245 | ret.add(o);
|
---|
[2388] | 246 | }
|
---|
[2165] | 247 | }
|
---|
[2388] | 248 | if (debug) {
|
---|
[2165] | 249 | out("done searching quad " + Long.toHexString(this.quad));
|
---|
[2388] | 250 | }
|
---|
[2165] | 251 | return ret;
|
---|
| 252 | }
|
---|
| 253 | /*
|
---|
| 254 | * This is stupid. I tried to have a QBLeaf and QBBranch
|
---|
| 255 | * class decending from a QBLevel. It's more than twice
|
---|
| 256 | * as slow. So, this throws OO out the window, but it
|
---|
| 257 | * is fast. Runtime type determination must be slow.
|
---|
| 258 | */
|
---|
| 259 | boolean isLeaf()
|
---|
| 260 | {
|
---|
| 261 | if (children == null)
|
---|
| 262 | return true;
|
---|
| 263 | return false;
|
---|
| 264 | }
|
---|
| 265 | QBLevel next_sibling()
|
---|
| 266 | {
|
---|
| 267 | boolean found_me = false;
|
---|
| 268 | if (parent == null)
|
---|
| 269 | return null;
|
---|
| 270 | int __nr = 0;
|
---|
| 271 | for (QBLevel sibling : parent.children) {
|
---|
| 272 | __nr++;
|
---|
| 273 | int nr = __nr-1;
|
---|
| 274 | if (sibling == null) {
|
---|
[2388] | 275 | if (debug) {
|
---|
[2165] | 276 | out("[" + this.level + "] null child nr: " + nr);
|
---|
[2388] | 277 | }
|
---|
[2165] | 278 | continue;
|
---|
| 279 | }
|
---|
| 280 | // We're looking for the *next* child
|
---|
| 281 | // after us.
|
---|
| 282 | if (sibling == this) {
|
---|
[2388] | 283 | if (debug) {
|
---|
[2165] | 284 | out("[" + this.level + "] I was child nr: " + nr);
|
---|
[2388] | 285 | }
|
---|
[2165] | 286 | found_me = true;
|
---|
| 287 | continue;
|
---|
| 288 | }
|
---|
| 289 | if (found_me) {
|
---|
[2388] | 290 | if (debug) {
|
---|
[2165] | 291 | out("[" + this.level + "] next sibling was child nr: " + nr);
|
---|
[2388] | 292 | }
|
---|
[2165] | 293 | return sibling;
|
---|
| 294 | }
|
---|
[2388] | 295 | if (debug) {
|
---|
[2165] | 296 | out("[" + this.level + "] nr: " + nr + " is before me, ignoring...");
|
---|
[2388] | 297 | }
|
---|
[2165] | 298 | }
|
---|
| 299 | return null;
|
---|
| 300 | }
|
---|
[2263] | 301 | boolean hasContent()
|
---|
[2165] | 302 | {
|
---|
[2441] | 303 | return content != null;
|
---|
[2263] | 304 | }
|
---|
[2423] | 305 | QBLevel nextSibling()
|
---|
[2263] | 306 | {
|
---|
[2165] | 307 | QBLevel next = this;
|
---|
[2423] | 308 | QBLevel sibling = next.next_sibling();
|
---|
| 309 | // Walk back up the tree to find the
|
---|
| 310 | // next sibling node. It may be either
|
---|
| 311 | // a leaf or branch.
|
---|
| 312 | while (sibling == null) {
|
---|
| 313 | if (debug) {
|
---|
| 314 | out("no siblings at level["+next.level+"], moving to parent");
|
---|
[2427] | 315 | }
|
---|
[2423] | 316 | next = next.parent;
|
---|
| 317 | if (next == null) {
|
---|
| 318 | break;
|
---|
[2427] | 319 | }
|
---|
[2423] | 320 | sibling = next.next_sibling();
|
---|
[2165] | 321 | }
|
---|
[2423] | 322 | next = sibling;
|
---|
| 323 | return next;
|
---|
| 324 | }
|
---|
[2440] | 325 | QBLevel firstChild()
|
---|
| 326 | {
|
---|
| 327 | QBLevel ret = null;
|
---|
| 328 | for (QBLevel child : this.children) {
|
---|
[2441] | 329 | if (child == null) {
|
---|
[2440] | 330 | continue;
|
---|
[2441] | 331 | }
|
---|
[2440] | 332 | ret = child;
|
---|
| 333 | break;
|
---|
| 334 | }
|
---|
| 335 | return ret;
|
---|
| 336 | }
|
---|
[2449] | 337 | QBLevel nextNode()
|
---|
| 338 | {
|
---|
| 339 | if (this.isLeaf())
|
---|
| 340 | return this.nextSibling();
|
---|
| 341 | return this.firstChild();
|
---|
| 342 | }
|
---|
[2423] | 343 | QBLevel nextContentNode()
|
---|
| 344 | {
|
---|
[2449] | 345 | QBLevel next = this.nextNode();
|
---|
[2165] | 346 | if (next == null)
|
---|
[2449] | 347 | return next;
|
---|
| 348 | if (next.hasContent())
|
---|
| 349 | return next;
|
---|
| 350 | return next.nextContentNode();
|
---|
[2165] | 351 | }
|
---|
| 352 | int size()
|
---|
| 353 | {
|
---|
| 354 | if (isLeaf())
|
---|
| 355 | return size_leaf();
|
---|
| 356 | return size_branch();
|
---|
| 357 | }
|
---|
| 358 | private int size_leaf()
|
---|
| 359 | {
|
---|
| 360 | if (content == null) {
|
---|
[2388] | 361 | if (debug) {
|
---|
[2420] | 362 | out("["+level+"] leaf size: null content, children? " + Arrays.toString(children));
|
---|
[2388] | 363 | }
|
---|
[2165] | 364 | return 0;
|
---|
| 365 | }
|
---|
[2388] | 366 | if (debug) {
|
---|
[2165] | 367 | out("["+level+"] leaf size: " + content.size());
|
---|
[2388] | 368 | }
|
---|
[2165] | 369 | return content.size();
|
---|
| 370 | }
|
---|
| 371 | private int size_branch()
|
---|
| 372 | {
|
---|
| 373 | int ret = 0;
|
---|
| 374 | for (QBLevel l : children) {
|
---|
[2388] | 375 | if (l == null) {
|
---|
[2165] | 376 | continue;
|
---|
[2388] | 377 | }
|
---|
[2165] | 378 | ret += l.size();
|
---|
| 379 | }
|
---|
[2388] | 380 | if (content != null) {
|
---|
[2263] | 381 | ret += content.size();
|
---|
[2388] | 382 | }
|
---|
| 383 | if (debug) {
|
---|
[2165] | 384 | out("["+level+"] branch size: " + ret);
|
---|
[2388] | 385 | }
|
---|
[2165] | 386 | return ret;
|
---|
| 387 | }
|
---|
[2263] | 388 | boolean contains(T n)
|
---|
[2165] | 389 | {
|
---|
| 390 | QBLevel res = find_exact(n);
|
---|
| 391 | if (res == null)
|
---|
| 392 | return false;
|
---|
| 393 | return true;
|
---|
| 394 | }
|
---|
[2263] | 395 | QBLevel find_exact(T n)
|
---|
[2165] | 396 | {
|
---|
| 397 | if (content != null && content.contains(n))
|
---|
[2355] | 398 | return this;
|
---|
| 399 | return find_exact_child(n);
|
---|
[2165] | 400 | }
|
---|
[2355] | 401 | private QBLevel find_exact_child(T n)
|
---|
[2165] | 402 | {
|
---|
| 403 | QBLevel ret = null;
|
---|
[2355] | 404 | if (children == null)
|
---|
| 405 | return ret;
|
---|
[2165] | 406 | for (QBLevel l : children) {
|
---|
[2388] | 407 | if (l == null) {
|
---|
[2165] | 408 | continue;
|
---|
[2388] | 409 | }
|
---|
[2165] | 410 | ret = l.find_exact(n);
|
---|
[2388] | 411 | if (ret != null) {
|
---|
[2165] | 412 | break;
|
---|
[2388] | 413 | }
|
---|
[2165] | 414 | }
|
---|
| 415 | return ret;
|
---|
| 416 | }
|
---|
[2263] | 417 | boolean add(T n)
|
---|
[2165] | 418 | {
|
---|
[2263] | 419 | if (consistency_testing) {
|
---|
| 420 | if (!matches(n, this.bbox())) {
|
---|
| 421 | out("-----------------------------");
|
---|
| 422 | debug = true;
|
---|
| 423 | get_index(n, level);
|
---|
[2442] | 424 | get_index(n, level-1);
|
---|
| 425 | int nr = 0;
|
---|
| 426 | for (QBLevel sibling : parent.children) {
|
---|
| 427 | out("sibling["+ (nr++) +"]: " + sibling.bbox() + " this: " + (this==sibling));
|
---|
| 428 | }
|
---|
| 429 | abort("\nobject " + n + " does not belong in node at level: " + level + " bbox: " + this.bbox());
|
---|
[2263] | 430 | }
|
---|
| 431 | }
|
---|
[2440] | 432 | synchronized (split_lock) {
|
---|
| 433 | if (isLeaf()) {
|
---|
| 434 | __add_to_leaf(n);
|
---|
| 435 | } else {
|
---|
| 436 | __add_to_branch(n);
|
---|
| 437 | }
|
---|
[2388] | 438 | }
|
---|
[2165] | 439 | return true;
|
---|
| 440 | }
|
---|
[2440] | 441 | QBLevel __add_to_branch(T n)
|
---|
[2165] | 442 | {
|
---|
[2263] | 443 | int index = get_index(n, level);
|
---|
| 444 | if (index == -1) {
|
---|
[2388] | 445 | if (debug) {
|
---|
[2263] | 446 | out("unable to get index for " + n + "at level: " + level + ", adding content to branch: " + this);
|
---|
[2388] | 447 | }
|
---|
[2440] | 448 | this.__add_content(n);
|
---|
[2263] | 449 | return this;
|
---|
| 450 | }
|
---|
| 451 | if (debug) {
|
---|
[2165] | 452 | out("[" + level + "]: " + n +
|
---|
[2388] | 453 | " index " + index + " levelquad:" + this.quads() + " level bbox:" + this.bbox());
|
---|
[2165] | 454 | out(" put in child["+index+"]");
|
---|
[2263] | 455 | }
|
---|
[2388] | 456 | if (children[index] == null) {
|
---|
[2165] | 457 | children[index] = new QBLevel(this, index);
|
---|
[2388] | 458 | }
|
---|
[2165] | 459 | children[index].add(n);
|
---|
| 460 | return this;
|
---|
| 461 | }
|
---|
[2263] | 462 | private List<T> search(BBox search_bbox)
|
---|
[2165] | 463 | {
|
---|
[2263] | 464 | List<T> ret = null;
|
---|
[2388] | 465 | if (debug) {
|
---|
[2165] | 466 | System.out.print("[" + level + "] qb bbox: " + this.bbox() + " ");
|
---|
[2388] | 467 | }
|
---|
[2263] | 468 | if (!this.bbox().intersects(search_bbox)) {
|
---|
[2165] | 469 | if (debug) {
|
---|
| 470 | out("miss " + Long.toHexString(this.quad));
|
---|
| 471 | //QuadTiling.tile2xy(this.quad);
|
---|
| 472 | }
|
---|
| 473 | return ret;
|
---|
| 474 | }
|
---|
[2388] | 475 | if (this.hasContent()) {
|
---|
[2263] | 476 | ret = this.search_contents(search_bbox);
|
---|
[2388] | 477 | }
|
---|
| 478 | if (this.isLeaf())
|
---|
[2263] | 479 | return ret;
|
---|
| 480 | //if (this.hasContent())
|
---|
| 481 | // abort("branch had stuff");
|
---|
[2388] | 482 | if (debug) {
|
---|
[2165] | 483 | out("hit " + this.quads());
|
---|
[2388] | 484 | }
|
---|
[2165] | 485 |
|
---|
[2388] | 486 | if (debug) {
|
---|
[2165] | 487 | out("[" + level + "] not leaf, moving down");
|
---|
[2388] | 488 | }
|
---|
[2263] | 489 | for (int i = 0; i < children.length; i++) {
|
---|
| 490 | QBLevel q = children[i];
|
---|
[2388] | 491 | if (debug) {
|
---|
[2263] | 492 | out("child["+i+"]: " + q);
|
---|
[2388] | 493 | }
|
---|
| 494 | if (q == null) {
|
---|
[2165] | 495 | continue;
|
---|
[2388] | 496 | }
|
---|
| 497 | if (debug) {
|
---|
[2263] | 498 | System.out.print(i+": ");
|
---|
[2388] | 499 | }
|
---|
[2263] | 500 | List<T> coors = q.search(search_bbox);
|
---|
[2388] | 501 | if (coors == null) {
|
---|
[2165] | 502 | continue;
|
---|
[2388] | 503 | }
|
---|
| 504 | if (ret == null) {
|
---|
[2165] | 505 | ret = coors;
|
---|
[2388] | 506 | } else {
|
---|
[2165] | 507 | ret.addAll(coors);
|
---|
[2388] | 508 | }
|
---|
[2263] | 509 | if (q.bbox().bounds(search_bbox)) {
|
---|
[2165] | 510 | search_cache = q;
|
---|
| 511 | // optimization: do not continue searching
|
---|
| 512 | // other tiles if this one wholly contained
|
---|
| 513 | // what we were looking for.
|
---|
| 514 | if (coors.size() > 0 ) {
|
---|
[2388] | 515 | if (debug) {
|
---|
[2165] | 516 | out("break early");
|
---|
[2388] | 517 | }
|
---|
[2165] | 518 | break;
|
---|
| 519 | }
|
---|
| 520 | }
|
---|
| 521 | }
|
---|
| 522 | return ret;
|
---|
| 523 | }
|
---|
| 524 | public String quads()
|
---|
| 525 | {
|
---|
| 526 | return Long.toHexString(quad);
|
---|
| 527 | }
|
---|
| 528 | public void init(QBLevel parent)
|
---|
| 529 | {
|
---|
[2388] | 530 | this.parent = parent;
|
---|
| 531 | if (parent == null) {
|
---|
| 532 | this.level = 0;
|
---|
| 533 | } else {
|
---|
| 534 | this.level = parent.level + 1;
|
---|
| 535 | }
|
---|
| 536 | this.quad = 0;
|
---|
[2165] | 537 | }
|
---|
| 538 | int index_of(QBLevel find_this)
|
---|
| 539 | {
|
---|
| 540 | if (this.isLeaf())
|
---|
| 541 | return -1;
|
---|
| 542 |
|
---|
| 543 | for (int i = 0; i < QuadTiling.TILES_PER_LEVEL; i++) {
|
---|
| 544 | if (children[i] == find_this)
|
---|
| 545 | return i;
|
---|
| 546 | }
|
---|
| 547 | return -1;
|
---|
| 548 | }
|
---|
| 549 | public QBLevel(QBLevel parent, int parent_index)
|
---|
| 550 | {
|
---|
| 551 | this.init(parent);
|
---|
| 552 | int shift = (QuadBuckets.NR_LEVELS - level) * 2;
|
---|
| 553 | long mult = 1;
|
---|
| 554 | // Java blows the big one. It seems to wrap when
|
---|
| 555 | // you shift by > 31
|
---|
| 556 | if (shift >= 30) {
|
---|
| 557 | shift -= 30;
|
---|
| 558 | mult = 1<<30;
|
---|
| 559 | }
|
---|
| 560 | long this_quadpart = mult * (parent_index << shift);
|
---|
| 561 | this.quad = parent.quad | this_quadpart;
|
---|
[2388] | 562 | if (debug) {
|
---|
[2165] | 563 | out("new level["+this.level+"] bbox["+parent_index+"]: " + this.bbox()
|
---|
| 564 | + " coor: " + this.coor()
|
---|
| 565 | + " quadpart: " + Long.toHexString(this_quadpart)
|
---|
| 566 | + " quad: " + Long.toHexString(this.quad));
|
---|
[2388] | 567 | }
|
---|
[2165] | 568 | }
|
---|
| 569 | /*
|
---|
| 570 | * Surely we can calculate these efficiently instead of storing
|
---|
| 571 | */
|
---|
| 572 | double width = Double.NEGATIVE_INFINITY;
|
---|
| 573 | double width()
|
---|
| 574 | {
|
---|
| 575 | if (width != Double.NEGATIVE_INFINITY)
|
---|
| 576 | return this.width;
|
---|
| 577 | if (level == 0) {
|
---|
| 578 | width = this.bbox().width();
|
---|
| 579 | } else {
|
---|
| 580 | width = parent.width()/2;
|
---|
| 581 | }
|
---|
| 582 | return width;
|
---|
| 583 | }
|
---|
| 584 | double height()
|
---|
| 585 | {
|
---|
| 586 | return width()/2;
|
---|
| 587 | }
|
---|
| 588 | BBox bbox = null;
|
---|
| 589 | public BBox bbox()
|
---|
| 590 | {
|
---|
[2450] | 591 | if (bbox != null)
|
---|
[2165] | 592 | return bbox;
|
---|
| 593 | if (level == 0) {
|
---|
| 594 | bbox = new BBox(-180, 90, 180, -90);
|
---|
| 595 | } else {
|
---|
| 596 | LatLon bottom_left = this.coor();
|
---|
| 597 | double lat = bottom_left.lat() + this.height();
|
---|
| 598 | double lon = bottom_left.lon() + this.width();
|
---|
| 599 | LatLon top_right = new LatLon(lat, lon);
|
---|
| 600 | bbox = new BBox(bottom_left, top_right);
|
---|
| 601 | }
|
---|
| 602 | return bbox;
|
---|
| 603 | }
|
---|
| 604 | /*
|
---|
| 605 | * This gives the coordinate of the bottom-left
|
---|
| 606 | * corner of the box
|
---|
| 607 | */
|
---|
| 608 | LatLon coor()
|
---|
| 609 | {
|
---|
| 610 | return QuadTiling.tile2LatLon(this.quad);
|
---|
| 611 | }
|
---|
[2425] | 612 | boolean hasChildren()
|
---|
| 613 | {
|
---|
| 614 | if (children == null)
|
---|
| 615 | return false;
|
---|
| 616 |
|
---|
| 617 | for (QBLevel child : children) {
|
---|
| 618 | if (child != null)
|
---|
| 619 | return true;
|
---|
| 620 | }
|
---|
| 621 | return false;
|
---|
| 622 | }
|
---|
[2423] | 623 | void remove_from_parent()
|
---|
| 624 | {
|
---|
| 625 | if (parent == null)
|
---|
| 626 | return;
|
---|
| 627 |
|
---|
| 628 | int nr_siblings = 0;
|
---|
| 629 | for (int i = 0; i < parent.children.length; i++) {
|
---|
| 630 | QBLevel sibling = parent.children[i];
|
---|
[2427] | 631 | if (sibling != null) {
|
---|
[2423] | 632 | nr_siblings++;
|
---|
[2427] | 633 | }
|
---|
| 634 | if (sibling != this) {
|
---|
[2423] | 635 | continue;
|
---|
[2427] | 636 | }
|
---|
| 637 | if (!canRemove()) {
|
---|
[2626] | 638 | abort("attempt to remove non-empty child: " + this.content + " " + Arrays.toString(this.children));
|
---|
[2427] | 639 | }
|
---|
[2423] | 640 | parent.children[i] = null;
|
---|
| 641 | nr_siblings--;
|
---|
| 642 | }
|
---|
[2427] | 643 | if (parent.canRemove()) {
|
---|
[2423] | 644 | parent.remove_from_parent();
|
---|
[2427] | 645 | }
|
---|
[2423] | 646 | }
|
---|
| 647 | boolean canRemove()
|
---|
| 648 | {
|
---|
| 649 | if (content != null && content.size() > 0)
|
---|
| 650 | return false;
|
---|
[2425] | 651 | if (this.hasChildren())
|
---|
| 652 | return false;
|
---|
[2423] | 653 | return true;
|
---|
| 654 | }
|
---|
[2165] | 655 | }
|
---|
| 656 |
|
---|
| 657 | private QBLevel root;
|
---|
| 658 | private QBLevel search_cache;
|
---|
| 659 | public QuadBuckets()
|
---|
| 660 | {
|
---|
| 661 | clear();
|
---|
| 662 | }
|
---|
| 663 | public void clear()
|
---|
| 664 | {
|
---|
| 665 | root = new QBLevel(null);
|
---|
| 666 | search_cache = null;
|
---|
[2263] | 667 | if (debug) {
|
---|
[2165] | 668 | out("QuadBuckets() cleared: " + this);
|
---|
[2263] | 669 | out("root: " + root + " level: " + root.level + " bbox: " + root.bbox());
|
---|
| 670 | }
|
---|
[2165] | 671 | }
|
---|
[2263] | 672 | public boolean add(T n)
|
---|
[2165] | 673 | {
|
---|
[2388] | 674 | if (debug) {
|
---|
[2263] | 675 | out("QuadBuckets() add: " + n + " size now: " + this.size());
|
---|
[2388] | 676 | }
|
---|
[2165] | 677 | int before_size = -1;
|
---|
[2388] | 678 | if (consistency_testing) {
|
---|
[2165] | 679 | before_size = root.size();
|
---|
[2388] | 680 | }
|
---|
[2263] | 681 | boolean ret;
|
---|
| 682 | // A way with no nodes will have an infinitely large
|
---|
| 683 | // bounding box. Just stash it in the root node.
|
---|
[2388] | 684 | if (!n.isUsable()) {
|
---|
[2440] | 685 | synchronized (split_lock) {
|
---|
| 686 | ret = root.__add_content(n);
|
---|
| 687 | }
|
---|
[2388] | 688 | } else {
|
---|
[2263] | 689 | ret = root.add(n);
|
---|
[2388] | 690 | }
|
---|
[2165] | 691 | if (consistency_testing) {
|
---|
| 692 | int end_size = root.size();
|
---|
[2388] | 693 | if (ret) {
|
---|
[2165] | 694 | end_size--;
|
---|
[2388] | 695 | }
|
---|
| 696 | if (before_size != end_size) {
|
---|
[2165] | 697 | abort("size inconsistency before add: " + before_size + " after: " + end_size);
|
---|
[2388] | 698 | }
|
---|
[2165] | 699 | }
|
---|
[2388] | 700 | if (debug) {
|
---|
[2263] | 701 | out("QuadBuckets() add: " + n + " size after: " + this.size());
|
---|
[2388] | 702 | }
|
---|
[2165] | 703 | return ret;
|
---|
| 704 | }
|
---|
[2263] | 705 | public void reindex()
|
---|
| 706 | {
|
---|
| 707 | QBLevel newroot = new QBLevel(null);
|
---|
| 708 | Iterator<T> i = this.iterator();
|
---|
| 709 | while (i.hasNext()) {
|
---|
| 710 | T o = i.next();
|
---|
| 711 | newroot.add(o);
|
---|
| 712 | }
|
---|
| 713 | root = newroot;
|
---|
| 714 | }
|
---|
[2165] | 715 | public void unsupported()
|
---|
| 716 | {
|
---|
| 717 | out("unsupported operation");
|
---|
| 718 | throw new UnsupportedOperationException();
|
---|
| 719 | }
|
---|
[2420] | 720 | public boolean retainAll(Collection<?> objects)
|
---|
[2165] | 721 | {
|
---|
[2263] | 722 | for (T o : this) {
|
---|
[2388] | 723 | if (objects.contains(o)) {
|
---|
[2197] | 724 | continue;
|
---|
[2388] | 725 | }
|
---|
[2263] | 726 | if (!this.remove(o))
|
---|
[2197] | 727 | return false;
|
---|
| 728 | }
|
---|
| 729 | return true;
|
---|
[2165] | 730 | }
|
---|
[2420] | 731 | public boolean removeAll(Collection<?> objects)
|
---|
[2263] | 732 | {
|
---|
[2427] | 733 | boolean changed = false;
|
---|
[2263] | 734 | for (Object o : objects) {
|
---|
[2427] | 735 | changed = changed | remove(o);
|
---|
[2197] | 736 | }
|
---|
[2427] | 737 | return changed;
|
---|
[2165] | 738 | }
|
---|
[2420] | 739 | public boolean addAll(Collection<? extends T> objects)
|
---|
[2165] | 740 | {
|
---|
[2427] | 741 | boolean changed = false;
|
---|
[2263] | 742 | for (Object o : objects) {
|
---|
[2427] | 743 | changed = changed | this.add(convert(o));
|
---|
[2197] | 744 | }
|
---|
[2427] | 745 | return changed;
|
---|
[2165] | 746 | }
|
---|
[2420] | 747 | public boolean containsAll(Collection<?> objects)
|
---|
[2165] | 748 | {
|
---|
[2263] | 749 | for (Object o : objects) {
|
---|
| 750 | if (!this.contains(o))
|
---|
[2197] | 751 | return false;
|
---|
| 752 | }
|
---|
| 753 | return true;
|
---|
[2165] | 754 | }
|
---|
[2263] | 755 | // If anyone has suggestions for how to fix
|
---|
| 756 | // this properly, I'm listening :)
|
---|
[2353] | 757 | @SuppressWarnings("unchecked")
|
---|
[2263] | 758 | private T convert(Object raw)
|
---|
| 759 | {
|
---|
| 760 | return (T)raw;
|
---|
| 761 | }
|
---|
[2165] | 762 | public boolean remove(Object o)
|
---|
| 763 | {
|
---|
[2263] | 764 | return this.remove(convert(o));
|
---|
[2165] | 765 | }
|
---|
[2355] | 766 | public boolean remove_slow(T removeme)
|
---|
[2165] | 767 | {
|
---|
[2355] | 768 | boolean ret = false;
|
---|
| 769 | Iterator<T> i = this.iterator();
|
---|
| 770 | while (i.hasNext()) {
|
---|
| 771 | T o = i.next();
|
---|
[2388] | 772 | if (o != removeme) {
|
---|
[2355] | 773 | continue;
|
---|
[2388] | 774 | }
|
---|
[2355] | 775 | i.remove();
|
---|
| 776 | ret = true;
|
---|
| 777 | break;
|
---|
| 778 | }
|
---|
[2388] | 779 | if (debug) {
|
---|
[2355] | 780 | out("qb slow remove result: " + ret);
|
---|
[2388] | 781 | }
|
---|
[2355] | 782 | return ret;
|
---|
| 783 | }
|
---|
| 784 | public boolean remove(T o)
|
---|
| 785 | {
|
---|
| 786 | /*
|
---|
| 787 | * We first try a locational search
|
---|
| 788 | */
|
---|
| 789 | QBLevel bucket = root.find_exact(o);
|
---|
| 790 | /*
|
---|
| 791 | * That may fail because the object was
|
---|
| 792 | * moved or changed in some way, so we
|
---|
| 793 | * resort to an iterative search:
|
---|
| 794 | */
|
---|
[2286] | 795 | if (bucket == null)
|
---|
[2355] | 796 | return remove_slow(o);
|
---|
| 797 | boolean ret = bucket.remove_content(o);
|
---|
[2388] | 798 | if (debug) {
|
---|
[2355] | 799 | out("qb remove result: " + ret);
|
---|
[2388] | 800 | }
|
---|
[2263] | 801 | return ret;
|
---|
[2165] | 802 | }
|
---|
| 803 | public boolean contains(Object o)
|
---|
| 804 | {
|
---|
[2263] | 805 | QBLevel bucket = root.find_exact(convert(o));
|
---|
[2165] | 806 | if (bucket == null)
|
---|
| 807 | return false;
|
---|
| 808 | return true;
|
---|
| 809 | }
|
---|
[2263] | 810 | public ArrayList<T> toArrayList()
|
---|
[2165] | 811 | {
|
---|
[2263] | 812 | ArrayList<T> a = new ArrayList<T>();
|
---|
[2388] | 813 | for (T n : this) {
|
---|
[2165] | 814 | a.add(n);
|
---|
[2388] | 815 | }
|
---|
| 816 | if (debug) {
|
---|
| 817 | out("returning array list with size: " + a.size());
|
---|
| 818 | }
|
---|
[2165] | 819 | return a;
|
---|
| 820 | }
|
---|
| 821 | public Object[] toArray()
|
---|
| 822 | {
|
---|
| 823 | return this.toArrayList().toArray();
|
---|
| 824 | }
|
---|
[2420] | 825 | public <A> A[] toArray(A[] template)
|
---|
[2165] | 826 | {
|
---|
| 827 | return this.toArrayList().toArray(template);
|
---|
| 828 | }
|
---|
[2263] | 829 | class QuadBucketIterator implements Iterator<T>
|
---|
[2165] | 830 | {
|
---|
[2263] | 831 | QBLevel current_node;
|
---|
| 832 | int content_index;
|
---|
[2165] | 833 | int iterated_over;
|
---|
[2263] | 834 | QBLevel next_content_node(QBLevel q)
|
---|
[2165] | 835 | {
|
---|
| 836 | if (q == null)
|
---|
| 837 | return null;
|
---|
| 838 | QBLevel orig = q;
|
---|
[2440] | 839 | QBLevel next;
|
---|
| 840 | synchronized (split_lock) {
|
---|
| 841 | next = q.nextContentNode();
|
---|
| 842 | }
|
---|
[2263] | 843 | //if (consistency_testing && (orig == next))
|
---|
[2388] | 844 | if (orig == next) {
|
---|
[2165] | 845 | abort("got same leaf back leaf: " + q.isLeaf());
|
---|
[2388] | 846 | }
|
---|
[2165] | 847 | return next;
|
---|
| 848 | }
|
---|
[2263] | 849 | public QuadBucketIterator(QuadBuckets<T> qb)
|
---|
[2165] | 850 | {
|
---|
[2388] | 851 | if (debug) {
|
---|
[2263] | 852 | out(this + " is a new iterator qb: " + qb + " size: " + qb.size());
|
---|
[2388] | 853 | }
|
---|
| 854 | if (qb.root.isLeaf() || qb.root.hasContent()) {
|
---|
[2263] | 855 | current_node = qb.root;
|
---|
[2388] | 856 | } else {
|
---|
[2263] | 857 | current_node = next_content_node(qb.root);
|
---|
[2388] | 858 | }
|
---|
| 859 | if (debug) {
|
---|
[2263] | 860 | out("\titerator first leaf: " + current_node);
|
---|
[2388] | 861 | }
|
---|
[2165] | 862 | iterated_over = 0;
|
---|
| 863 | }
|
---|
| 864 | public boolean hasNext()
|
---|
| 865 | {
|
---|
| 866 | if (this.peek() == null) {
|
---|
[2388] | 867 | if (debug) {
|
---|
| 868 | out(this + " no hasNext(), but iterated over so far: " + iterated_over);
|
---|
| 869 | }
|
---|
[2165] | 870 | return false;
|
---|
| 871 | }
|
---|
| 872 | return true;
|
---|
| 873 | }
|
---|
[2263] | 874 | T peek()
|
---|
[2165] | 875 | {
|
---|
[2263] | 876 | if (current_node == null) {
|
---|
[2388] | 877 | if (debug) {
|
---|
[2165] | 878 | out("null current leaf, nowhere to go");
|
---|
[2388] | 879 | }
|
---|
[2165] | 880 | return null;
|
---|
| 881 | }
|
---|
[2263] | 882 | while((current_node.content == null) ||
|
---|
[2388] | 883 | (content_index >= current_node.content.size())) {
|
---|
| 884 | if (debug) {
|
---|
[2165] | 885 | out("moving to next leaf");
|
---|
[2388] | 886 | }
|
---|
[2263] | 887 | content_index = 0;
|
---|
| 888 | current_node = next_content_node(current_node);
|
---|
[2388] | 889 | if (current_node == null) {
|
---|
[2165] | 890 | break;
|
---|
[2388] | 891 | }
|
---|
[2165] | 892 | }
|
---|
[2263] | 893 | if (current_node == null || current_node.content == null) {
|
---|
[2388] | 894 | if (debug) {
|
---|
[2263] | 895 | out("late nowhere to go " + current_node);
|
---|
[2388] | 896 | }
|
---|
[2165] | 897 | return null;
|
---|
| 898 | }
|
---|
[2263] | 899 | return current_node.content.get(content_index);
|
---|
[2165] | 900 | }
|
---|
[2263] | 901 | public T next()
|
---|
[2165] | 902 | {
|
---|
[2263] | 903 | T ret = peek();
|
---|
| 904 | content_index++;
|
---|
[2388] | 905 | if (debug) {
|
---|
[2263] | 906 | out("iteration["+iterated_over+"] " + content_index + " leaf: " + current_node);
|
---|
[2388] | 907 | }
|
---|
[2165] | 908 | iterated_over++;
|
---|
| 909 | if (ret == null) {
|
---|
[2388] | 910 | if (debug) {
|
---|
[2165] | 911 | out(this + " no next node, but iterated over so far: " + iterated_over);
|
---|
[2388] | 912 | }
|
---|
[2165] | 913 | }
|
---|
| 914 | return ret;
|
---|
| 915 | }
|
---|
| 916 | public void remove()
|
---|
| 917 | {
|
---|
[2197] | 918 | // two uses
|
---|
| 919 | // 1. Back up to the thing we just returned
|
---|
| 920 | // 2. move the index back since we removed
|
---|
| 921 | // an element
|
---|
[2263] | 922 | content_index--;
|
---|
[2423] | 923 | T object = peek(); //TODO Is the call to peek() necessary?
|
---|
| 924 | current_node.remove_content(object);
|
---|
[2165] | 925 | }
|
---|
| 926 | }
|
---|
[2263] | 927 | public Iterator<T> iterator()
|
---|
[2165] | 928 | {
|
---|
| 929 | return new QuadBucketIterator(this);
|
---|
| 930 | }
|
---|
| 931 | public int size()
|
---|
| 932 | {
|
---|
| 933 | // This can certainly by optimized
|
---|
| 934 | int ret = root.size();
|
---|
[2388] | 935 | if (debug) {
|
---|
[2165] | 936 | out(this + " QuadBuckets size: " + ret);
|
---|
[2388] | 937 | }
|
---|
[2165] | 938 | return ret;
|
---|
| 939 | }
|
---|
[2263] | 940 | public int size_iter()
|
---|
| 941 | {
|
---|
| 942 | int count = 0;
|
---|
| 943 | Iterator<T> i = this.iterator();
|
---|
| 944 | while (i.hasNext()) {
|
---|
| 945 | i.next();
|
---|
| 946 | count++;
|
---|
| 947 | }
|
---|
| 948 | return count;
|
---|
| 949 | }
|
---|
[2165] | 950 | public boolean isEmpty()
|
---|
| 951 | {
|
---|
| 952 | if (this.size() == 0)
|
---|
| 953 | return true;
|
---|
| 954 | return false;
|
---|
| 955 | }
|
---|
[2430] | 956 | public static BBox search_to_bbox(LatLon point, double radius)
|
---|
[2165] | 957 | {
|
---|
| 958 | BBox bbox = new BBox(point.lon() - radius, point.lat() - radius,
|
---|
[2441] | 959 | point.lon() + radius, point.lat() + radius);
|
---|
[2388] | 960 | if (debug) {
|
---|
[2165] | 961 | out("search bbox before sanity: " + bbox);
|
---|
[2388] | 962 | }
|
---|
| 963 | if (debug) {
|
---|
[2165] | 964 | out("search bbox after sanity: " + bbox);
|
---|
[2388] | 965 | }
|
---|
[2165] | 966 | return bbox;
|
---|
| 967 | }
|
---|
[2263] | 968 | List<T> search(Way w)
|
---|
[2165] | 969 | {
|
---|
[2263] | 970 | BBox way_bbox = new BBox(w);
|
---|
| 971 | return this.search(way_bbox);
|
---|
[2165] | 972 | }
|
---|
[2263] | 973 | public List<T> search(Node n, double radius)
|
---|
[2165] | 974 | {
|
---|
[2263] | 975 | return this.search(n.getCoor(), radius);
|
---|
[2165] | 976 | }
|
---|
[2263] | 977 | public List<T> search(LatLon point, double radius)
|
---|
[2165] | 978 | {
|
---|
[2263] | 979 | if (point == null)
|
---|
| 980 | return Collections.emptyList();
|
---|
| 981 | return this.search(search_to_bbox(point, radius));
|
---|
| 982 | }
|
---|
| 983 | public List<T> search(LatLon b1, LatLon b2)
|
---|
| 984 | {
|
---|
[2165] | 985 | BBox bbox = new BBox(b1.lon(), b1.lat(), b2.lon(), b2.lat());
|
---|
| 986 | return this.search(bbox);
|
---|
| 987 | }
|
---|
[2263] | 988 | List<T> search(BBox search_bbox)
|
---|
[2165] | 989 | {
|
---|
| 990 | if (debug) {
|
---|
[2263] | 991 | out("qb root search at " + search_bbox);
|
---|
[2165] | 992 | out("root bbox: " + root.bbox());
|
---|
| 993 | }
|
---|
[2263] | 994 | List<T> ret;
|
---|
[2165] | 995 | // Doing this cuts down search cost on a real-life data
|
---|
| 996 | // set by about 25%
|
---|
| 997 | boolean cache_searches = true;
|
---|
| 998 | if (cache_searches) {
|
---|
[2388] | 999 | if (search_cache == null) {
|
---|
[2165] | 1000 | search_cache = root;
|
---|
[2388] | 1001 | }
|
---|
[2165] | 1002 | // Walk back up the tree when the last
|
---|
| 1003 | // search spot can not cover the current
|
---|
| 1004 | // search
|
---|
[2263] | 1005 | while (!search_cache.bbox().bounds(search_bbox)) {
|
---|
[2388] | 1006 | if (debug) {
|
---|
[2355] | 1007 | out("bbox: " + search_bbox);
|
---|
[2388] | 1008 | }
|
---|
[2263] | 1009 | if (debug) {
|
---|
| 1010 | out("search_cache: " + search_cache + " level: " + search_cache.level);
|
---|
| 1011 | out("search_cache.bbox(): " + search_cache.bbox());
|
---|
| 1012 | }
|
---|
[2165] | 1013 | search_cache = search_cache.parent;
|
---|
[2388] | 1014 | if (debug) {
|
---|
[2263] | 1015 | out("new search_cache: " + search_cache);
|
---|
[2388] | 1016 | }
|
---|
[2165] | 1017 | }
|
---|
| 1018 | } else {
|
---|
| 1019 | search_cache = root;
|
---|
| 1020 | }
|
---|
[2441] | 1021 |
|
---|
| 1022 | // Save parent because search_cache might change during search call
|
---|
| 1023 | QBLevel tmp = search_cache.parent;
|
---|
| 1024 |
|
---|
[2263] | 1025 | ret = search_cache.search(search_bbox);
|
---|
[2388] | 1026 | if (ret == null) {
|
---|
[2263] | 1027 | ret = new ArrayList<T>();
|
---|
[2388] | 1028 | }
|
---|
[2441] | 1029 |
|
---|
[2263] | 1030 | // A way that spans this bucket may be stored in one
|
---|
| 1031 | // of the nodes which is a parent of the search cache
|
---|
| 1032 | while (tmp != null) {
|
---|
| 1033 | List<T> content_result = tmp.search_contents(search_bbox);
|
---|
[2388] | 1034 | if (content_result != null) {
|
---|
[2263] | 1035 | ret.addAll(content_result);
|
---|
[2388] | 1036 | }
|
---|
[2263] | 1037 | tmp = tmp.parent;
|
---|
| 1038 | }
|
---|
[2388] | 1039 | if (debug) {
|
---|
[2263] | 1040 | out("search of QuadBuckets for " + search_bbox + " ret len: " + ret.size());
|
---|
[2388] | 1041 | }
|
---|
[2263] | 1042 | return ret;
|
---|
[2165] | 1043 | }
|
---|
[2441] | 1044 |
|
---|
| 1045 | public void printTree() {
|
---|
| 1046 | printTreeRecursive(root, 0);
|
---|
| 1047 | }
|
---|
| 1048 |
|
---|
| 1049 | private void printTreeRecursive(QBLevel level, int indent) {
|
---|
| 1050 | if (level == null) {
|
---|
| 1051 | printIndented(indent, "<empty child>");
|
---|
| 1052 | return;
|
---|
| 1053 | }
|
---|
| 1054 | printIndented(indent, level);
|
---|
| 1055 | if (level.hasContent()) {
|
---|
| 1056 | for (T o:level.content) {
|
---|
| 1057 | printIndented(indent, o);
|
---|
| 1058 | }
|
---|
| 1059 | }
|
---|
| 1060 | if (level.children != null) {
|
---|
| 1061 | for (QBLevel child:level.children) {
|
---|
| 1062 | printTreeRecursive(child, indent + 2);
|
---|
| 1063 | }
|
---|
| 1064 | }
|
---|
| 1065 | }
|
---|
| 1066 |
|
---|
| 1067 | private void printIndented(int indent, Object msg) {
|
---|
| 1068 | for (int i=0; i<indent; i++) {
|
---|
| 1069 | System.out.print(' ');
|
---|
| 1070 | }
|
---|
| 1071 | System.out.println(msg);
|
---|
| 1072 | }
|
---|
[2165] | 1073 | }
|
---|