| | 1 | package org.openstreetmap.josm.data.osm; |
| | 2 | import java.util.ArrayList; |
| | 3 | import java.util.Collection; |
| | 4 | import java.util.Collections; |
| | 5 | import java.util.Set; |
| | 6 | import java.util.SortedMap; |
| | 7 | import java.util.TreeMap; |
| | 8 | |
| | 9 | import org.openstreetmap.josm.data.coor.EastNorth; |
| | 10 | import org.openstreetmap.josm.data.osm.Node; |
| | 11 | import org.openstreetmap.josm.tools.Pair; |
| | 12 | |
| | 13 | import java.nio.MappedByteBuffer; |
| | 14 | import java.nio.DoubleBuffer; |
| | 15 | import java.nio.channels.FileChannel; |
| | 16 | import java.io.FileOutputStream; |
| | 17 | import java.io.RandomAccessFile; |
| | 18 | |
| | 19 | import java.io.IOException; |
| | 20 | import java.io.BufferedReader; |
| | 21 | import java.io.InputStreamReader; |
| | 22 | |
| | 23 | import java.util.Map.Entry; |
| | 24 | import java.awt.geom.Point2D; |
| | 25 | |
| | 26 | //import java.util.List; |
| | 27 | import java.util.*; |
| | 28 | import java.util.Collection; |
| | 29 | |
| | 30 | import org.openstreetmap.josm.data.coor.QuadTiling; |
| | 31 | import org.openstreetmap.josm.data.coor.CachedLatLon; |
| | 32 | import org.openstreetmap.josm.data.coor.EastNorth; |
| | 33 | import org.openstreetmap.josm.data.coor.LatLon; |
| | 34 | import org.openstreetmap.josm.data.osm.visitor.Visitor; |
| | 35 | |
| | 36 | |
| | 37 | public class QuadBuckets implements Collection<Node> |
| | 38 | { |
| | 39 | public static boolean debug = false; |
| | 40 | static boolean consistency_testing = false; |
| | 41 | |
| | 42 | static void abort(String s) |
| | 43 | { |
| | 44 | out(s); |
| | 45 | Object o = null; |
| | 46 | o.hashCode(); |
| | 47 | } |
| | 48 | static void out(String s) |
| | 49 | { |
| | 50 | System.out.println(s); |
| | 51 | } |
| | 52 | // periodic output |
| | 53 | long last_out = -1; |
| | 54 | void pout(String s) |
| | 55 | { |
| | 56 | long now = System.currentTimeMillis(); |
| | 57 | if (now - last_out < 300) |
| | 58 | return; |
| | 59 | last_out = now; |
| | 60 | System.out.print(s + "\r"); |
| | 61 | } |
| | 62 | void pout(String s, int i, int total) |
| | 63 | { |
| | 64 | long now = System.currentTimeMillis(); |
| | 65 | if ((now - last_out < 300) && |
| | 66 | ((i+1) < total)) |
| | 67 | return; |
| | 68 | last_out = now; |
| | 69 | // cast to float to keep the output size down |
| | 70 | System.out.print(s + " " + (float)((i+1)*100.0/total) + "% done \r"); |
| | 71 | } |
| | 72 | // 24 levels of buckets gives us bottom-level |
| | 73 | // buckets that are about 2 meters on a side. |
| | 74 | // That should do. :) |
| | 75 | public static int NR_LEVELS = 24; |
| | 76 | public static double WORLD_PARTS = (1 << NR_LEVELS); |
| | 77 | |
| | 78 | public static int MAX_OBJECTS_PER_LEVEL = 16; |
| | 79 | // has to be a power of 2 |
| | 80 | public static int TILES_PER_LEVEL_SHIFT = 2; |
| | 81 | public static int TILES_PER_LEVEL = 1<<TILES_PER_LEVEL_SHIFT; |
| | 82 | class BBox |
| | 83 | { |
| | 84 | private double xmin; |
| | 85 | private double xmax; |
| | 86 | private double ymin; |
| | 87 | private double ymax; |
| | 88 | void sanity() |
| | 89 | { |
| | 90 | if (xmin < -180.0) |
| | 91 | xmin = -180.0; |
| | 92 | if (xmax > 180.0) |
| | 93 | xmax = 180.0; |
| | 94 | if (ymin < -90.0) |
| | 95 | ymin = -90.0; |
| | 96 | if (ymax > 90.0) |
| | 97 | ymax = 90.0; |
| | 98 | if ((xmin < -180.0) || |
| | 99 | (xmax > 180.0) || |
| | 100 | (ymin < -90.0) || |
| | 101 | (ymax > 90.0)) { |
| | 102 | out("bad BBox: " + this); |
| | 103 | Object o = null; |
| | 104 | o.hashCode(); |
| | 105 | } |
| | 106 | } |
| | 107 | public String toString() |
| | 108 | { |
| | 109 | return "[ " + xmin + " -> " + xmax + ", " + |
| | 110 | ymin + " -> " + ymax + " ]"; |
| | 111 | } |
| | 112 | double min(double a, double b) |
| | 113 | { |
| | 114 | if (a < b) |
| | 115 | return a; |
| | 116 | return b; |
| | 117 | } |
| | 118 | double max(double a, double b) |
| | 119 | { |
| | 120 | if (a > b) |
| | 121 | return a; |
| | 122 | return b; |
| | 123 | } |
| | 124 | public BBox(LatLon a, LatLon b) |
| | 125 | { |
| | 126 | xmin = min(a.lon(), b.lon()); |
| | 127 | xmax = max(a.lon(), b.lon()); |
| | 128 | ymin = min(a.lat(), b.lat()); |
| | 129 | ymax = max(a.lat(), b.lat()); |
| | 130 | sanity(); |
| | 131 | } |
| | 132 | public BBox(double a_x, double a_y, double b_x, double b_y) |
| | 133 | { |
| | 134 | xmin = min(a_x, b_x); |
| | 135 | xmax = max(a_x, b_x); |
| | 136 | ymin = min(a_y, b_y); |
| | 137 | ymax = max(a_y, b_y); |
| | 138 | sanity(); |
| | 139 | } |
| | 140 | public double height() |
| | 141 | { |
| | 142 | return ymax-ymin; |
| | 143 | } |
| | 144 | public double width() |
| | 145 | { |
| | 146 | return xmax-xmin; |
| | 147 | } |
| | 148 | boolean bounds(BBox b) |
| | 149 | { |
| | 150 | if (!(xmin <= b.xmin) || |
| | 151 | !(xmax >= b.xmax) || |
| | 152 | !(ymin <= b.ymin) || |
| | 153 | !(ymax >= b.ymax)) |
| | 154 | return false; |
| | 155 | return true; |
| | 156 | } |
| | 157 | boolean bounds(LatLon c) |
| | 158 | { |
| | 159 | if ((xmin <= c.lon()) && |
| | 160 | (xmax >= c.lon()) && |
| | 161 | (ymin <= c.lat()) && |
| | 162 | (ymax >= c.lat())) |
| | 163 | return true; |
| | 164 | return false; |
| | 165 | } |
| | 166 | boolean inside(BBox b) |
| | 167 | { |
| | 168 | if (xmin >= b.xmax) |
| | 169 | return false; |
| | 170 | if (xmax <= b.xmin) |
| | 171 | return false; |
| | 172 | if (ymin >= b.ymax) |
| | 173 | return false; |
| | 174 | if (ymax <= b.ymin) |
| | 175 | return false; |
| | 176 | return true; |
| | 177 | } |
| | 178 | boolean intersects(BBox b) |
| | 179 | { |
| | 180 | return this.inside(b) || b.inside(this); |
| | 181 | } |
| | 182 | } |
| | 183 | class QBLevel |
| | 184 | { |
| | 185 | int level; |
| | 186 | long quad; |
| | 187 | QBLevel parent; |
| | 188 | |
| | 189 | public List<Node> content; |
| | 190 | public QBLevel children[]; |
| | 191 | public QBLevel(QBLevel parent) |
| | 192 | { |
| | 193 | init(parent); |
| | 194 | } |
| | 195 | String quads(Node n) |
| | 196 | { |
| | 197 | return Long.toHexString(QuadTiling.quadTile(n.getCoor())); |
| | 198 | } |
| | 199 | void split() |
| | 200 | { |
| | 201 | if (debug) |
| | 202 | out("splitting "+this.bbox()+" level "+level+" with " |
| | 203 | + content.size() + " entries (my dimensions: " |
| | 204 | + this.bbox.width()+", "+this.bbox.height()+")"); |
| | 205 | if (children != null) { |
| | 206 | abort("overwrote children"); |
| | 207 | } |
| | 208 | children = new QBLevel[QuadTiling.TILES_PER_LEVEL]; |
| | 209 | // deferring allocation of children until use |
| | 210 | // seems a bit faster |
| | 211 | //for (int i = 0; i < TILES_PER_LEVEL; i++) |
| | 212 | // children[i] = new QBLevel(this, i); |
| | 213 | List<Node> tmpcontent = content; |
| | 214 | content = null; |
| | 215 | for (Node n : tmpcontent) { |
| | 216 | int new_index = QuadTiling.index(n, level); |
| | 217 | if (children[new_index] == null) |
| | 218 | children[new_index] = new QBLevel(this, new_index); |
| | 219 | QBLevel child = children[new_index]; |
| | 220 | if (debug) |
| | 221 | out("putting "+n+"(q:"+quads(n)+") into ["+new_index+"] " + child.bbox()); |
| | 222 | child.add(n); |
| | 223 | } |
| | 224 | } |
| | 225 | void add_leaf(Node n) |
| | 226 | { |
| | 227 | LatLon c = n.getCoor(); |
| | 228 | QBLevel ret = this; |
| | 229 | if (content == null) { |
| | 230 | if (debug) |
| | 231 | out(" new content array"); |
| | 232 | // I chose a LinkedList because we do not do |
| | 233 | // any real random access to this. We generally |
| | 234 | // just run through the whole thing all at once |
| | 235 | content = new LinkedList<Node>(); |
| | 236 | } |
| | 237 | content.add(n); |
| | 238 | if (content.size() > MAX_OBJECTS_PER_LEVEL) { |
| | 239 | if (debug) |
| | 240 | out("["+level+"] deciding to split"); |
| | 241 | if (level >= NR_LEVELS-1) { |
| | 242 | out("can not split, but too deep: " + level + " size: " + content.size()); |
| | 243 | return; |
| | 244 | } |
| | 245 | int before_size = -1; |
| | 246 | if (consistency_testing) |
| | 247 | before_size = this.size(); |
| | 248 | this.split(); |
| | 249 | if (consistency_testing) { |
| | 250 | int after_size = this.size(); |
| | 251 | if (before_size != after_size) { |
| | 252 | abort("["+level+"] split done before: " + before_size + " after: " + after_size); |
| | 253 | } |
| | 254 | } |
| | 255 | return; |
| | 256 | } |
| | 257 | if (debug) { |
| | 258 | out(" plain content put now have " + content.size()); |
| | 259 | if (content.contains(c)) |
| | 260 | out(" and I already have that one"); |
| | 261 | } |
| | 262 | } |
| | 263 | |
| | 264 | List<Node> search(BBox bbox, LatLon point, double radius) |
| | 265 | { |
| | 266 | if (isLeaf()) |
| | 267 | return search_leaf(bbox, point, radius); |
| | 268 | return search_branch(bbox, point, radius); |
| | 269 | } |
| | 270 | private List<Node> search_leaf(BBox bbox, LatLon point, double radius) |
| | 271 | { |
| | 272 | if (debug) |
| | 273 | out("searching contents in tile " + this.bbox() + " for " + bbox); |
| | 274 | /* |
| | 275 | * It is possible that this was created in a split |
| | 276 | * but never got any content populated. |
| | 277 | */ |
| | 278 | if (content == null) |
| | 279 | return null; |
| | 280 | // We could delay allocating this. But, the way it is currently |
| | 281 | // used, we almost always have a node in the area that we're |
| | 282 | // searching since we're searching around other nodes. |
| | 283 | // |
| | 284 | // the iterator's calls to ArrayList.size() were showing up in |
| | 285 | // some profiles, so I'm trying a LinkedList instead |
| | 286 | List<Node> ret = new LinkedList<Node>(); |
| | 287 | for (Node n : content) { |
| | 288 | // This supports two modes: one where the bbox is just |
| | 289 | // an outline of the point/radius combo, and one where |
| | 290 | // it is *just* the bbox. If it is *just* the bbox, |
| | 291 | // don't consider the points below |
| | 292 | if (point == null) { |
| | 293 | ret.add(n); |
| | 294 | continue; |
| | 295 | } |
| | 296 | LatLon c = n.getCoor(); |
| | 297 | // is greatCircleDistance really necessary? |
| | 298 | double d = c.greatCircleDistance(point); |
| | 299 | if (debug) |
| | 300 | out("[" + level + "] Checking coor: " + c + " dist: " + d + " vs. " + radius + " " + quads(n)); |
| | 301 | if (d > radius) |
| | 302 | continue; |
| | 303 | if (debug) |
| | 304 | out("hit in quad: "+Long.toHexString(this.quad)+"\n"); |
| | 305 | //if (ret == null) |
| | 306 | // ret = new LinkedList<Node>(); |
| | 307 | ret.add(n); |
| | 308 | } |
| | 309 | if (debug) |
| | 310 | out("done searching quad " + Long.toHexString(this.quad)); |
| | 311 | return ret; |
| | 312 | } |
| | 313 | /* |
| | 314 | * This is stupid. I tried to have a QBLeaf and QBBranch |
| | 315 | * class decending from a QBLevel. It's more than twice |
| | 316 | * as slow. So, this throws OO out the window, but it |
| | 317 | * is fast. Runtime type determination must be slow. |
| | 318 | */ |
| | 319 | boolean isLeaf() |
| | 320 | { |
| | 321 | if (children == null) |
| | 322 | return true; |
| | 323 | return false; |
| | 324 | } |
| | 325 | QBLevel next_sibling() |
| | 326 | { |
| | 327 | boolean found_me = false; |
| | 328 | if (parent == null) |
| | 329 | return null; |
| | 330 | int __nr = 0; |
| | 331 | for (QBLevel sibling : parent.children) { |
| | 332 | __nr++; |
| | 333 | int nr = __nr-1; |
| | 334 | if (sibling == null) { |
| | 335 | if (debug) |
| | 336 | out("[" + this.level + "] null child nr: " + nr); |
| | 337 | continue; |
| | 338 | } |
| | 339 | // We're looking for the *next* child |
| | 340 | // after us. |
| | 341 | if (sibling == this) { |
| | 342 | if (debug) |
| | 343 | out("[" + this.level + "] I was child nr: " + nr); |
| | 344 | found_me = true; |
| | 345 | continue; |
| | 346 | } |
| | 347 | if (found_me) { |
| | 348 | if (debug) |
| | 349 | out("[" + this.level + "] next sibling was child nr: " + nr); |
| | 350 | return sibling; |
| | 351 | } |
| | 352 | if (debug) |
| | 353 | out("[" + this.level + "] nr: " + nr + " is before me, ignoring..."); |
| | 354 | } |
| | 355 | return null; |
| | 356 | } |
| | 357 | QBLevel nextLeaf() |
| | 358 | { |
| | 359 | QBLevel next = this; |
| | 360 | if (this.isLeaf()) { |
| | 361 | QBLevel sibling = next.next_sibling(); |
| | 362 | // Walk back up the tree to find the |
| | 363 | // next sibling node. It may be either |
| | 364 | // a leaf or branch. |
| | 365 | while (sibling == null) { |
| | 366 | if (debug) |
| | 367 | out("no siblings at level["+next.level+"], moving to parent"); |
| | 368 | next = next.parent; |
| | 369 | if (next == null) |
| | 370 | break; |
| | 371 | sibling = next.next_sibling(); |
| | 372 | } |
| | 373 | next = sibling; |
| | 374 | } |
| | 375 | if (next == null) |
| | 376 | return null; |
| | 377 | // all branches are guaranteed to have |
| | 378 | // at least one leaf. It may not have |
| | 379 | // any contents, but there *will* be a |
| | 380 | // leaf. So, walk back down the tree |
| | 381 | // and find the first leaf |
| | 382 | while (!next.isLeaf()) { |
| | 383 | if (debug) |
| | 384 | out("["+next.level+"] next node is a branch, moving down..."); |
| | 385 | for (QBLevel child : next.children) { |
| | 386 | if (child == null) |
| | 387 | continue; |
| | 388 | next = child; |
| | 389 | break; |
| | 390 | } |
| | 391 | } |
| | 392 | return next; |
| | 393 | } |
| | 394 | int size() |
| | 395 | { |
| | 396 | if (isLeaf()) |
| | 397 | return size_leaf(); |
| | 398 | return size_branch(); |
| | 399 | } |
| | 400 | private int size_leaf() |
| | 401 | { |
| | 402 | if (content == null) { |
| | 403 | if (debug) |
| | 404 | out("["+level+"] leaf size: null content, children? " + children); |
| | 405 | return 0; |
| | 406 | } |
| | 407 | if (debug) |
| | 408 | out("["+level+"] leaf size: " + content.size()); |
| | 409 | return content.size(); |
| | 410 | } |
| | 411 | private int size_branch() |
| | 412 | { |
| | 413 | int ret = 0; |
| | 414 | for (QBLevel l : children) { |
| | 415 | if (l == null) |
| | 416 | continue; |
| | 417 | ret += l.size(); |
| | 418 | } |
| | 419 | if (debug) |
| | 420 | out("["+level+"] branch size: " + ret); |
| | 421 | return ret; |
| | 422 | } |
| | 423 | boolean contains(Node n) |
| | 424 | { |
| | 425 | QBLevel res = find_exact(n); |
| | 426 | if (res == null) |
| | 427 | return false; |
| | 428 | return true; |
| | 429 | } |
| | 430 | QBLevel find_exact(Node n) |
| | 431 | { |
| | 432 | if (isLeaf()) |
| | 433 | return find_exact_leaf(n); |
| | 434 | return find_exact_branch(n); |
| | 435 | } |
| | 436 | QBLevel find_exact_leaf(Node n) |
| | 437 | { |
| | 438 | QBLevel ret = null; |
| | 439 | if (content != null && content.contains(n)) |
| | 440 | ret = this; |
| | 441 | return ret; |
| | 442 | } |
| | 443 | QBLevel find_exact_branch(Node n) |
| | 444 | { |
| | 445 | QBLevel ret = null; |
| | 446 | for (QBLevel l : children) { |
| | 447 | if (l == null) |
| | 448 | continue; |
| | 449 | ret = l.find_exact(n); |
| | 450 | if (ret != null) |
| | 451 | break; |
| | 452 | } |
| | 453 | return ret; |
| | 454 | } |
| | 455 | boolean add(Node n) |
| | 456 | { |
| | 457 | if (isLeaf()) |
| | 458 | add_leaf(n); |
| | 459 | else |
| | 460 | add_branch(n); |
| | 461 | return true; |
| | 462 | } |
| | 463 | QBLevel add_branch(Node n) |
| | 464 | { |
| | 465 | LatLon c = n.getCoor(); |
| | 466 | int index = QuadTiling.index(n, level); |
| | 467 | if (debug) |
| | 468 | out("[" + level + "]: " + n + |
| | 469 | " index " + index + " levelquad:" + this.quads() + " level bbox:" + this.bbox()); |
| | 470 | if (debug) |
| | 471 | out(" put in child["+index+"]"); |
| | 472 | if (children[index] == null) |
| | 473 | children[index] = new QBLevel(this, index); |
| | 474 | children[index].add(n); |
| | 475 | /* this is broken at the moment because we need to handle the null n.getCoor() |
| | 476 | if (consistency_testing && !children[index].bbox().bounds(n.getCoor())) { |
| | 477 | out("target child["+index+"] does not bound " + children[index].bbox() + " " + c); |
| | 478 | for (int i = 0; i < children.length; i++) { |
| | 479 | QBLevel ch = children[i]; |
| | 480 | if (ch == null) |
| | 481 | continue; |
| | 482 | out("child["+i+"] quad: " + ch.quads() + " bounds: " + ch.bbox.bounds(n.getCoor())); |
| | 483 | } |
| | 484 | out(" coor quad: " + quads(n)); |
| | 485 | abort(""); |
| | 486 | }*/ |
| | 487 | |
| | 488 | if (consistency_testing) { |
| | 489 | for (int i = 0; i < children.length; i++) { |
| | 490 | QBLevel ch = children[i]; |
| | 491 | if (ch == null) |
| | 492 | continue; |
| | 493 | if (ch.bbox().bounds(c) && i != index) { |
| | 494 | out("multible bounding?? expected: " + index + " but saw at " + i + " " + ch.quads()); |
| | 495 | out("child["+i+"] bbox: " + ch.bbox()); |
| | 496 | } |
| | 497 | } |
| | 498 | } |
| | 499 | return this; |
| | 500 | } |
| | 501 | private List<Node> search_branch(BBox bbox, LatLon point, double radius) |
| | 502 | { |
| | 503 | List<Node> ret = null; |
| | 504 | if (debug) |
| | 505 | System.out.print("[" + level + "] qb bbox: " + this.bbox() + " "); |
| | 506 | if (!this.bbox().intersects(bbox)) { |
| | 507 | if (debug) { |
| | 508 | out("miss " + Long.toHexString(this.quad)); |
| | 509 | //QuadTiling.tile2xy(this.quad); |
| | 510 | } |
| | 511 | return ret; |
| | 512 | } |
| | 513 | if (debug) |
| | 514 | out("hit " + this.quads()); |
| | 515 | |
| | 516 | if (debug) |
| | 517 | out("[" + level + "] not leaf, moving down"); |
| | 518 | int child_nr = 0; |
| | 519 | for (QBLevel q : children) { |
| | 520 | if (q == null) |
| | 521 | continue; |
| | 522 | child_nr++; |
| | 523 | if (debug) |
| | 524 | System.out.print(child_nr+": "); |
| | 525 | List<Node> coors = q.search(bbox, point, radius); |
| | 526 | if (coors == null) |
| | 527 | continue; |
| | 528 | if (ret == null) |
| | 529 | ret = coors; |
| | 530 | else |
| | 531 | ret.addAll(coors); |
| | 532 | if (q.bbox().bounds(bbox)) { |
| | 533 | search_cache = q; |
| | 534 | // optimization: do not continue searching |
| | 535 | // other tiles if this one wholly contained |
| | 536 | // what we were looking for. |
| | 537 | if (coors.size() > 0 ) { |
| | 538 | if (debug) |
| | 539 | out("break early"); |
| | 540 | break; |
| | 541 | } |
| | 542 | } |
| | 543 | } |
| | 544 | return ret; |
| | 545 | } |
| | 546 | public String quads() |
| | 547 | { |
| | 548 | return Long.toHexString(quad); |
| | 549 | } |
| | 550 | public void init(QBLevel parent) |
| | 551 | { |
| | 552 | this.parent = parent; |
| | 553 | if (parent != null) |
| | 554 | this.level = parent.level + 1; |
| | 555 | this.quad = 0; |
| | 556 | } |
| | 557 | int index_of(QBLevel find_this) |
| | 558 | { |
| | 559 | if (this.isLeaf()) |
| | 560 | return -1; |
| | 561 | |
| | 562 | for (int i = 0; i < QuadTiling.TILES_PER_LEVEL; i++) { |
| | 563 | if (children[i] == find_this) |
| | 564 | return i; |
| | 565 | } |
| | 566 | return -1; |
| | 567 | } |
| | 568 | public QBLevel(QBLevel parent, int parent_index) |
| | 569 | { |
| | 570 | this.init(parent); |
| | 571 | this.level = parent.level+1; |
| | 572 | this.parent = parent; |
| | 573 | int shift = (QuadBuckets.NR_LEVELS - level) * 2; |
| | 574 | long mult = 1; |
| | 575 | // Java blows the big one. It seems to wrap when |
| | 576 | // you shift by > 31 |
| | 577 | if (shift >= 30) { |
| | 578 | shift -= 30; |
| | 579 | mult = 1<<30; |
| | 580 | } |
| | 581 | long this_quadpart = mult * (parent_index << shift); |
| | 582 | this.quad = parent.quad | this_quadpart; |
| | 583 | if (debug) |
| | 584 | out("new level["+this.level+"] bbox["+parent_index+"]: " + this.bbox() |
| | 585 | + " coor: " + this.coor() |
| | 586 | + " quadpart: " + Long.toHexString(this_quadpart) |
| | 587 | + " quad: " + Long.toHexString(this.quad)); |
| | 588 | } |
| | 589 | /* |
| | 590 | * Surely we can calculate these efficiently instead of storing |
| | 591 | */ |
| | 592 | double width = Double.NEGATIVE_INFINITY; |
| | 593 | double width() |
| | 594 | { |
| | 595 | if (width != Double.NEGATIVE_INFINITY) |
| | 596 | return this.width; |
| | 597 | if (level == 0) { |
| | 598 | width = this.bbox().width(); |
| | 599 | } else { |
| | 600 | width = parent.width()/2; |
| | 601 | } |
| | 602 | return width; |
| | 603 | } |
| | 604 | double height() |
| | 605 | { |
| | 606 | return width()/2; |
| | 607 | } |
| | 608 | BBox bbox = null; |
| | 609 | public BBox bbox() |
| | 610 | { |
| | 611 | if (bbox != null) { |
| | 612 | bbox.sanity(); |
| | 613 | return bbox; |
| | 614 | } |
| | 615 | if (level == 0) { |
| | 616 | bbox = new BBox(-180, 90, 180, -90); |
| | 617 | } else { |
| | 618 | LatLon bottom_left = this.coor(); |
| | 619 | double lat = bottom_left.lat() + this.height(); |
| | 620 | double lon = bottom_left.lon() + this.width(); |
| | 621 | LatLon top_right = new LatLon(lat, lon); |
| | 622 | bbox = new BBox(bottom_left, top_right); |
| | 623 | } |
| | 624 | bbox.sanity(); |
| | 625 | return bbox; |
| | 626 | } |
| | 627 | /* |
| | 628 | * This gives the coordinate of the bottom-left |
| | 629 | * corner of the box |
| | 630 | */ |
| | 631 | LatLon coor() |
| | 632 | { |
| | 633 | return QuadTiling.tile2LatLon(this.quad); |
| | 634 | } |
| | 635 | } |
| | 636 | |
| | 637 | private QBLevel root; |
| | 638 | private QBLevel search_cache; |
| | 639 | public QuadBuckets() |
| | 640 | { |
| | 641 | clear(); |
| | 642 | } |
| | 643 | public void clear() |
| | 644 | { |
| | 645 | root = new QBLevel(null); |
| | 646 | search_cache = null; |
| | 647 | if (debug) |
| | 648 | out("QuadBuckets() cleared: " + this); |
| | 649 | } |
| | 650 | public boolean add(Node n) |
| | 651 | { |
| | 652 | if (debug) |
| | 653 | out(this + " QuadBuckets() add: " + n + " size now: " + this.size()); |
| | 654 | int before_size = -1; |
| | 655 | if (consistency_testing) |
| | 656 | before_size = root.size(); |
| | 657 | boolean ret = root.add(n); |
| | 658 | if (consistency_testing) { |
| | 659 | int end_size = root.size(); |
| | 660 | if (ret) |
| | 661 | end_size--; |
| | 662 | if (before_size != end_size) |
| | 663 | abort("size inconsistency before add: " + before_size + " after: " + end_size); |
| | 664 | } |
| | 665 | return ret; |
| | 666 | } |
| | 667 | public void unsupported() |
| | 668 | { |
| | 669 | out("unsupported operation"); |
| | 670 | Object o = null; |
| | 671 | o.hashCode(); |
| | 672 | throw new UnsupportedOperationException(); |
| | 673 | } |
| | 674 | public boolean retainAll(Collection nodes) |
| | 675 | { |
| | 676 | unsupported(); |
| | 677 | return false; |
| | 678 | } |
| | 679 | public boolean removeAll(Collection nodes) |
| | 680 | { |
| | 681 | unsupported(); |
| | 682 | return false; |
| | 683 | } |
| | 684 | public boolean addAll(Collection nodes) |
| | 685 | { |
| | 686 | unsupported(); |
| | 687 | return false; |
| | 688 | } |
| | 689 | public boolean containsAll(Collection nodes) |
| | 690 | { |
| | 691 | unsupported(); |
| | 692 | return false; |
| | 693 | } |
| | 694 | private void check_type(Object o) |
| | 695 | { |
| | 696 | if (o instanceof Node) |
| | 697 | return; |
| | 698 | unsupported(); |
| | 699 | } |
| | 700 | public boolean remove(Object o) |
| | 701 | { |
| | 702 | check_type(o); |
| | 703 | return this.remove((Node)o); |
| | 704 | } |
| | 705 | public boolean remove(Node n) |
| | 706 | { |
| | 707 | QBLevel bucket = root.find_exact(n); |
| | 708 | if (!bucket.isLeaf()) |
| | 709 | abort("found branch where leaf expected"); |
| | 710 | return bucket.content.remove(n); |
| | 711 | } |
| | 712 | public boolean contains(Object o) |
| | 713 | { |
| | 714 | check_type(o); |
| | 715 | QBLevel bucket = root.find_exact((Node)o); |
| | 716 | if (bucket == null) |
| | 717 | return false; |
| | 718 | return true; |
| | 719 | } |
| | 720 | public ArrayList<Node> toArrayList() |
| | 721 | { |
| | 722 | ArrayList<Node> a = new ArrayList<Node>(); |
| | 723 | for (Node n : this) |
| | 724 | a.add(n); |
| | 725 | out("returning array list with size: " + a.size()); |
| | 726 | return a; |
| | 727 | } |
| | 728 | public Object[] toArray() |
| | 729 | { |
| | 730 | return this.toArrayList().toArray(); |
| | 731 | } |
| | 732 | public <T> T[] toArray(T[] template) |
| | 733 | { |
| | 734 | return this.toArrayList().toArray(template); |
| | 735 | } |
| | 736 | class QuadBucketIterator implements Iterator<Node> |
| | 737 | { |
| | 738 | QBLevel current_leaf; |
| | 739 | int index_in_leaf; |
| | 740 | int iterated_over; |
| | 741 | QBLevel next_leaf(QBLevel q) |
| | 742 | { |
| | 743 | if (q == null) |
| | 744 | return null; |
| | 745 | QBLevel orig = q; |
| | 746 | QBLevel next = q.nextLeaf(); |
| | 747 | if (consistency_testing && (orig == next)) |
| | 748 | abort("got same leaf back leaf: " + q.isLeaf()); |
| | 749 | return next; |
| | 750 | } |
| | 751 | public QuadBucketIterator(QuadBuckets qb) |
| | 752 | { |
| | 753 | if (debug) |
| | 754 | out(this + " is a new iterator qb.root: " + qb.root + " size: " + qb.size()); |
| | 755 | if (qb.root.isLeaf()) |
| | 756 | current_leaf = qb.root; |
| | 757 | else |
| | 758 | current_leaf = next_leaf(qb.root); |
| | 759 | if (debug) |
| | 760 | out("\titerator first leaf: " + current_leaf); |
| | 761 | iterated_over = 0; |
| | 762 | } |
| | 763 | public boolean hasNext() |
| | 764 | { |
| | 765 | if (this.peek() == null) { |
| | 766 | if (debug) |
| | 767 | out(this + " no hasNext(), but iterated over so far: " + iterated_over); |
| | 768 | return false; |
| | 769 | } |
| | 770 | return true; |
| | 771 | } |
| | 772 | Node peek() |
| | 773 | { |
| | 774 | if (current_leaf == null) { |
| | 775 | if (debug) |
| | 776 | out("null current leaf, nowhere to go"); |
| | 777 | return null; |
| | 778 | } |
| | 779 | while((current_leaf.content == null) || |
| | 780 | (index_in_leaf >= current_leaf.content.size())) { |
| | 781 | if (debug) |
| | 782 | out("moving to next leaf"); |
| | 783 | index_in_leaf = 0; |
| | 784 | current_leaf = next_leaf(current_leaf); |
| | 785 | if (current_leaf == null) |
| | 786 | break; |
| | 787 | } |
| | 788 | if (current_leaf == null || current_leaf.content == null) { |
| | 789 | if (debug) |
| | 790 | out("late nowhere to go " + current_leaf); |
| | 791 | return null; |
| | 792 | } |
| | 793 | return current_leaf.content.get(index_in_leaf); |
| | 794 | } |
| | 795 | public Node next() |
| | 796 | { |
| | 797 | Node ret = peek(); |
| | 798 | index_in_leaf++; |
| | 799 | if (debug) |
| | 800 | out("iteration["+iterated_over+"] " + index_in_leaf + " leaf: " + current_leaf); |
| | 801 | iterated_over++; |
| | 802 | if (ret == null) { |
| | 803 | if (debug) |
| | 804 | out(this + " no next node, but iterated over so far: " + iterated_over); |
| | 805 | } |
| | 806 | return ret; |
| | 807 | } |
| | 808 | public void remove() |
| | 809 | { |
| | 810 | Node object = peek(); |
| | 811 | current_leaf.content.remove(object); |
| | 812 | } |
| | 813 | } |
| | 814 | public Iterator<Node> iterator() |
| | 815 | { |
| | 816 | return new QuadBucketIterator(this); |
| | 817 | } |
| | 818 | public int size() |
| | 819 | { |
| | 820 | // This can certainly by optimized |
| | 821 | int ret = root.size(); |
| | 822 | if (debug) |
| | 823 | out(this + " QuadBuckets size: " + ret); |
| | 824 | return ret; |
| | 825 | } |
| | 826 | public boolean isEmpty() |
| | 827 | { |
| | 828 | if (this.size() == 0) |
| | 829 | return true; |
| | 830 | return false; |
| | 831 | } |
| | 832 | public BBox search_to_bbox(LatLon point, double radius) |
| | 833 | { |
| | 834 | BBox bbox = new BBox(point.lon() - radius, point.lat() - radius, |
| | 835 | point.lon() + radius, point.lat() + radius); |
| | 836 | if (debug) |
| | 837 | out("search bbox before sanity: " + bbox); |
| | 838 | bbox.sanity(); |
| | 839 | if (debug) |
| | 840 | out("search bbox after sanity: " + bbox); |
| | 841 | return bbox; |
| | 842 | } |
| | 843 | List<Node> search(LatLon point, double radius) |
| | 844 | { |
| | 845 | return this.search(search_to_bbox(point, radius), point, radius); |
| | 846 | } |
| | 847 | List<Node> search(BBox bbox) |
| | 848 | { |
| | 849 | return this.search(bbox, null, -1); |
| | 850 | } |
| | 851 | public List<Node> search(LatLon b1, LatLon b2) |
| | 852 | { |
| | 853 | BBox bbox = new BBox(b1.lon(), b1.lat(), b2.lon(), b2.lat()); |
| | 854 | bbox.sanity(); |
| | 855 | return this.search(bbox); |
| | 856 | } |
| | 857 | List<Node> search(BBox bbox, LatLon point, double radius) |
| | 858 | { |
| | 859 | if (debug) { |
| | 860 | out("qb root search at " + point + " around: " + radius); |
| | 861 | out("root bbox: " + root.bbox()); |
| | 862 | } |
| | 863 | List<Node> ret = null; |
| | 864 | // Doing this cuts down search cost on a real-life data |
| | 865 | // set by about 25% |
| | 866 | boolean cache_searches = true; |
| | 867 | if (cache_searches) { |
| | 868 | if (search_cache == null) |
| | 869 | search_cache = root; |
| | 870 | // Walk back up the tree when the last |
| | 871 | // search spot can not cover the current |
| | 872 | // search |
| | 873 | while (!search_cache.bbox().bounds(bbox)) { |
| | 874 | search_cache = search_cache.parent; |
| | 875 | } |
| | 876 | } else { |
| | 877 | search_cache = root; |
| | 878 | } |
| | 879 | return search_cache.search(bbox, point, radius); |
| | 880 | } |
| | 881 | } |