001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wpvs/utils/QuadTreeSplitter.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
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     Aennchenstrasse 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Jens Fitzke
035     lat/lon GmbH
036     Aennchenstrasse 19
037     53177 Bonn
038     Germany
039     E-Mail: jens.fitzke@uni-bonn.de
040    
041     ---------------------------------------------------------------------------*/
042    
043    package org.deegree.ogcwebservices.wpvs.utils;
044    
045    import java.awt.Color;
046    import java.awt.Composite;
047    import java.awt.Graphics2D;
048    import java.util.ArrayList;
049    import java.util.Collections;
050    import java.util.Iterator;
051    import java.util.LinkedHashMap;
052    import java.util.Set;
053    
054    import javax.vecmath.Vector3d;
055    
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.model.crs.CoordinateSystem;
059    import org.deegree.model.spatialschema.Envelope;
060    import org.deegree.model.spatialschema.GeometryException;
061    import org.deegree.model.spatialschema.GeometryFactory;
062    import org.deegree.model.spatialschema.Position;
063    import org.deegree.model.spatialschema.Surface;
064    
065    /**
066     * <p>
067     * The <code>QuadTreeSplitter</code> class can be used to create x-y axis alligned request quads
068     * from a qiven List of {@link ResolutionStripe} s. These Stripes depend on the ViewFrustrum and
069     * it's projection on the x-y plane (the so called footprint). To create an approximation of this
070     * footprint a Quadtree (a geometric spatial structure, which recursively divides a boundingbox into
071     * four containing boundingboxes) is built. The leafs of this tree are merged according to their
072     * resolution and size to create the requeststripes.
073     * </p>
074     * 
075     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
076     * 
077     * @author last edited by: $Author: bezema $
078     * 
079     * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
080     * 
081     */
082    
083    public class QuadTreeSplitter {
084    
085        private static final ILogger LOG = LoggerFactory.getLogger( QuadTreeSplitter.class );
086    
087        private QuadNode rootNode;
088    
089        private ArrayList<ResolutionStripe> resolutionStripes;
090    
091        private double minimalTerrainHeight;
092    
093        private double minimalResolution;
094    
095        private CoordinateSystem crs;
096    
097        private boolean highQuality;
098    
099        private double scale;
100    
101    //    private double imageWidth;
102    
103        /**
104         * Creates a new Quadtree, from the given resolutionstripes. The resulting tree can be used to
105         * generate requeststripes which are (x-y) axis aligned.
106         * <p>
107         * The Quality argument is used for the recursive termination criteria, if it is set to true the
108         * requeststripes will accurately approximate the footprint (=projection of the viewfrustrum
109         * onto the ground) and the resolutions given in the resolutionstripes this results in a lot of
110         * requests which can slow down the wpvs. If set to false the footprint and the given
111         * resolutions will be approximated poorly but only a few requeststripes are created, resulting
112         * in a faster wpvs.
113         * </p>
114         * 
115         * @param resolutionStripes
116         *            the different resolutionstripes.
117         * @param imageWidth
118         *            the width of the target image, necessary for calculating the width resolution of
119         *            the requeststripe.
120         * @param highQuality
121         *            true if accurate (but many) requeststripes should be generated, false if none
122         *            accurate (but only a few) requests should be generated.
123         * @param g2d
124         *            for debugging -> TODO delete
125         */
126        public QuadTreeSplitter( ArrayList<ResolutionStripe> resolutionStripes, double imageWidth,
127                                boolean highQuality, Graphics2D g2d ) {
128            if ( resolutionStripes == null && resolutionStripes.size() <= 0 )
129                return;
130            this.resolutionStripes = resolutionStripes;
131            //this.imageWidth = imageWidth;
132            this.crs = resolutionStripes.get( 0 ).getCRSName();
133            this.scale = resolutionStripes.get( 0 ).getScale();
134            this.minimalTerrainHeight = resolutionStripes.get( 0 ).getMinimalTerrainHeight();
135            this.highQuality = highQuality;
136            // For the merge, the check is newmin < existing_min therefore -> min large and max small
137            // Position minPos = GeometryFactory.createPosition( 0, 0, 0 );
138            // Position maxPos = GeometryFactory.createPosition( 0, 0, 0 );
139            Envelope bbox = resolutionStripes.get( 0 ).getSurface().getEnvelope();
140            minimalResolution = Double.MAX_VALUE;
141            double maxResolution = Double.MIN_VALUE;
142            // find the highest and loweset maxResolution (which are needed for termination decissions
143            // and
144            // find create the axis-alligned bbox of the resolutionstripes which will be the rootnode.
145            for ( int i = 0; i < resolutionStripes.size(); ++i ) {
146                try {
147                    bbox = bbox.merge( resolutionStripes.get( i ).getSurface().getEnvelope() );
148                    // minimalResolution is the smallest number
149                    minimalResolution = Math.min( minimalResolution,
150                                                  resolutionStripes.get( i ).getMinResolution() );
151                    maxResolution = Math.max( maxResolution,
152                                              resolutionStripes.get( i ).getMaxResolution() );
153                } catch ( GeometryException e ) {
154                    e.printStackTrace();
155                    System.out.println( e.getLocalizedMessage() );
156                }
157            }
158            try {
159                if ( Math.abs( minimalResolution ) < 0.00001 ) // almost null
160                    minimalResolution = Math.pow( maxResolution, 1.0 / resolutionStripes.size() );
161                Position min = bbox.getMin();
162    
163                double zValue = min.getZ();
164                if ( Double.isNaN( min.getZ() ) )
165                    zValue = 0;
166    
167                Vector3d leftPoint = new Vector3d( min.getX(), min.getY(), zValue );
168                Vector3d rightPoint = new Vector3d( min.getX() + ( bbox.getWidth() ),
169                                                    min.getY() + ( bbox.getHeight() ), zValue );
170                double currentResolution = StripeFactory.calcScaleOfVector( leftPoint, rightPoint,
171                                                                            imageWidth );
172    
173                rootNode = new QuadNode( GeometryFactory.createSurface( bbox, crs ), maxResolution,
174                                         minimalResolution );
175    
176                createTree( rootNode, currentResolution, g2d );
177    
178            } catch ( GeometryException e ) {
179                LOG.logError( e.getLocalizedMessage(), e );
180            }
181        }
182    
183        /**
184         * After instantiating a Quadtree, this method can be called to build the (x-y) axis-alligned
185         * request stripes.
186         * 
187         * @param g2d
188         *            TODO just for debugging to be removed.
189         * @param extraRequestPercentage
190         *            a percentage to be added to the resulting stripes ( a value between [0,1] ),
191         *            which might correct gapes between stripes.
192         * @return the (x-y) axis-alligned request squares best fitted the given resolutionstripes.
193         */
194        public ArrayList<ResolutionStripe> getRequestQuads( Graphics2D g2d,
195                                                           double extraRequestPercentage ) {
196    
197            ArrayList<ResolutionStripe> resultList = new ArrayList<ResolutionStripe>();
198            if ( rootNode != null ) {
199                LinkedHashMap<Double, ArrayList<QuadNode>> lhm = new LinkedHashMap<Double, ArrayList<QuadNode>>(
200                                                                                                                 100 );
201                outputNodes( rootNode, lhm );
202                Set<Double> keys = lhm.keySet();
203                for ( Double resolution : keys ) {
204                    if ( lhm.containsKey( resolution ) ) {
205                        ArrayList<QuadNode> originalNodes = lhm.get( resolution );
206                        ArrayList<QuadNode> result = new ArrayList<QuadNode>( originalNodes.size() / 2 );
207                        // sorted to x values first.
208                        ArrayList<QuadNode> resultX = new ArrayList<QuadNode>( result.size() );
209                        boolean resort = mergeAndSort( originalNodes, resultX );
210                        while ( resort ) {
211                            result.clear();
212                            result.addAll( resultX );
213                            resultX.clear();
214                            resort = mergeAndSort( result, resultX );
215                        }
216                        // Check if sorting to y results in better values;
217                        for ( QuadNode node : originalNodes ) {
218                            node.compareY();
219                        }
220                        ArrayList<QuadNode> resultY = new ArrayList<QuadNode>();
221                        resort = mergeAndSort( originalNodes, resultY );
222                        while ( resort ) {
223                            result.clear();
224                            result.addAll( resultY );
225                            resultY.clear();
226                            resort = mergeAndSort( result, resultY );
227                        }
228                        result.clear();
229                        // Find the optimal sorting order (lesser quads) and check if the perpendicular
230                        // order results in lesser requeststripes (it usually does)
231                        if ( resultX.size() < resultY.size() ) {
232                            for ( QuadNode node : resultX ) {
233                                node.compareY();
234                            }
235                            while ( mergeAndSort( resultX, result ) ) {
236                                resultX.clear();
237                                resultX.addAll( result );
238                                result.clear();
239                            }
240                        } else {
241                            for ( QuadNode node : resultY ) {
242                                node.compareX();
243                            }
244                            while ( mergeAndSort( resultY, result ) ) {
245                                resultY.clear();
246                                resultY.addAll( result );
247                                result.clear();
248                            }
249                        }
250                        for ( QuadNode quad : result ) {
251                            Envelope env = quad.getBBox().getEnvelope();
252                            Position envMin = env.getMin();
253                            Position envMax = env.getMax();
254    
255                            double width = env.getWidth();
256                            double height = env.getHeight();
257    
258                            // enlarge the envelope to extraRequestPercentage to correct gapes between
259                            // stripes.
260                            double extraWidth = width * extraRequestPercentage;
261                            double extraHeight = height * extraRequestPercentage;
262    
263                            Position newMin = GeometryFactory.createPosition(
264                                                                              envMin.getX()-extraWidth,
265                                                                              envMin.getY()-extraHeight,
266                                                                              envMin.getZ() );
267    
268                            Position newMax = GeometryFactory.createPosition(
269                                                                              envMax.getX()+extraWidth,
270                                                                              envMax.getY()+extraHeight,
271                                                                              envMax.getZ() );
272    
273    //                        Vector3d minVec = new Vector3d( newMin.getX(), newMin.getY(), newMin.getZ() );
274    //                        Vector3d maxVec = new Vector3d( newMax.getX(), newMin.getY(), newMin.getZ() );
275    //
276    //                        double maxResolution = StripeFactory.calcScaleOfVector( minVec, maxVec,
277    //                                                                                imageWidth );
278                            double maxResolution = quad.getMinResolution();
279                            double minResolution = maxResolution;
280    
281                            try {
282                                Surface resultSurface = GeometryFactory.createSurface(
283                                                                                       GeometryFactory.createEnvelope(
284                                                                                                                       newMin,
285                                                                                                                       newMax,
286                                                                                                                       this.crs ),
287                                                                                       this.crs );
288                                ResolutionStripe rs = new ResolutionStripe( resultSurface,
289                                                                            maxResolution,
290                                                                            minResolution,
291                                                                            minimalTerrainHeight, scale );
292                                resultList.add( rs );
293    
294                            } catch ( GeometryException e ) {
295                                LOG.logError( e.getLocalizedMessage(), e );
296                            }
297                        }
298                    }
299                }
300            }
301            if ( g2d != null ) {
302                for ( ResolutionStripe stripe : resultList ) {
303                    drawSquare( new QuadNode( stripe.getSurface(), stripe.getMaxResolution(),
304                                              stripe.getMinResolution() ), g2d, Color.MAGENTA );
305                }
306            }
307            return resultList;
308        }
309    
310        /**
311         * A little helper function which first sorts the given list of quadnodes according to their
312         * sorting order and afterwards merges all stripes which are adjacent and have the same
313         * resolution.
314         * 
315         * @param toBeMerged
316         *            the list of Quadnodes which must be sorted and merged.
317         * @param resultList
318         *            the list containing the merged quadnodes.
319         * @return true if the list should be rechecked (that is, if one or more merge(s) took place)
320         */
321        private boolean mergeAndSort( ArrayList<QuadNode> toBeMerged, ArrayList<QuadNode> resultList ) {
322            Collections.sort( toBeMerged );
323            Iterator<QuadNode> it = toBeMerged.iterator();
324            QuadNode first = null;
325            QuadNode second = null;
326            boolean needsResort = false;
327            resultList.clear();
328            while ( second != null || it.hasNext() ) {
329                if ( second == null ) {
330                    first = it.next();
331                } else {
332                    first = second;
333                    second = null;
334                }
335                Envelope requestEnvelope = first.getBBox().getEnvelope();
336                while ( it.hasNext() && second == null ) {
337                    second = it.next();
338                    if ( first.canMerge( second ) ) {
339                        try {
340                            requestEnvelope = requestEnvelope.merge( second.getBBox().getEnvelope() );
341                        } catch ( GeometryException ge ) {
342                            // An error occured, it might be best to not merge these envelopes.
343                            LOG.logError( ge.getLocalizedMessage(), ge );
344                        }
345                        second = null;
346                        needsResort = true;
347                    }
348                }
349                Surface resultSurface = null;
350                try {
351                    resultSurface = GeometryFactory.createSurface( requestEnvelope, crs );
352                } catch ( GeometryException ge ) {
353                    // An error occured, it might be best not to merge these envelopes.
354                    LOG.logError( ge.getLocalizedMessage(), ge );
355                }
356                resultList.add( new QuadNode( resultSurface, first.getMaxResolution(),
357                                              first.getMinResolution(), first.isComparingX() ) );
358            }
359            return needsResort;
360        }
361    
362        /**
363         * This Method actually builds the tree. The decission of a split is made by evaluating the
364         * minimal maxResolution of the intersecting ResultionStripe.
365         * 
366         * @param father
367         *            the Father node which will be splittet into four axis aligned sons
368         * @param fatherResolution
369         *            the maxResolution of a width axis of the axis-aligned bbox
370         * @param g2d
371         *            just for debugging-> TODO must be deleted
372         * @throws GeometryException
373         *             if the Envelope cannot be created
374         */
375        private void createTree( QuadNode father, double fatherResolution, Graphics2D g2d )
376                                throws GeometryException {
377            Position min = father.getBBox().getEnvelope().getMin();
378            double widthHalf = 0.5 * father.getBBox().getEnvelope().getWidth();
379            double heightHalf = 0.5 * father.getBBox().getEnvelope().getHeight();
380            double lowerLeftX = min.getX();
381            double lowerLeftY = min.getY();
382            double currentResolution = 0.5 * fatherResolution;
383            // no more recursion.
384            if ( currentResolution < minimalResolution )
385                return;
386    
387            checkSon( father, currentResolution, QuadNode.LOWER_LEFT_SON, lowerLeftX, lowerLeftY,
388                      lowerLeftX + widthHalf, lowerLeftY + heightHalf, g2d );
389    
390            // lowerright
391            checkSon( father, currentResolution, QuadNode.LOWER_RIGHT_SON, lowerLeftX + widthHalf,
392                      lowerLeftY, lowerLeftX + ( 2 * widthHalf ), lowerLeftY + heightHalf, g2d );
393    
394            // upperleft
395            checkSon( father, currentResolution, QuadNode.UPPER_LEFT_SON, lowerLeftX, lowerLeftY
396                                                                                      + heightHalf,
397                      lowerLeftX + widthHalf, lowerLeftY + ( 2 * heightHalf ), g2d );
398    
399            // upperright
400            checkSon( father, currentResolution, QuadNode.UPPER_RIGHT_SON, lowerLeftX + widthHalf,
401                      lowerLeftY + heightHalf, lowerLeftX + 2 * widthHalf, lowerLeftY + 2 * heightHalf,
402                      g2d );
403        }
404    
405        /**
406         * Decides if the father quad has to be subdivided into it's sons.
407         * 
408         * @param father
409         *            the Father quad to divide
410         * @param maxResolution
411         *            the maxResolution of the fathers son (half the maxResolution of the father)
412         * @param quadArea
413         *            the area of a son of the son (1/16 of the fathers area)
414         * @param SON_ID
415         *            the son to check
416         * @param lowerLeftX
417         *            minx of the bbox of the fathers son
418         * @param lowerLeftY
419         *            miny of the bbox of the fathers son
420         * @param upperRightX
421         *            maxx of the bbox of the fathers son
422         * @param upperRightY
423         *            maxY of the bbox of the fathers son
424         * @param g2d
425         *            for debugging -> TODO delete
426         * @throws GeometryException
427         *             if no surface can be created
428         */
429        private void checkSon( QuadNode father, double resolution, final int SON_ID, double lowerLeftX,
430                              double lowerLeftY, double upperRightX, double upperRightY, Graphics2D g2d )
431                                throws GeometryException {
432            Position min = GeometryFactory.createPosition(
433                                                           lowerLeftX,
434                                                           lowerLeftY,
435                                                           father.getBBox().getEnvelope().getMin().getZ() );
436            Position max = GeometryFactory.createPosition(
437                                                           upperRightX,
438                                                           upperRightY,
439                                                           father.getBBox().getEnvelope().getMax().getZ() );
440            Surface bbox = GeometryFactory.createSurface(
441                                                          GeometryFactory.createEnvelope( min, max, crs ),
442                                                          crs );
443            ResolutionStripe intersectedStripe = ( highQuality ) ? getIntersectionForQualityConfiguration( bbox )
444                                                                : getIntersectionForFastConfiguration( bbox );
445            if ( intersectedStripe != null ) { // found an intersecting resolutionStripe
446                QuadNode son = new QuadNode( bbox, intersectedStripe.getMaxResolution(),
447                                             intersectedStripe.getMinResolution() );
448                double sonsResolution = intersectedStripe.getMinResolution();
449                father.addSon( SON_ID, son );
450                if ( resolution >= sonsResolution ) {
451                    createTree( son, resolution, g2d );
452                }
453            }
454        }
455    
456        /**
457         * Finds the resolutionstripe with the lowest minResolution which intersects with the given
458         * bbox. Resulting in a lot of different requests.
459         * 
460         * @param bbox
461         *            the BoundingBox of the Envelope to check.
462         * @return the resolutionStripe which intersects the bbox.
463         */
464        private ResolutionStripe getIntersectionForQualityConfiguration( Surface bbox ) {
465            ResolutionStripe resultStripe = null;
466            for ( ResolutionStripe stripe : resolutionStripes ) {
467                if ( bbox.intersects( stripe.getSurface() ) ) {
468                    if ( resultStripe != null ) {
469                        if ( ( stripe.getMinResolution() < resultStripe.getMinResolution() ) ) {
470                            resultStripe = stripe;
471                        }
472                    } else {
473                        resultStripe = stripe;
474                    }
475                }
476            }
477            return resultStripe;
478        }
479    
480        /**
481         * Finds the resolutionstripe with the highest maxResolution which intersects with the given
482         * bbox. Resulting in only a few different requests.
483         * 
484         * @param bbox
485         *            the BoundingBox of the Envelope to check.
486         * @return the resolutionStripe which intersects the bbox.
487         */
488        private ResolutionStripe getIntersectionForFastConfiguration( Surface bbox ) {
489            ResolutionStripe resultStripe = null;
490            for ( ResolutionStripe stripe : resolutionStripes ) {
491                if ( bbox.intersects( stripe.getSurface() ) ) {
492                    if ( resultStripe != null ) {
493                        if ( ( stripe.getMaxResolution() > resultStripe.getMaxResolution() ) ) {
494                            resultStripe = stripe;
495                        }
496                    } else {
497                        resultStripe = stripe;
498                    }
499                }
500            }
501            return resultStripe;
502        }
503    
504        /**
505         * Outputs the tree
506         * 
507         * @param g2d
508         *            if the quadtree should be drawn.
509         */
510        public void outputTree( Graphics2D g2d ) {
511            if ( rootNode != null ) {
512                if ( g2d != null ) {
513                    System.out.println( "number Of leaves-> " + outputNodes( rootNode, g2d ) );
514                } else {
515                    outputNodes( rootNode, "" );
516                }
517            }
518        }
519    
520        private int outputNodes( QuadNode father, Graphics2D g2d ) {
521            if ( father.isLeaf() ) {
522                drawSquare( father, g2d, Color.BLACK );
523                return 1;
524            }
525            QuadNode[] nodes = father.getSons();
526            int result = 0;
527            for ( QuadNode node : nodes ) {
528                if ( node != null ) {
529                    result += outputNodes( node, g2d );
530                }
531    
532            }
533            return result;
534        }
535    
536        private void outputNodes( QuadNode father, String indent ) {
537            if ( father.isLeaf() ) {
538                System.out.println( indent + "(father)" + father.getBBox() );
539            } else {
540                QuadNode[] nodes = father.getSons();
541                for ( QuadNode node : nodes ) {
542                    if ( node != null ) {
543                        indent += "-";
544                        outputNodes( node, indent );
545                    }
546                }
547            }
548        }
549    
550        /**
551         * Find the leaf nodes and add them according to their maxResolution in a LinkedHashMap.
552         * 
553         * @param father
554         *            the node to check
555         * @param outputMap
556         *            the map to output to.
557         */
558        private void outputNodes( QuadNode father, LinkedHashMap<Double, ArrayList<QuadNode>> outputMap ) {
559            if ( father.isLeaf() ) {
560                Double key = new Double( father.getMaxResolution() );
561                ArrayList<QuadNode> ts = outputMap.get( key );
562                if ( ts == null ) { // I know, but I don't put null values so it's ok
563                    ts = new ArrayList<QuadNode>();
564                    outputMap.put( key, ts );
565                }
566                if ( ts.add( father ) == false ) {
567                    System.out.println( "quadnode allready in set" );
568                }
569            } else {
570                QuadNode[] nodes = father.getSons();
571                for ( QuadNode node : nodes ) {
572                    if ( node != null ) {
573                        outputNodes( node, outputMap );
574                    }
575                }
576            }
577        }
578    
579        private void drawSquare( QuadNode node, Graphics2D g2d, Color c ) {
580            if ( g2d != null ) {
581                g2d.setColor( c );
582                Envelope env = node.getBBox().getEnvelope();
583                Position min = env.getMin();
584                int height = (int) env.getHeight();
585                int width = (int) env.getWidth();
586                g2d.drawRect( (int) min.getX(), (int) min.getY(), width, height );
587                Composite co = g2d.getComposite();
588                g2d.setColor( new Color( c.getRed(), c.getGreen(), c.getBlue(), 64 ) );
589                g2d.fillRect( (int) min.getX(), (int) min.getY(), width, height );
590                g2d.setComposite( co );
591            }
592        }
593    
594        /**
595         * 
596         * The <code>QuadNode</code> class is the bean for every node of the quadtree. It contains a
597         * axis-aligned BBox and the maxResolution of its associated resolutionStripe. It can have upto
598         * four sons.
599         * 
600         * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
601         * 
602         * @author last edited by: $Author: bezema $
603         * 
604         * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
605         * 
606         */
607        private class QuadNode implements Comparable<QuadNode> {
608    
609            private QuadNode[] sons;
610    
611            private Surface bbox;
612    
613            private double maxResolution;
614    
615            private double minResolution;
616    
617            private double comparePosition;
618    
619            private double compareLength;
620    
621            private boolean comparingX;
622    
623            static final int LOWER_LEFT_SON = 0;
624    
625            static final int LOWER_RIGHT_SON = 1;
626    
627            static final int UPPER_LEFT_SON = 2;
628    
629            static final int UPPER_RIGHT_SON = 3;
630    
631            QuadNode( Surface bbox, double maxResolution, double minResolution ) {
632                this.bbox = bbox;
633                sons = new QuadNode[4];
634                this.maxResolution = maxResolution;
635                this.minResolution = minResolution;
636                comparePosition = bbox.getEnvelope().getMin().getX();
637                compareLength = bbox.getEnvelope().getWidth();
638                comparingX = true;
639            }
640    
641            QuadNode( Surface bbox, double maxResolution, double minResolution,
642                             boolean compareDirection ) {
643                this( bbox, maxResolution, minResolution );
644                if ( compareDirection )
645                    compareX();
646                else
647                    compareY();
648            }
649    
650            void addSon( final int sonID, QuadNode son ) {
651                if ( sonID == LOWER_LEFT_SON || sonID == LOWER_RIGHT_SON || sonID == UPPER_LEFT_SON
652                     || sonID == UPPER_RIGHT_SON ) {
653                    this.sons[sonID] = son;
654                }
655            }
656    
657            Surface getBBox() {
658                return bbox;
659            }
660    
661            void compareX() {
662                comparePosition = bbox.getEnvelope().getMin().getX();
663                compareLength = bbox.getEnvelope().getWidth();
664                comparingX = true;
665            }
666    
667            void compareY() {
668                comparePosition = bbox.getEnvelope().getMin().getY();
669                compareLength = bbox.getEnvelope().getHeight();
670                comparingX = false;
671            }
672    
673            /**
674             * If this Quadnode has no sons it is called a leaf.
675             * 
676             * @return true if no sons, false otherwhise.
677             */
678            boolean isLeaf() {
679                return ( sons[0] == null && sons[1] == null && sons[2] == null && sons[3] == null );
680            }
681    
682            QuadNode[] getSons() {
683                return sons;
684            }
685    
686            QuadNode getSon( final int sonID ) {
687                if ( sonID != LOWER_LEFT_SON || sonID != LOWER_RIGHT_SON || sonID != UPPER_LEFT_SON
688                     || sonID != UPPER_RIGHT_SON )
689                    return null;
690                return sons[sonID];
691            }
692    
693            /**
694             * @return The max maxResolution of the Stripe.
695             */
696            double getMaxResolution() {
697                return maxResolution;
698            }
699    
700            /**
701             * @return the minResolution value.
702             */
703            double getMinResolution() {
704                return minResolution;
705            }
706    
707            boolean isComparingX() {
708                return comparingX;
709            }
710    
711            double getComparePosition() {
712                return comparePosition;
713            }
714    
715            double getCompareLength() {
716                return compareLength;
717            }
718    
719            /*
720             * Attention, the equal result "0" is not really a check for the equality of two Quadnodes,
721             * it just reflex, that two QuadNodes have the same sorting properties -> the position - (y
722             * or x) and the length in this direction are equal. It is very plausible that they have
723             * totally different positions and length in the other (not checked) direction.
724             * 
725             * @see java.lang.Comparable#compareTo(T)
726             */
727            public int compareTo( QuadNode other ) {
728                double otherPosition = other.getComparePosition();
729                if ( Math.abs( comparePosition - otherPosition ) < 0.00001 ) {
730                    double otherLength = other.getCompareLength();
731                    if ( Math.abs( compareLength - otherLength ) < 0.00001 ) {
732                        if ( comparingX ) {
733                            double thisMinY = this.bbox.getEnvelope().getMin().getY();
734                            double otherMinY = other.getBBox().getEnvelope().getMin().getY();
735                            if ( ( Math.abs( thisMinY - otherMinY ) < 0.00001 ) )
736                                return 0;
737                            if ( thisMinY < otherMinY )
738                                return 1;
739                            return -1;
740                        }
741                        double thisMinX = this.bbox.getEnvelope().getMin().getX();
742                        double otherMinX = other.getBBox().getEnvelope().getMin().getX();
743                        if ( ( Math.abs( thisMinX - otherMinX ) < 0.00001 ) )
744                            return 0;
745                        if ( thisMinX < otherMinX )
746                            return 1;
747                        return -1;
748                    }
749                    if ( compareLength < otherLength ) {
750                        return -1;
751                    }
752                    return 1;
753                }
754                if ( comparePosition < otherPosition )
755                    return -1;
756                return 1;
757            }
758    
759            /**
760             * simple check if two quadnodes can be merged, according to their positions, length and if
761             * they are adjacent.
762             * 
763             * @param other
764             * @return true if this QuadNode can be merged with the Other.
765             */
766            boolean canMerge( QuadNode other ) {
767                double otherPosition = other.getComparePosition();
768                if ( Math.abs( comparePosition - otherPosition ) < 0.01 ) {
769                    double otherLength = other.getCompareLength();
770                    if ( Math.abs( compareLength - otherLength ) < 0.01 ) {
771                        // the origins and the length are mergable, now check if the Quadnodes are
772                        // adjacent
773                        if ( comparingX ) {
774                            double thisMaxY = this.bbox.getEnvelope().getMax().getY();
775                            double thisMinY = this.bbox.getEnvelope().getMin().getY();
776                            double otherMinY = other.getBBox().getEnvelope().getMin().getY();
777                            double otherMaxY = other.getBBox().getEnvelope().getMax().getY();
778                            if ( ( Math.abs( thisMaxY - otherMinY ) < 0.00001 )
779                                 || ( Math.abs( thisMinY - otherMaxY ) < 0.00001 ) ) {
780                                return true;
781                            }
782                        } else {
783    
784                            double thisMaxX = this.bbox.getEnvelope().getMax().getX();
785                            double thisMinX = this.bbox.getEnvelope().getMin().getX();
786                            double otherMinX = other.getBBox().getEnvelope().getMin().getX();
787                            double otherMaxX = other.getBBox().getEnvelope().getMax().getX();
788                            if ( ( Math.abs( thisMaxX - otherMinX ) < 0.00001 )
789                                 || ( Math.abs( thisMinX - otherMaxX ) < 0.00001 ) ) {
790                                return true;
791                            }
792                        }
793                    }
794                }
795                return false;
796            }
797    
798            @Override
799            public String toString() {
800                return "QuadNode sorted in Direction: " + ( ( comparingX ) ? "x" : "y" )
801                       + " comparePosition: " + comparePosition + " compareLength: " + compareLength;
802            }
803    
804        }
805    }