001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/graphics/displayelements/LabelFactory.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    package org.deegree.graphics.displayelements;
037    
038    import static java.lang.Double.parseDouble;
039    import static java.lang.Math.abs;
040    import static java.lang.Math.sqrt;
041    import static org.deegree.framework.log.LoggerFactory.getLogger;
042    
043    import java.awt.Color;
044    import java.awt.Font;
045    import java.awt.Graphics2D;
046    import java.awt.font.FontRenderContext;
047    import java.awt.font.LineMetrics;
048    import java.awt.geom.Rectangle2D;
049    import java.util.ArrayList;
050    import java.util.Collections;
051    import java.util.Iterator;
052    import java.util.List;
053    
054    import org.deegree.framework.log.ILogger;
055    import org.deegree.graphics.sld.Halo;
056    import org.deegree.graphics.sld.LabelPlacement;
057    import org.deegree.graphics.sld.LinePlacement;
058    import org.deegree.graphics.sld.ParameterValueType;
059    import org.deegree.graphics.sld.PointPlacement;
060    import org.deegree.graphics.sld.TextSymbolizer;
061    import org.deegree.graphics.transformation.GeoTransform;
062    import org.deegree.model.feature.Feature;
063    import org.deegree.model.filterencoding.FilterEvaluationException;
064    import org.deegree.model.spatialschema.Curve;
065    import org.deegree.model.spatialschema.Geometry;
066    import org.deegree.model.spatialschema.GeometryException;
067    import org.deegree.model.spatialschema.GeometryFactory;
068    import org.deegree.model.spatialschema.LineString;
069    import org.deegree.model.spatialschema.MultiCurve;
070    import org.deegree.model.spatialschema.MultiPoint;
071    import org.deegree.model.spatialschema.MultiSurface;
072    import org.deegree.model.spatialschema.Point;
073    import org.deegree.model.spatialschema.Position;
074    import org.deegree.model.spatialschema.Surface;
075    
076    /**
077     * Does the labeling, i.e. creates (screen) <tt>Label</tt> representations from <tt>LabelDisplayElement</tt>s.
078     * <p>
079     * Different geometry-types (of the LabelDisplayElement) imply different strategies concerning the way the
080     * <tt>Labels</tt> are generated.
081     * <p>
082     *
083     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
084     * @version $Revision: 27114 $ $Date: 2010-09-30 16:04:40 +0200 (Do, 30 Sep 2010) $
085     */
086    public class LabelFactory {
087    
088        private static final ILogger LOG = getLogger( LabelFactory.class );
089    
090        /**
091         * @param caption
092         * @param font
093         * @param color
094         * @param metrics
095         * @param feature
096         * @param halo
097         * @param x
098         * @param y
099         * @param w
100         * @param h
101         * @param rotation
102         * @param anchorPointX
103         * @param anchorPointY
104         * @param displacementX
105         * @param displacementY
106         * @return label-representations
107         * @deprecated use the one with opacity instead
108         */
109        @Deprecated
110        public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature,
111                                         Halo halo, int x, int y, int w, int h, double rotation, double anchorPointX,
112                                         double anchorPointY, double displacementX, double displacementY ) {
113            return createLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPointX,
114                                anchorPointY, displacementX, displacementY, 1.0 );
115        }
116    
117        /**
118         * @param caption
119         * @param font
120         * @param color
121         * @param metrics
122         * @param feature
123         * @param halo
124         * @param x
125         * @param y
126         * @param w
127         * @param h
128         * @param rotation
129         * @param anchorPointX
130         * @param anchorPointY
131         * @param displacementX
132         * @param displacementY
133         * @param opacity
134         * @return label representations
135         */
136        public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature,
137                                         Halo halo, int x, int y, int w, int h, double rotation, double anchorPointX,
138                                         double anchorPointY, double displacementX, double displacementY, double opacity ) {
139            if ( rotation == 0.0 ) {
140                return new HorizontalLabel( caption, font, color, metrics, feature, halo, x, y, w, h,
141                                            new double[] { anchorPointX, anchorPointY }, new double[] { displacementX,
142                                                                                                       displacementY },
143                                            opacity );
144            }
145            return new RotatedLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation,
146                                     new double[] { anchorPointX, anchorPointY }, new double[] { displacementX,
147                                                                                                 displacementY }, opacity );
148    
149        }
150    
151        /**
152         * @param caption
153         * @param font
154         * @param color
155         * @param metrics
156         * @param feature
157         * @param halo
158         * @param x
159         * @param y
160         * @param w
161         * @param h
162         * @param rotation
163         * @param anchorPoint
164         * @param displacement
165         * @param opacity
166         * @return the label
167         */
168        public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature,
169                                         Halo halo, int x, int y, int w, int h, double rotation, double[] anchorPoint,
170                                         double[] displacement, double opacity ) {
171            if ( rotation == 0.0 ) {
172                return new HorizontalLabel( caption, font, color, metrics, feature, halo, x, y, w, h, anchorPoint,
173                                            displacement, opacity );
174            }
175            return new RotatedLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPoint,
176                                     displacement, opacity );
177    
178        }
179    
180        /**
181         * @param caption
182         * @param font
183         * @param color
184         * @param metrics
185         * @param feature
186         * @param halo
187         * @param x
188         * @param y
189         * @param w
190         * @param h
191         * @param rotation
192         * @param anchorPoint
193         * @param displacement
194         * @return label-representations
195         * @deprecated use the one with opacity instead
196         */
197        @Deprecated
198        public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature,
199                                         Halo halo, int x, int y, int w, int h, double rotation, double[] anchorPoint,
200                                         double[] displacement ) {
201            return createLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPoint,
202                                displacement, 1.0 );
203        }
204    
205        /**
206         * Generates label-representations for a given <tt>LabelDisplayElement</tt>.
207         * <p>
208         *
209         * @param element
210         * @param projection
211         * @param g
212         * @return label-representations
213         * @throws Exception
214         */
215        public static Label[] createLabels( LabelDisplayElement element, GeoTransform projection, Graphics2D g )
216                                throws Exception {
217    
218            Label[] labels = new Label[0];
219            Feature feature = element.getFeature();
220            String caption = element.getLabel().evaluate( feature );
221    
222            // sanity check: empty labels are ignored
223            if ( caption == null || caption.trim().equals( "" ) ) {
224                return labels;
225            }
226    
227            Geometry geometry = element.getGeometry();
228            TextSymbolizer symbolizer = (TextSymbolizer) element.getSymbolizer();
229    
230            // gather font information
231            org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
232            java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature )
233                                                                                  | sldFont.getWeight( feature ),
234                                                    sldFont.getSize( feature ) );
235            g.setFont( font );
236    
237            // the bboxing is not a good solution, the geometry should be used instead (rectangle or
238            // something)
239            // on the other hand, we'd need a flag to symbolize "use the surface as bounding box"
240            // anyway, so... TODO
241            ParameterValueType[] bbox = symbolizer.getBoundingBox();
242            if ( bbox != null ) {
243                try {
244                    double x = parseDouble( bbox[0].evaluate( feature ) );
245                    double y = parseDouble( bbox[1].evaluate( feature ) );
246                    double maxx = parseDouble( bbox[2].evaluate( feature ) );
247                    double maxy = parseDouble( bbox[3].evaluate( feature ) );
248                    double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
249                    return new Label[] { createLabelInABox( caption, font, sldFont.getColor( feature ),
250                                                            symbolizer.getHalo(), x, y, maxx - x, maxy - y, feature,
251                                                            projection, opacity ) };
252                } catch ( NumberFormatException nfe ) {
253                    LOG.logWarning( "Could not render text because of missing bbox attributes." );
254                    return labels; // empty array
255                }
256            }
257    
258            FontRenderContext frc = g.getFontRenderContext();
259            Rectangle2D bounds = font.getStringBounds( caption, frc );
260            LineMetrics metrics = font.getLineMetrics( caption, frc );
261            int w = (int) bounds.getWidth();
262            int h = (int) bounds.getHeight();
263            // int descent = (int) metrics.getDescent ();
264    
265            if ( geometry instanceof Point || geometry instanceof MultiPoint ) {
266    
267                // get screen coordinates
268                int[] coords = calcScreenCoordinates( projection, geometry );
269                int x = coords[0];
270                int y = coords[1];
271    
272                // default placement information
273                double rotation = 0.0;
274                double[] anchorPoint = { 0.0, 0.5 };
275                double[] displacement = { 0.0, 0.0 };
276    
277                // use placement information from SLD
278                LabelPlacement lPlacement = symbolizer.getLabelPlacement();
279    
280                if ( lPlacement != null ) {
281                    PointPlacement pPlacement = lPlacement.getPointPlacement();
282                    anchorPoint = pPlacement.getAnchorPoint( feature );
283                    displacement = pPlacement.getDisplacement( feature );
284                    rotation = pPlacement.getRotation( feature );
285                }
286    
287                double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
288                labels = new Label[] { createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
289                                                    symbolizer.getHalo(), x, y, w, h, rotation, anchorPoint, displacement,
290                                                    opacity ) };
291            } else if ( geometry instanceof Surface || geometry instanceof MultiSurface ) {
292    
293                // get screen coordinates
294                int[] coords = calcScreenCoordinates( projection, geometry );
295                int x = coords[0];
296                int y = coords[1];
297    
298                // default placement information
299                double rotation = 0.0;
300                double[] anchorPoint = { 0.5, 0.5 };
301                double[] displacement = { 0.0, 0.0 };
302    
303                // use placement information from SLD
304                LabelPlacement lPlacement = symbolizer.getLabelPlacement();
305    
306                if ( lPlacement != null ) {
307                    PointPlacement pPlacement = lPlacement.getPointPlacement();
308    
309                    // check if the label is to be centered within the intersection
310                    // of
311                    // the screen surface and the polygon geometry
312                    if ( pPlacement.isAuto() ) {
313                        Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null );
314                        Geometry intersection = screenSurface.intersection( geometry );
315                        if ( intersection != null ) {
316                            Position source = intersection.getCentroid().getPosition();
317                            x = (int) ( projection.getDestX( source.getX() ) + 0.5 );
318                            y = (int) ( projection.getDestY( source.getY() ) + 0.5 );
319                        }
320                    }
321                    anchorPoint = pPlacement.getAnchorPoint( feature );
322                    displacement = pPlacement.getDisplacement( feature );
323                    rotation = pPlacement.getRotation( feature );
324                }
325    
326                double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
327                labels = new Label[] { createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
328                                                    symbolizer.getHalo(), x, y, w, h, rotation, anchorPoint, displacement,
329                                                    opacity )
330    
331                };
332            } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) {
333                Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null );
334                Geometry intersection = screenSurface.intersection( geometry );
335                if ( intersection != null ) {
336                    List<Label> list = null;
337                    if ( intersection instanceof Curve ) {
338                        list = createLabels( (Curve) intersection, element, g, projection );
339                    } else if ( intersection instanceof MultiCurve ) {
340                        list = createLabels( (MultiCurve) intersection, element, g, projection );
341                    } else {
342                        throw new Exception( "Intersection produced unexpected " + "geometry type: '"
343                                             + intersection.getClass().getName() + "'!" );
344                    }
345                    labels = new Label[list.size()];
346                    for ( int i = 0; i < labels.length; i++ ) {
347                        Label label = list.get( i );
348                        labels[i] = label;
349                    }
350                }
351            } else {
352                throw new Exception( "LabelFactory does not implement generation " + "of Labels from geometries of type: '"
353                                     + geometry.getClass().getName() + "'!" );
354            }
355            return labels;
356        }
357    
358        /**
359         * Determines positions on the given <tt>MultiCurve</tt> where a caption could be drawn. For each of this positons,
360         * three candidates are produced; one on the line, one above of it and one below.
361         * <p>
362         *
363         * @param multiCurve
364         * @param element
365         * @param g
366         * @param projection
367         * @return ArrayList containing Arrays of Label-objects
368         * @throws FilterEvaluationException
369         */
370        public static List<Label> createLabels( MultiCurve multiCurve, LabelDisplayElement element, Graphics2D g,
371                                                GeoTransform projection )
372                                throws FilterEvaluationException {
373    
374            List<Label> placements = Collections.synchronizedList( new ArrayList<Label>( 10 ) );
375            for ( int i = 0; i < multiCurve.getSize(); i++ ) {
376                Curve curve = multiCurve.getCurveAt( i );
377                placements.addAll( createLabels( curve, element, g, projection ) );
378            }
379            return placements;
380        }
381    
382        /**
383         * Determines positions on the given <tt>Curve</tt> where a caption could be drawn. For each of this positons, three
384         * candidates are produced; one on the line, one above of it and one below.
385         * <p>
386         *
387         * @param curve
388         * @param element
389         * @param g
390         * @param projection
391         * @return ArrayList containing Arrays of Label-objects
392         * @throws FilterEvaluationException
393         */
394        public static ArrayList<Label> createLabels( Curve curve, LabelDisplayElement element, Graphics2D g,
395                                                     GeoTransform projection )
396                                throws FilterEvaluationException {
397    
398            Feature feature = element.getFeature();
399    
400            // determine the placement type and parameters from the TextSymbolizer
401            double perpendicularOffset = 0.0;
402            int placementType = LinePlacement.TYPE_ABSOLUTE;
403            double lineWidth = 3.0;
404            int gap = 6;
405            TextSymbolizer symbolizer = ( (TextSymbolizer) element.getSymbolizer() );
406            if ( symbolizer.getLabelPlacement() != null ) {
407                LinePlacement linePlacement = symbolizer.getLabelPlacement().getLinePlacement();
408                if ( linePlacement != null ) {
409                    placementType = linePlacement.getPlacementType( feature );
410                    perpendicularOffset = linePlacement.getPerpendicularOffset( feature );
411                    lineWidth = linePlacement.getLineWidth( feature );
412                    gap = linePlacement.getGap( feature );
413                }
414            }
415    
416            // get width & height of the caption
417            String caption = element.getLabel().evaluate( feature );
418            org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
419            java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature )
420                                                                                  | sldFont.getWeight( feature ),
421                                                    sldFont.getSize( feature ) );
422            g.setFont( font );
423            FontRenderContext frc = g.getFontRenderContext();
424            Rectangle2D bounds = font.getStringBounds( caption, frc );
425            LineMetrics metrics = font.getLineMetrics( caption, frc );
426            double width = bounds.getWidth();
427            double height = bounds.getHeight();
428    
429            // get screen coordinates of the line
430            int[][] pos = calcScreenCoordinates( projection, curve );
431    
432            // ideal distance from the line
433            double delta = height / 2.0 + lineWidth / 2.0;
434    
435            // walk along the linestring and "collect" possible placement positions
436            int w = (int) width;
437            int lastX = pos[0][0];
438            int lastY = pos[1][0];
439            int count = pos[2][0];
440            int boxStartX = lastX;
441            int boxStartY = lastY;
442    
443            ArrayList<Label> labels = new ArrayList<Label>( 100 );
444            List<int[]> eCandidates = Collections.synchronizedList( new ArrayList<int[]>( 100 ) );
445            int i = 1;
446            int kk = 0;
447            while ( i < count && kk < 100 ) {
448                kk++;
449                int x = pos[0][i];
450                int y = pos[1][i];
451    
452                // segment found where endpoint of box should be located?
453                if ( getDistance( boxStartX, boxStartY, x, y ) >= w ) {
454    
455                    double offx = 0, offy = 0;
456    
457                    if ( abs( perpendicularOffset ) > 1E-10 ) {
458                        double dx = lastX - x;
459                        double dy = lastY - y;
460    
461                        double len = sqrt( dx * dx + dy * dy );
462                        dx /= len;
463                        dy /= len;
464    
465                        offx += perpendicularOffset * dy;
466                        offy += -perpendicularOffset * dx;
467                    }
468    
469                    int[] p0 = new int[] { boxStartX, boxStartY };
470                    int[] p1 = new int[] { lastX, lastY };
471                    int[] p2 = new int[] { x, y };
472    
473                    int[] p = findPointWithDistance( p0, p1, p2, w );
474                    x = p[0];
475                    y = p[1];
476    
477                    lastX = x;
478                    lastY = y;
479                    int boxEndX = x;
480                    int boxEndY = y;
481    
482                    // does the linesegment run from right to left?
483                    if ( x <= boxStartX ) {
484                        boxEndX = boxStartX;
485                        boxEndY = boxStartY;
486                        boxStartX = x;
487                        boxStartY = y;
488                        x = boxEndX;
489                        y = boxEndY;
490                    }
491    
492                    double rotation = getRotation( boxStartX, boxStartY, x, y );
493                    double[] deviation = calcDeviation( new int[] { boxStartX, boxStartY }, new int[] { boxEndX, boxEndY },
494                                                        eCandidates );
495    
496                    Label label = null;
497    
498                    boxStartX += offx;
499                    boxStartY += offy;
500                    boxEndX += offx;
501                    boxEndY += offy;
502    
503                    double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
504    
505                    switch ( placementType ) {
506                    case LinePlacement.TYPE_ABSOLUTE: {
507                        label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
508                                             symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height,
509                                             rotation, 0.0, 0.5, ( w - width ) / 2, 0, opacity );
510                        break;
511                    }
512                    case LinePlacement.TYPE_ABOVE: {
513                        label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
514                                             symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height,
515                                             rotation, 0.0, 0.5, ( w - width ) / 2, delta + deviation[0], opacity );
516                        break;
517                    }
518                    case LinePlacement.TYPE_BELOW:
519                    case LinePlacement.TYPE_AUTO: {
520                        label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
521                                             symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height,
522                                             rotation, 0.0, 0.5, ( w - width ) / 2, -delta - deviation[1], opacity );
523                        break;
524                    }
525                    case LinePlacement.TYPE_CENTER: {
526                        label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
527                                             symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height,
528                                             rotation, 0.0, 0.5, ( w - width ) / 2, 0.0, opacity );
529                        break;
530                    }
531                    default: {
532                        // nothing?
533                    }
534                    }
535                    labels.add( label );
536                    boxStartX = lastX;
537                    boxStartY = lastY;
538                    eCandidates.clear();
539                } else {
540                    eCandidates.add( new int[] { x, y } );
541                    lastX = x;
542                    lastY = y;
543                    i++;
544                }
545            }
546    
547            // pick lists of boxes on the linestring
548            ArrayList<Label> pick = new ArrayList<Label>( 100 );
549            int n = labels.size();
550            for ( int j = n / 2; j < labels.size(); j += ( gap + 1 ) ) {
551                pick.add( labels.get( j ) );
552            }
553            for ( int j = n / 2 - ( gap + 1 ); j > 0; j -= ( gap + 1 ) ) {
554                pick.add( labels.get( j ) );
555            }
556            return pick;
557        }
558    
559        /**
560         * Calculates the maximum deviation that points on a linestring have to the ideal line between the starting point
561         * and the end point.
562         * <p>
563         * The ideal line is thought to be running from left to right, the left deviation value generally is above the line,
564         * the right value is below.
565         * <p>
566         *
567         * @param start
568         *            starting point of the linestring
569         * @param end
570         *            end point of the linestring
571         * @param points
572         *            points in between
573         * @return maximum deviation
574         */
575        public static double[] calcDeviation( int[] start, int[] end, List<int[]> points ) {
576    
577            // extreme deviation to the left
578            double d1 = 0.0;
579            // extreme deviation to the right
580            double d2 = 0.0;
581            Iterator<int[]> it = points.iterator();
582    
583            // eventually swap start and end point
584            if ( start[0] > end[0] ) {
585                int[] tmp = start;
586                start = end;
587                end = tmp;
588            }
589    
590            if ( start[0] != end[0] ) {
591                // label orientation is not completly vertical
592                if ( start[1] != end[1] ) {
593                    // label orientation is not completly horizontal
594                    while ( it.hasNext() ) {
595                        int[] point = it.next();
596                        double u = ( (double) end[1] - (double) start[1] ) / ( (double) end[0] - (double) start[0] );
597                        double x = ( u * u * start[0] - u * ( (double) start[1] - (double) point[1] ) + point[0] )
598                                   / ( 1.0 + u * u );
599                        double y = ( x - start[0] ) * u + start[1];
600                        double d = getDistance( point, new int[] { (int) ( x + 0.5 ), (int) ( y + 0.5 ) } );
601                        if ( y >= point[1] ) {
602                            // candidate for left extreme value
603                            if ( d > d1 ) {
604                                d1 = d;
605                            }
606                        } else if ( d > d2 ) {
607                            // candidate for right extreme value
608                            d2 = d;
609                        }
610                    }
611                } else {
612                    // label orientation is completly horizontal
613                    while ( it.hasNext() ) {
614                        int[] point = it.next();
615                        double d = point[1] - start[1];
616                        if ( d < 0 ) {
617                            // candidate for left extreme value
618                            if ( -d > d1 ) {
619                                d1 = -d;
620                            }
621                        } else if ( d > d2 ) {
622                            // candidate for left extreme value
623                            d2 = d;
624                        }
625                    }
626                }
627            } else {
628                // label orientation is completly vertical
629                while ( it.hasNext() ) {
630                    int[] point = it.next();
631                    double d = point[0] - start[0];
632                    if ( d < 0 ) {
633                        // candidate for left extreme value
634                        if ( -d > d1 ) {
635                            d1 = -d;
636                        }
637                    } else if ( d > d2 ) {
638                        // candidate for right extreme value
639                        d2 = d;
640                    }
641                }
642            }
643            return new double[] { d1, d2 };
644        }
645    
646        /**
647         * Finds a point on the line between p1 and p2 that has a certain distance from point p0 (provided that there is
648         * such a point).
649         * <p>
650         *
651         * @param p0
652         *            point that is used as reference point for the distance
653         * @param p1
654         *            starting point of the line
655         * @param p2
656         *            end point of the line
657         * @param d
658         *            distance
659         * @return point on the line between p1 and p2 that has a certain distance from point p0
660         */
661        public static int[] findPointWithDistance( int[] p0, int[] p1, int[] p2, int d ) {
662    
663            double x, y;
664            double x0 = p0[0];
665            double y0 = p0[1];
666            double x1 = p1[0];
667            double y1 = p1[1];
668            double x2 = p2[0];
669            double y2 = p2[1];
670    
671            if ( x1 != x2 ) {
672                // line segment does not run vertical
673                double u = ( y2 - y1 ) / ( x2 - x1 );
674                double p = -2 * ( x0 + u * u * x1 - u * ( y1 - y0 ) ) / ( u * u + 1 );
675                double q = ( ( y1 - y0 ) * ( y1 - y0 ) + u * u * x1 * x1 + x0 * x0 - 2 * u * x1 * ( y1 - y0 ) - d * d )
676                           / ( u * u + 1 );
677                double minX = x1;
678                double maxX = x2;
679                double minY = y1;
680                double maxY = y2;
681                if ( minX > maxX ) {
682                    minX = x2;
683                    maxX = x1;
684                }
685                if ( minY > maxY ) {
686                    minY = y2;
687                    maxY = y1;
688                }
689                x = -p / 2 - Math.sqrt( ( p / 2 ) * ( p / 2 ) - q );
690                if ( x < minX || x > maxX ) {
691                    x = -p / 2 + Math.sqrt( ( p / 2 ) * ( p / 2 ) - q );
692                }
693                y = ( x - x1 ) * u + y1;
694            } else {
695                // vertical line segment
696                x = x1;
697                double minY = y1;
698                double maxY = y2;
699    
700                if ( minY > maxY ) {
701                    minY = y2;
702                    maxY = y1;
703                }
704    
705                double p = -2 * y0;
706                double q = y0 * y0 + ( x1 - x0 ) * ( x1 - x0 ) - d * d;
707    
708                y = -p / 2 - Math.sqrt( ( p / 2 ) * ( p / 2 ) - q );
709                if ( y < minY || y > maxY ) {
710                    y = -p / 2 + Math.sqrt( ( p / 2 ) * ( p / 2 ) - q );
711                }
712            }
713            return new int[] { (int) ( x + 0.5 ), (int) ( y + 0.5 ) };
714        }
715    
716        /**
717         * Creates a label that fits in the specified box.
718         *
719         * @param text
720         * @param font
721         * @param color
722         * @param halo
723         * @param x
724         * @param y
725         * @param w
726         * @param h
727         * @param feature
728         * @param projection
729         * @return the label
730         * @deprecated use the one with opacity instead
731         */
732        @Deprecated
733        public static HorizontalLabel createLabelInABox( String text, Font font, Color color, Halo halo, double x,
734                                                         double y, double w, double h, Feature feature,
735                                                         GeoTransform projection ) {
736            return createLabelInABox( text, font, color, halo, x, y, w, h, feature, projection, 1.0 );
737        }
738    
739        /**
740         * @param text
741         * @param font
742         * @param color
743         * @param halo
744         * @param x
745         * @param y
746         * @param w
747         * @param h
748         * @param feature
749         * @param projection
750         * @param opacity
751         * @return the label
752         */
753        public static HorizontalLabel createLabelInABox( String text, Font font, Color color, Halo halo, double x,
754                                                         double y, double w, double h, Feature feature,
755                                                         GeoTransform projection, double opacity ) {
756    
757            // one could probably get a significant speedup if no new fonts would be created (ie, use
758            // another method to find out the text extent of a given string and font size)
759            FontRenderContext frc = new FontRenderContext( null, false, false );
760            font = font.deriveFont( 128f ); // TODO hardcoded max
761            Rectangle2D rect = font.getStringBounds( text, frc );
762            int ix = (int) projection.getDestX( x );
763            int iy = (int) projection.getDestY( y );
764            int iw = abs( ( (int) projection.getDestX( x + w ) ) - ix );
765            int ih = abs( ( (int) projection.getDestY( y + h ) ) - iy );
766            while ( rect.getWidth() > iw && rect.getHeight() > ih && font.getSize2D() > 5 ) {
767                font = font.deriveFont( font.getSize2D() - 4 );
768                rect = font.getStringBounds( text, frc );
769            }
770    
771            LOG.logDebug( "Determined font size from bounding box", font.getSize2D() );
772    
773            return new HorizontalLabel( text, font, color, font.getLineMetrics( text, frc ), feature, halo, ix, iy, iw, ih,
774                                        new double[] { 0, 0 }, new double[] { 0, 0 }, opacity );
775        }
776    
777        /**
778         * @param x1
779         * @param y1
780         * @param x2
781         * @param y2
782         * @return rotation (degree) of the line between two passed coordinates
783         */
784        public static double getRotation( double x1, double y1, double x2, double y2 ) {
785            double dx = x2 - x1;
786            double dy = y2 - y1;
787    
788            return Math.toDegrees( Math.atan( dy / dx ) );
789        }
790    
791        /**
792         * @param p1
793         * @param p2
794         * @return distance between two passed coordinates
795         */
796        public static double getDistance( int[] p1, int[] p2 ) {
797            double dx = p1[0] - p2[0];
798            double dy = p1[1] - p2[1];
799            return Math.sqrt( dx * dx + dy * dy );
800        }
801    
802        /**
803         * @param x1
804         * @param y1
805         * @param x2
806         * @param y2
807         * @return distance between two passed coordinates
808         */
809        public static double getDistance( double x1, double y1, double x2, double y2 ) {
810            double dx = x2 - x1;
811            double dy = y2 - y1;
812            return Math.sqrt( dx * dx + dy * dy );
813        }
814    
815        /**
816         * Calculates the screen coordinates of the given <tt>Curve</tt>. physical screen coordinates
817         *
818         * @param projection
819         * @param curve
820         * @return the coordinates
821         */
822        public static int[][] calcScreenCoordinates( GeoTransform projection, Curve curve ) {
823    
824            LineString lineString = null;
825            try {
826                lineString = curve.getAsLineString();
827            } catch ( GeometryException e ) {
828                // ignored, assumed to be valid
829            }
830    
831            int count = lineString.getNumberOfPoints();
832            int[][] pos = new int[3][];
833            pos[0] = new int[count];
834            pos[1] = new int[count];
835            pos[2] = new int[1];
836    
837            int k = 0;
838            for ( int i = 0; i < count; i++ ) {
839                Position position = lineString.getPositionAt( i );
840                double tx = projection.getDestX( position.getX() );
841                double ty = projection.getDestY( position.getY() );
842                if ( i > 0 ) {
843                    if ( getDistance( tx, ty, pos[0][k - 1], pos[1][k - 1] ) > 1 ) {
844                        pos[0][k] = (int) ( tx + 0.5 );
845                        pos[1][k] = (int) ( ty + 0.5 );
846                        k++;
847                    }
848                } else {
849                    pos[0][k] = (int) ( tx + 0.5 );
850                    pos[1][k] = (int) ( ty + 0.5 );
851                    k++;
852                }
853            }
854            pos[2][0] = k;
855    
856            return pos;
857        }
858    
859        /**
860         * Returns the physical (screen) coordinates.
861         *
862         * @param projection
863         * @param geometry
864         *
865         * @return physical screen coordinates
866         */
867        public static int[] calcScreenCoordinates( GeoTransform projection, Geometry geometry ) {
868    
869            int[] coords = new int[2];
870    
871            Position source = null;
872            if ( geometry instanceof Point ) {
873                source = ( (Point) geometry ).getPosition();
874            } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) {
875                source = geometry.getCentroid().getPosition();
876            } else {
877                source = geometry.getCentroid().getPosition();
878            }
879    
880            coords[0] = (int) ( projection.getDestX( source.getX() ) + 0.5 );
881            coords[1] = (int) ( projection.getDestY( source.getY() ) + 0.5 );
882            return coords;
883        }
884    }