001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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 }