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