001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wpvs/utils/QuadTreeSplitter.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 037 package org.deegree.ogcwebservices.wpvs.utils; 038 039 import java.awt.Color; 040 import java.awt.Composite; 041 import java.awt.Graphics2D; 042 import java.util.ArrayList; 043 import java.util.Collections; 044 import java.util.Iterator; 045 import java.util.LinkedHashMap; 046 import java.util.List; 047 import java.util.Set; 048 import java.util.TreeSet; 049 050 import javax.vecmath.Vector3d; 051 052 import org.deegree.framework.log.ILogger; 053 import org.deegree.framework.log.LoggerFactory; 054 import org.deegree.model.crs.CoordinateSystem; 055 import org.deegree.model.spatialschema.Envelope; 056 import org.deegree.model.spatialschema.GeometryException; 057 import org.deegree.model.spatialschema.GeometryFactory; 058 import org.deegree.model.spatialschema.Position; 059 import org.deegree.model.spatialschema.Surface; 060 import org.deegree.model.spatialschema.WKTAdapter; 061 import org.deegree.ogcwebservices.wpvs.configuration.WPVSConfiguration; 062 import org.deegree.ogcwebservices.wpvs.configuration.WPVSDeegreeParams; 063 064 /** 065 * <p> 066 * The <code>QuadTreeSplitter</code> class can be used to create x-y axis alligned request quads from a qiven List of 067 * {@link ResolutionStripe} s. These Stripes depend on the ViewFrustrum and it's projection on the x-y plane (the so 068 * called footprint). To create an approximation of this footprint a Quadtree (a geometric spatial structure, which 069 * recursively divides a boundingbox into four containing boundingboxes) is built. The leafs of this tree are merged 070 * according to their resolution and size to create the requeststripes. 071 * </p> 072 * 073 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a> 074 * 075 * @author last edited by: $Author: aschmitz $ 076 * 077 * @version $Revision: 23585 $, $Date: 2010-04-13 14:07:18 +0200 (Di, 13 Apr 2010) $ 078 * 079 */ 080 081 public class QuadTreeSplitter { 082 083 private static final ILogger LOG = LoggerFactory.getLogger( QuadTreeSplitter.class ); 084 085 private QuadNode rootNode; 086 087 private TreeSet<ResolutionStripe> resolutionStripes; 088 089 private double minimalTerrainHeight; 090 091 private double minimalResolution; 092 093 private CoordinateSystem crs; 094 095 private boolean highQuality; 096 097 private double scale; 098 099 // private double imageWidth; 100 101 /** 102 * Creates a new Quadtree, from the given resolutionstripes. The resulting tree can be used to generate 103 * requeststripes which are (x-y) axis aligned. 104 * <p> 105 * The Quality argument is used for the recursive termination criteria, if it is set to true the requeststripes will 106 * accurately approximate the footprint (=projection of the viewfrustrum onto the ground) and the resolutions given 107 * in the resolutionstripes this results in a lot of requests which can slow down the wpvs. If set to false the 108 * footprint and the given resolutions will be approximated poorly but only a few requeststripes are created, 109 * resulting in a faster wpvs. 110 * </p> 111 * 112 * @param resolutionStripes 113 * the different resolutionstripes. 114 * @param imageWidth 115 * the width of the target image, necessary for calculating the width resolution of the requeststripe. 116 * @param highQuality 117 * true if accurate (but many) requeststripes should be generated, false if none accurate (but only a 118 * few) requests should be generated. 119 */ 120 public QuadTreeSplitter( ArrayList<ResolutionStripe> resolutionStripes, double imageWidth, boolean highQuality ) { 121 if ( resolutionStripes == null || resolutionStripes.size() <= 0 ) { 122 return; 123 } 124 this.resolutionStripes = new TreeSet<ResolutionStripe>( resolutionStripes ); 125 // this.imageWidth = imageWidth; 126 this.crs = resolutionStripes.get( 0 ).getCRSName(); 127 this.scale = resolutionStripes.get( 0 ).getScale(); 128 this.minimalTerrainHeight = resolutionStripes.get( 0 ).getMinimalTerrainHeight(); 129 this.highQuality = highQuality; 130 // For the merge, the check is newmin < existing_min therefore -> min large and max small 131 // Position minPos = GeometryFactory.createPosition( 0, 0, 0 ); 132 // Position maxPos = GeometryFactory.createPosition( 0, 0, 0 ); 133 Envelope bbox = resolutionStripes.get( 0 ).getSurface().getEnvelope(); 134 minimalResolution = Double.MAX_VALUE; 135 double maxResolution = Double.MIN_VALUE; 136 137 // find the highest and loweset maxResolution (which are needed for termination decissions and 138 // find create the axis-alligned bbox of the resolutionstripes which will be the rootnode. 139 for ( int i = 0; i < resolutionStripes.size(); ++i ) { 140 try { 141 bbox = bbox.merge( resolutionStripes.get( i ).getSurface().getEnvelope() ); 142 // minimalResolution is the smallest number 143 minimalResolution = Math.min( minimalResolution, resolutionStripes.get( i ).getMinResolution() ); 144 maxResolution = Math.max( maxResolution, resolutionStripes.get( i ).getMaxResolution() ); 145 } catch ( GeometryException e ) { 146 e.printStackTrace(); 147 System.out.println( e.getLocalizedMessage() ); 148 } 149 } 150 LOG.logDebug( "Found minimalResolution: " + minimalResolution ); 151 LOG.logDebug( "Found maximumResolution: " + maxResolution ); 152 try { 153 if ( Math.abs( minimalResolution ) < 0.00001 ) { // almost null 154 minimalResolution = Math.pow( maxResolution, 1.0 / resolutionStripes.size() ); 155 LOG.logDebug( "Recalculated minimalResolution (< 0.00001) ) to: " + minimalResolution ); 156 } 157 Position min = bbox.getMin(); 158 159 double zValue = min.getZ(); 160 if ( Double.isNaN( min.getZ() ) ) { 161 zValue = minimalTerrainHeight; 162 } 163 164 Vector3d leftPoint = new Vector3d( min.getX(), min.getY(), zValue ); 165 Vector3d rightPoint = new Vector3d( min.getX() + ( bbox.getWidth() ), min.getY() + ( bbox.getHeight() ), 166 zValue ); 167 Vector3d ul = new Vector3d( leftPoint.x, rightPoint.y, zValue ); 168 double quadResolution = StripeFactory.calcScaleOfVector( leftPoint, rightPoint, imageWidth ); 169 double quadResolution2 = StripeFactory.calcScaleOfVector( leftPoint, ul, imageWidth ); 170 if ( quadResolution < quadResolution2 ) { 171 quadResolution = quadResolution2; 172 } 173 174 LOG.logDebug( "The root node of the quadtree has a max resolution of: " + maxResolution 175 + " and a min Resolution of: " + minimalResolution ); 176 177 rootNode = new QuadNode( GeometryFactory.createSurface( bbox, crs ), maxResolution, minimalResolution ); 178 179 createTree( rootNode, quadResolution ); 180 181 } catch ( GeometryException e ) { 182 LOG.logError( e.getLocalizedMessage(), e ); 183 } 184 } 185 186 /** 187 * After instantiating a Quadtree, this method can be called to build the (x-y) axis-alligned request stripes. 188 * 189 * @param extraRequestPercentage 190 * a percentage to be added to the resulting stripes ( a value between [0,1] ), which might correct gapes 191 * between stripes.{@link WPVSDeegreeParams#getExtendRequestPercentage()} 192 * @param quadMergeCount 193 * the number of leaves this splitter can have, before it starts merging leaves together 194 * {@link WPVSDeegreeParams#getQuadMergeCount() } 195 * @return the (x-y) axis-alligned request squares best fitted the given resolutionstripes. 196 */ 197 public ArrayList<ResolutionStripe> getRequestQuads( double extraRequestPercentage, int quadMergeCount ) { 198 199 ArrayList<ResolutionStripe> resultList = new ArrayList<ResolutionStripe>(); 200 if ( rootNode != null ) { 201 LinkedHashMap<Double, ArrayList<QuadNode>> lhm = new LinkedHashMap<Double, ArrayList<QuadNode>>( 100 ); 202 outputNodes( rootNode, lhm ); 203 Set<Double> keys = lhm.keySet(); 204 int nrOfStripes = 0; 205 for ( Double resolution : keys ) { 206 if ( lhm.containsKey( resolution ) ) { 207 List<QuadNode> originalNodes = lhm.get( resolution ); 208 nrOfStripes += originalNodes.size(); 209 } 210 } 211 212 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 213 if ( nrOfStripes < quadMergeCount ) { 214 LOG.logDebug( "Not merging leaves because there are less leaves: " + nrOfStripes 215 + " then configured quadMergeCount:" + quadMergeCount ); 216 } 217 try { 218 StringBuilder sb = new StringBuilder(); 219 outputNodes( rootNode, sb ); 220 LOG.logDebug( "tree-leaves:\n" + sb.toString() ); 221 } catch ( GeometryException e ) { 222 // TODO Auto-generated catch block 223 e.printStackTrace(); 224 } 225 } 226 retrieveStripes( resultList, lhm, extraRequestPercentage, ( nrOfStripes > quadMergeCount ) ); 227 } 228 return resultList; 229 } 230 231 private void retrieveStripes( List<ResolutionStripe> resultList, LinkedHashMap<Double, ArrayList<QuadNode>> lhm, 232 double extraRequestPercentage, boolean mergeAndSort ) { 233 Set<Double> keys = lhm.keySet(); 234 for ( Double resolution : keys ) { 235 if ( lhm.containsKey( resolution ) ) { 236 List<QuadNode> originalNodes = lhm.get( resolution ); 237 List<QuadNode> result = new ArrayList<QuadNode>( originalNodes.size() ); 238 if ( mergeAndSort ) { 239 // sorted to x values first. 240 LOG.logDebug( "Sorting x order" ); 241 List<QuadNode> resultX = mergeAndSort( originalNodes ); 242 243 // Check if sorting to y results in better values; 244 for ( QuadNode node : originalNodes ) { 245 node.compareY(); 246 } 247 LOG.logDebug( "Sorting y order" ); 248 List<QuadNode> resultY = mergeAndSort( originalNodes ); 249 250 // Find the optimal sorting order (lesser quads) and check if the perpendicular 251 // order results in lesser requeststripes (it usually does) 252 if ( resultX.size() < resultY.size() ) { 253 for ( QuadNode node : resultX ) { 254 node.compareY(); 255 } 256 LOG.logDebug( "res-Sorting x order" ); 257 result = mergeAndSort( resultX ); 258 } else { 259 for ( QuadNode node : resultY ) { 260 node.compareX(); 261 } 262 LOG.logDebug( "res-Sorting y order" ); 263 result = mergeAndSort( resultY ); 264 } 265 } else { 266 result.addAll( originalNodes ); 267 } 268 for ( QuadNode quad : result ) { 269 Envelope env = quad.getBBox().getEnvelope(); 270 Position envMin = env.getMin(); 271 Position envMax = env.getMax(); 272 273 double width = env.getWidth(); 274 double height = env.getHeight(); 275 276 // enlarge the envelope to extraRequestPercentage to correct gapes between stripes. 277 double extraWidth = width * extraRequestPercentage; 278 double extraHeight = height * extraRequestPercentage; 279 280 Position newMin = GeometryFactory.createPosition( envMin.getX() - extraWidth, envMin.getY() 281 - extraHeight, 282 envMin.getZ() ); 283 284 Position newMax = GeometryFactory.createPosition( envMax.getX() + extraWidth, envMax.getY() 285 + extraHeight, 286 envMax.getZ() ); 287 288 double minResolution = quad.getMinResolution(); 289 double maxResolution = quad.getMaxResolution(); 290 291 if ( minResolution > maxResolution ) { 292 double tmp = minResolution; 293 minResolution = maxResolution; 294 maxResolution = tmp; 295 } 296 297 try { 298 Surface resultSurface = GeometryFactory.createSurface( 299 GeometryFactory.createEnvelope( newMin, 300 newMax, 301 this.crs ), 302 this.crs ); 303 ResolutionStripe rs = new ResolutionStripe( resultSurface, maxResolution, minResolution, 304 minimalTerrainHeight, scale ); 305 resultList.add( rs ); 306 307 } catch ( GeometryException e ) { 308 LOG.logError( e.getLocalizedMessage(), e ); 309 } 310 } 311 } 312 } 313 } 314 315 /** 316 * A little helper function which first sorts the given list of quadnodes according to their sorting order and 317 * afterwards merges all stripes which are adjacent and have the same resolution. 318 * 319 * @param toBeMerged 320 * the list of Quadnodes which must be sorted and merged. 321 * @return a list containing the merged quadnodes of the given list. 322 */ 323 private List<QuadNode> mergeAndSort( List<QuadNode> toBeMerged ) { 324 Collections.sort( toBeMerged ); 325 List<QuadNode> resultList = new ArrayList<QuadNode>( toBeMerged ); 326 boolean needsResort = true; 327 while ( needsResort ) { 328 needsResort = false; 329 List<QuadNode> tmpResults = new ArrayList<QuadNode>( resultList ); 330 Iterator<QuadNode> it = tmpResults.iterator(); 331 QuadNode first = null; 332 QuadNode second = null; 333 resultList.clear(); 334 // Iterate over the quadnodes. 335 while ( second != null || it.hasNext() ) { 336 // this is neccessary if the first could not be merged with the next, we don't want to lose the 337 // iterators reference, therefore the first quad references the second which was actually the it.next() 338 if ( second == null ) { 339 first = it.next(); 340 } else { 341 first = second; 342 second = null; 343 } 344 Envelope requestEnvelope = first.getBBox().getEnvelope(); 345 int nrOfMerges = 0; 346 double curMinResolution = first.getMinResolution(); 347 double curMaxResolution = first.getMaxResolution(); 348 while ( it.hasNext() && second == null ) { 349 second = it.next(); 350 // if the first and the second quad cannot be merged, the second will become the first, if they can 351 // merge, they will be merged together and the iterator moves on, this means, we have to go over all 352 // quads again after this round, for else we might not find all possible merges. 353 if ( first.canMerge( second ) ) { 354 try { 355 Envelope tmpEnvelope = requestEnvelope; 356 requestEnvelope = requestEnvelope.merge( second.getBBox().getEnvelope() ); 357 // curMinResolution = first.getMinResolution() / ( nrOfMerges + 2 ); 358 // curMaxResolution = first.getMaxResolution() / ( nrOfMerges + 2 ); 359 int rH = (int) Math.round( requestEnvelope.getHeight() / Math.abs( curMinResolution ) ); 360 int rW = (int) Math.round( requestEnvelope.getWidth() / Math.abs( curMinResolution ) ); 361 if ( rH < WPVSConfiguration.MAX_REQUEST_SIZE && rW < WPVSConfiguration.MAX_REQUEST_SIZE ) { 362 LOG.logDebug( "Merging quads:\n" 363 + WKTAdapter.export( GeometryFactory.createSurface( tmpEnvelope, crs ) ).toString() 364 + "\n" + WKTAdapter.export( second.getBBox() ).toString() ); 365 // double tmpScale = WPVSConfiguration.MAX_REQUEST_SIZE / (double)((rH > rW )?rW:rH); 366 // curMaxResolution *=tmpScale; 367 // curMinResolution *=tmpScale; 368 369 second = null; 370 needsResort = true; 371 nrOfMerges++; 372 } else { 373 requestEnvelope = tmpEnvelope; 374 LOG.logDebug( "Cannot merge two quads, because their merged sizes( rw: " 375 + rW 376 + ", rH: " 377 + rH 378 + " would be bigger as the maximum retrievable size:\n" 379 + WKTAdapter.export( GeometryFactory.createSurface( requestEnvelope, crs ) ).toString() 380 + "\n" + WKTAdapter.export( second.getBBox() ).toString() ); 381 382 } 383 } catch ( GeometryException ge ) { 384 // An error occured, it might be best to not merge these envelopes. 385 LOG.logError( ge.getLocalizedMessage(), ge ); 386 } 387 } 388 389 } 390 if ( nrOfMerges > 0 ) { 391 try { 392 Surface resultSurface = GeometryFactory.createSurface( requestEnvelope, crs ); 393 resultList.add( new QuadNode( resultSurface, 394 // first.getMaxResolution(), 395 // first.getMinResolution(), 396 curMaxResolution, curMinResolution, first.isComparingX() ) ); 397 } catch ( GeometryException ge ) { 398 // An error occured, it might be best not to merge these envelopes. 399 LOG.logError( ge.getLocalizedMessage(), ge ); 400 } 401 } else { 402 resultList.add( first ); 403 } 404 } 405 } 406 return resultList; 407 } 408 409 /** 410 * This Method actually builds the tree. The decission of a split is made by evaluating the minimal maxResolution of 411 * the intersecting ResultionStripe. 412 * 413 * @param father 414 * the Father node which will be splittet into four axis aligned sons 415 * @param quadResolution 416 * the maxResolution of a width axis of the axis-aligned bbox 417 * @throws GeometryException 418 * if the Envelope cannot be created 419 */ 420 private void createTree( QuadNode father, double quadResolution ) 421 throws GeometryException { 422 LOG.logDebug( WKTAdapter.export( father.getBBox() ).toString() ); 423 LOG.logDebug( father.toString() ); 424 LOG.logDebug( "Fatherresolution: " + quadResolution ); 425 Position min = father.getBBox().getEnvelope().getMin(); 426 double widthHalf = 0.5 * father.getBBox().getEnvelope().getWidth(); 427 double heightHalf = 0.5 * father.getBBox().getEnvelope().getHeight(); 428 double lowerLeftX = min.getX(); 429 double lowerLeftY = min.getY(); 430 double sonsResolution = 0.5 * quadResolution; 431 // recursion is not necessary if the resolution is smaller then the minimalResolution. 432 if ( sonsResolution < minimalResolution ) { 433 LOG.logDebug( "The currentresolution (" + sonsResolution + ") is smaller then the minimalResolution(" 434 + minimalResolution + ")" ); 435 return; 436 } 437 438 checkSon( father, sonsResolution, QuadNode.LOWER_LEFT_SON, lowerLeftX, lowerLeftY, lowerLeftX + widthHalf, 439 lowerLeftY + heightHalf ); 440 441 // lowerright 442 checkSon( father, sonsResolution, QuadNode.LOWER_RIGHT_SON, lowerLeftX + widthHalf, lowerLeftY, 443 lowerLeftX + ( 2 * widthHalf ), lowerLeftY + heightHalf ); 444 445 // upperleft 446 checkSon( father, sonsResolution, QuadNode.UPPER_LEFT_SON, lowerLeftX, lowerLeftY + heightHalf, lowerLeftX 447 + widthHalf, 448 lowerLeftY + ( 2 * heightHalf ) ); 449 450 // upperright 451 checkSon( father, sonsResolution, QuadNode.UPPER_RIGHT_SON, lowerLeftX + widthHalf, lowerLeftY + heightHalf, 452 lowerLeftX + 2 * widthHalf, lowerLeftY + 2 * heightHalf ); 453 } 454 455 /** 456 * Decides if the father quad has to be subdivided into it's sons. 457 * 458 * @param father 459 * the Father quad to divide 460 * @param quadResolution 461 * the maxResolution of the fathers son (half the maxResolution of the father) 462 * @param SON_ID 463 * the son to check 464 * @param lowerLeftX 465 * minx of the bbox of the fathers son 466 * @param lowerLeftY 467 * miny of the bbox of the fathers son 468 * @param upperRightX 469 * maxx of the bbox of the fathers son 470 * @param upperRightY 471 * maxY of the bbox of the fathers son 472 * @throws GeometryException 473 * if no surface can be created 474 */ 475 private void checkSon( QuadNode father, double quadResolution, final int SON_ID, double lowerLeftX, 476 double lowerLeftY, double upperRightX, double upperRightY ) 477 throws GeometryException { 478 Position min = GeometryFactory.createPosition( lowerLeftX, lowerLeftY, minimalTerrainHeight ); 479 /* 480 * father.getBBox() .getEnvelope() .getMin() .getZ() ); 481 */ 482 Position max = GeometryFactory.createPosition( upperRightX, upperRightY, minimalTerrainHeight ); 483 /* 484 * father.getBBox() .getEnvelope() .getMax() .getZ()); 485 */ 486 Surface bbox = GeometryFactory.createSurface( GeometryFactory.createEnvelope( min, max, crs ), crs ); 487 488 ResolutionStripe intersectedStripe = ( highQuality ) ? getIntersectionForQualityConfiguration( bbox ) 489 : getIntersectionForFastConfiguration( bbox ); 490 491 if ( intersectedStripe != null ) { // found an intersecting resolutionStripe 492 LOG.logDebug( "Following quad( 1 ) Found intersecting stripe(2):\n" + WKTAdapter.export( bbox ) + "\n" 493 + WKTAdapter.export( intersectedStripe.getSurface() ) ); 494 QuadNode son = new QuadNode( bbox, intersectedStripe.getMaxResolution(), 495 intersectedStripe.getMinResolution() ); 496 double intersectResolution = ( highQuality ) ? intersectedStripe.getMinResolution() 497 : intersectedStripe.getMaxResolution(); 498 LOG.logDebug( "sonsResolution: " + intersectResolution + " currentResolution of quad: " + quadResolution ); 499 father.addSon( SON_ID, son ); 500 int rH = (int) Math.round( bbox.getEnvelope().getHeight() / Math.abs( intersectedStripe.getMinResolution() ) ); 501 int rW = (int) Math.round( bbox.getEnvelope().getWidth() / Math.abs( intersectedStripe.getMinResolution() ) ); 502 /* ( rH < WPVSConfiguration.MAX_REQUEST_SIZE && rW < WPVSConfiguration.MAX_REQUEST_SIZE ) || */ 503 if ( ( rH > WPVSConfiguration.MAX_REQUEST_SIZE || rW > WPVSConfiguration.MAX_REQUEST_SIZE ) 504 || quadResolution >= ( intersectResolution * 0.95 ) ) { 505 createTree( son, quadResolution ); 506 } 507 } else { 508 LOG.logDebug( "Following quad found no intersections :\n" + WKTAdapter.export( bbox ) ); 509 } 510 } 511 512 /** 513 * Finds the resolutionstripe with the lowest minResolution which intersects with the given bbox. Resulting in a lot 514 * of different requests. 515 * 516 * @param bbox 517 * the BoundingBox of the Envelope to check. 518 * @return the resolutionStripe which intersects the bbox. 519 */ 520 private ResolutionStripe getIntersectionForQualityConfiguration( Surface bbox ) { 521 LOG.logDebug( "Trying to find intersection with Quality, e.g. min( of all intersecting minResolutions )" ); 522 ResolutionStripe resultStripe = null; 523 for ( ResolutionStripe stripe : resolutionStripes ) { 524 if ( bbox.intersects( stripe.getSurface() ) ) { 525 if ( resultStripe != null ) { 526 if ( ( stripe.getMinResolution() < resultStripe.getMinResolution() ) ) { 527 resultStripe = stripe; 528 } 529 } else { 530 resultStripe = stripe; 531 } 532 } 533 } 534 return resultStripe; 535 } 536 537 /** 538 * Finds the resolutionstripe with the highest maxResolution which intersects with the given bbox. Resulting in only 539 * a few different requests. 540 * 541 * @param bbox 542 * the BoundingBox of the Envelope to check. 543 * @return the resolutionStripe which intersects the bbox. 544 */ 545 private ResolutionStripe getIntersectionForFastConfiguration( Surface bbox ) { 546 LOG.logDebug( "Trying to find intersection with Speed, e.g. max( of all intersecting maxResolutions )" ); 547 ResolutionStripe resultStripe = null; 548 for ( ResolutionStripe stripe : resolutionStripes ) { 549 if ( bbox.intersects( stripe.getSurface() ) ) { 550 if ( resultStripe != null ) { 551 if ( ( stripe.getMaxResolution() < resultStripe.getMaxResolution() ) ) { 552 resultStripe = stripe; 553 } 554 } else { 555 resultStripe = stripe; 556 } 557 } 558 } 559 return resultStripe; 560 } 561 562 /** 563 * Outputs the tree 564 * 565 * @param g2d 566 * if the quadtree should be drawn. 567 */ 568 public void outputTree( Graphics2D g2d ) { 569 if ( rootNode != null ) { 570 if ( g2d != null ) { 571 System.out.println( "number Of leaves-> " + outputNodes( rootNode, g2d ) ); 572 } else { 573 outputNodes( rootNode, "" ); 574 } 575 } 576 } 577 578 private int outputNodes( QuadNode father, Graphics2D g2d ) { 579 if ( father.isLeaf() ) { 580 drawSquare( father, g2d, Color.BLACK ); 581 return 1; 582 } 583 QuadNode[] nodes = father.getSons(); 584 int result = 0; 585 for ( QuadNode node : nodes ) { 586 if ( node != null ) { 587 result += outputNodes( node, g2d ); 588 } 589 590 } 591 return result; 592 } 593 594 private void outputNodes( QuadNode father, String indent ) { 595 if ( father.isLeaf() ) { 596 System.out.println( indent + "(father)" + father.getBBox() ); 597 } else { 598 QuadNode[] nodes = father.getSons(); 599 for ( QuadNode node : nodes ) { 600 if ( node != null ) { 601 indent += "-"; 602 outputNodes( node, indent ); 603 } 604 } 605 } 606 } 607 608 private void outputNodes( QuadNode father, StringBuilder sb ) 609 throws GeometryException { 610 if ( father.isLeaf() ) { 611 sb.append( WKTAdapter.export( father.getBBox() ).toString() ).append( "\n" ); 612 } else { 613 QuadNode[] nodes = father.getSons(); 614 for ( QuadNode node : nodes ) { 615 if ( node != null ) { 616 outputNodes( node, sb ); 617 } 618 } 619 } 620 } 621 622 /** 623 * Find the leaf nodes and add them according to their maxResolution in a LinkedHashMap. 624 * 625 * @param father 626 * the node to check 627 * @param outputMap 628 * the map to output to. 629 */ 630 private void outputNodes( QuadNode father, LinkedHashMap<Double, ArrayList<QuadNode>> outputMap ) { 631 if ( father.isLeaf() ) { 632 Double key = new Double( father.getMinResolution() ); 633 ArrayList<QuadNode> ts = outputMap.get( key ); 634 if ( ts == null ) { // I know, but I don't put null values so it's ok 635 ts = new ArrayList<QuadNode>(); 636 outputMap.put( key, ts ); 637 } 638 if ( ts.add( father ) == false ) { 639 System.out.println( "quadnode allready in set" ); 640 } 641 } else { 642 QuadNode[] nodes = father.getSons(); 643 for ( QuadNode node : nodes ) { 644 if ( node != null ) { 645 outputNodes( node, outputMap ); 646 } 647 } 648 } 649 } 650 651 private void drawSquare( QuadNode node, Graphics2D g2d, Color c ) { 652 if ( g2d != null ) { 653 g2d.setColor( c ); 654 Envelope env = node.getBBox().getEnvelope(); 655 Position min = env.getMin(); 656 int height = (int) env.getHeight(); 657 int width = (int) env.getWidth(); 658 g2d.drawRect( (int) min.getX(), (int) min.getY(), width, height ); 659 Composite co = g2d.getComposite(); 660 g2d.setColor( new Color( c.getRed(), c.getGreen(), c.getBlue(), 64 ) ); 661 g2d.fillRect( (int) min.getX(), (int) min.getY(), width, height ); 662 g2d.setComposite( co ); 663 } 664 } 665 666 /** 667 * 668 * The <code>QuadNode</code> class is the bean for every node of the quadtree. It contains a axis-aligned BBox and 669 * the maxResolution of its associated resolutionStripe. It can have upto four sons. 670 * 671 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a> 672 * 673 * @author last edited by: $Author: aschmitz $ 674 * 675 * @version $Revision: 23585 $, $Date: 2010-04-13 14:07:18 +0200 (Di, 13 Apr 2010) $ 676 * 677 */ 678 private class QuadNode implements Comparable<QuadNode> { 679 680 private QuadNode[] sons; 681 682 private Surface bbox; 683 684 private double maxResolution; 685 686 private double minResolution; 687 688 private double comparePosition; 689 690 private double compareLength; 691 692 private boolean comparingX; 693 694 /** 695 * The Denoting lower left son 696 */ 697 static final int LOWER_LEFT_SON = 0; 698 699 /** 700 * The Denoting lower right son 701 */ 702 static final int LOWER_RIGHT_SON = 1; 703 704 /** 705 * The Denoting upper left son 706 */ 707 static final int UPPER_LEFT_SON = 2; 708 709 /** 710 * The Denoting upper right son 711 */ 712 static final int UPPER_RIGHT_SON = 3; 713 714 /** 715 * 716 * @param bbox 717 * @param maxResolution 718 * @param minResolution 719 */ 720 QuadNode( Surface bbox, double maxResolution, double minResolution ) { 721 this.bbox = bbox; 722 sons = new QuadNode[4]; 723 this.maxResolution = maxResolution; 724 this.minResolution = minResolution; 725 comparePosition = bbox.getEnvelope().getMin().getX(); 726 compareLength = bbox.getEnvelope().getWidth(); 727 comparingX = true; 728 } 729 730 /** 731 * 732 * @param bbox 733 * @param maxResolution 734 * @param minResolution 735 * @param compareDirection 736 */ 737 QuadNode( Surface bbox, double maxResolution, double minResolution, boolean compareDirection ) { 738 this( bbox, maxResolution, minResolution ); 739 if ( compareDirection ) 740 compareX(); 741 else 742 compareY(); 743 } 744 745 /** 746 * Add a son to this node. 747 * 748 * @param sonID 749 * which son 750 * @param son 751 * the reference 752 */ 753 void addSon( final int sonID, QuadNode son ) { 754 if ( sonID == LOWER_LEFT_SON || sonID == LOWER_RIGHT_SON || sonID == UPPER_LEFT_SON 755 || sonID == UPPER_RIGHT_SON ) { 756 this.sons[sonID] = son; 757 } 758 } 759 760 /** 761 * @return bbox of node 762 */ 763 Surface getBBox() { 764 return bbox; 765 } 766 767 /** 768 * set the node to compare along the x axis 769 */ 770 void compareX() { 771 comparePosition = bbox.getEnvelope().getMin().getX(); 772 compareLength = bbox.getEnvelope().getWidth(); 773 comparingX = true; 774 } 775 776 /** 777 * set the node to compare along the y axis 778 */ 779 void compareY() { 780 comparePosition = bbox.getEnvelope().getMin().getY(); 781 compareLength = bbox.getEnvelope().getHeight(); 782 comparingX = false; 783 } 784 785 /** 786 * If this Quadnode has no sons it is called a leaf. 787 * 788 * @return true if no sons, false otherwhise. 789 */ 790 boolean isLeaf() { 791 return ( sons[0] == null && sons[1] == null && sons[2] == null && sons[3] == null ); 792 } 793 794 /** 795 * @return the sons or null if no sons are set. 796 */ 797 QuadNode[] getSons() { 798 return sons; 799 } 800 801 /** 802 * @param sonID 803 * @return the son or null if no son could be mapped to the sonid. 804 */ 805 QuadNode getSon( final int sonID ) { 806 if ( sonID != LOWER_LEFT_SON || sonID != LOWER_RIGHT_SON || sonID != UPPER_LEFT_SON 807 || sonID != UPPER_RIGHT_SON ) 808 return null; 809 return sons[sonID]; 810 } 811 812 /** 813 * @return The max maxResolution of the Stripe. 814 */ 815 double getMaxResolution() { 816 return maxResolution; 817 } 818 819 /** 820 * @return the minResolution value. 821 */ 822 double getMinResolution() { 823 return minResolution; 824 } 825 826 /** 827 * @return true if the comparing will be along the x axis. 828 */ 829 boolean isComparingX() { 830 return comparingX; 831 } 832 833 /** 834 * @return true if the comparing will be along the y axis. 835 */ 836 double getComparePosition() { 837 return comparePosition; 838 } 839 840 /** 841 * @return true if the length will be compared 842 */ 843 double getCompareLength() { 844 return compareLength; 845 } 846 847 /* 848 * Attention, the equal result "0" is not really a check for the equality of two Quadnodes, it just reflex, that 849 * two QuadNodes have the same sorting properties -> the position - (y or x) and the length in this direction 850 * are equal. It is very plausible that they have totally different positions and length in the other (not 851 * checked) direction. 852 * 853 * @see java.lang.Comparable#compareTo(T) 854 */ 855 public int compareTo( QuadNode other ) { 856 double otherPosition = other.getComparePosition(); 857 if ( Math.abs( comparePosition - otherPosition ) < 0.00001 ) { 858 double otherLength = other.getCompareLength(); 859 if ( Math.abs( compareLength - otherLength ) < 0.00001 ) { 860 if ( comparingX ) { 861 double thisMinY = this.bbox.getEnvelope().getMin().getY(); 862 double otherMinY = other.getBBox().getEnvelope().getMin().getY(); 863 if ( ( Math.abs( thisMinY - otherMinY ) < 0.00001 ) ) 864 return 0; 865 if ( thisMinY < otherMinY ) 866 return 1; 867 return -1; 868 } 869 double thisMinX = this.bbox.getEnvelope().getMin().getX(); 870 double otherMinX = other.getBBox().getEnvelope().getMin().getX(); 871 if ( ( Math.abs( thisMinX - otherMinX ) < 0.00001 ) ) 872 return 0; 873 if ( thisMinX < otherMinX ) 874 return 1; 875 return -1; 876 } 877 if ( compareLength < otherLength ) { 878 return -1; 879 } 880 return 1; 881 } 882 if ( comparePosition < otherPosition ) 883 return -1; 884 return 1; 885 } 886 887 /** 888 * simple check if two quadnodes can be merged, according to their positions, length and if they are adjacent. 889 * 890 * @param other 891 * @return true if this QuadNode can be merged with the Other. 892 */ 893 boolean canMerge( QuadNode other ) { 894 double otherPosition = other.getComparePosition(); 895 if ( Math.abs( comparePosition - otherPosition ) < 0.01 ) { 896 double otherLength = other.compareLength; 897 if ( Math.abs( compareLength - otherLength ) < 0.01 ) { 898 // the origins and the length are mergable, now check if the Quadnodes are 899 // adjacent 900 if ( comparingX ) { 901 double thisMaxY = this.bbox.getEnvelope().getMax().getY(); 902 double thisMinY = this.bbox.getEnvelope().getMin().getY(); 903 double otherMinY = other.getBBox().getEnvelope().getMin().getY(); 904 double otherMaxY = other.getBBox().getEnvelope().getMax().getY(); 905 return ( ( Math.abs( thisMaxY - otherMinY ) < 0.00001 ) || ( Math.abs( thisMinY - otherMaxY ) < 0.00001 ) ); 906 } 907 // comparing Y 908 double thisMaxX = this.bbox.getEnvelope().getMax().getX(); 909 double thisMinX = this.bbox.getEnvelope().getMin().getX(); 910 double otherMinX = other.getBBox().getEnvelope().getMin().getX(); 911 double otherMaxX = other.getBBox().getEnvelope().getMax().getX(); 912 return ( Math.abs( thisMaxX - otherMinX ) < 0.00001 ) 913 || ( Math.abs( thisMinX - otherMaxX ) < 0.00001 ); 914 915 } 916 } 917 return false; 918 } 919 920 @Override 921 public String toString() { 922 return "QuadNode sorted in Direction: " + ( ( comparingX ) ? "x" : "y" ) + " comparePosition: " 923 + comparePosition + " compareLength: " + compareLength; 924 } 925 926 } 927 }