001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/graphics/optimizers/LabelChoiceFactory.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.optimizers;
037    
038    import java.awt.Graphics2D;
039    import java.awt.font.FontRenderContext;
040    import java.awt.font.LineMetrics;
041    import java.awt.geom.Rectangle2D;
042    import java.util.ArrayList;
043    
044    import org.deegree.framework.log.ILogger;
045    import org.deegree.framework.log.LoggerFactory;
046    import org.deegree.graphics.displayelements.Label;
047    import org.deegree.graphics.displayelements.LabelDisplayElement;
048    import org.deegree.graphics.displayelements.LabelFactory;
049    import org.deegree.graphics.sld.LabelPlacement;
050    import org.deegree.graphics.sld.LinePlacement;
051    import org.deegree.graphics.sld.PointPlacement;
052    import org.deegree.graphics.sld.TextSymbolizer;
053    import org.deegree.graphics.transformation.GeoTransform;
054    import org.deegree.model.feature.Feature;
055    import org.deegree.model.filterencoding.FilterEvaluationException;
056    import org.deegree.model.spatialschema.Curve;
057    import org.deegree.model.spatialschema.Geometry;
058    import org.deegree.model.spatialschema.GeometryFactory;
059    import org.deegree.model.spatialschema.MultiCurve;
060    import org.deegree.model.spatialschema.MultiSurface;
061    import org.deegree.model.spatialschema.Point;
062    import org.deegree.model.spatialschema.Position;
063    import org.deegree.model.spatialschema.Surface;
064    
065    /**
066     * Factory for {@link LabelChoice} objects.
067     * 
068     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
069     * @author last edited by: $Author: aschmitz $
070     * 
071     * @version $Revision: 19328 $, $Date: 2009-08-26 09:07:46 +0200 (Mi, 26. Aug 2009) $
072     */
073    public class LabelChoiceFactory {
074    
075        private static ILogger LOG = LoggerFactory.getLogger( LabelChoiceFactory.class );
076    
077        /**
078         * Determines {@link LabelChoice} candidates for the given {@link LabelDisplayElement}.
079         * 
080         * @param element
081         * @param g
082         * @param projection
083         * @return possible {@link LabelChoice}s
084         */
085        static ArrayList<LabelChoice> createLabelChoices( LabelDisplayElement element, Graphics2D g, GeoTransform projection ) {
086    
087            ArrayList<LabelChoice> choices = new ArrayList<LabelChoice>();
088    
089            try {
090                Feature feature = element.getFeature();
091                String caption = element.getLabel().evaluate( feature );
092    
093                // sanity check: empty labels are ignored
094                if ( caption == null || caption.trim().equals( "" ) ) {
095                    return choices;
096                }
097    
098                Geometry geometry = element.getGeometry();
099                TextSymbolizer symbolizer = (TextSymbolizer) element.getSymbolizer();
100    
101                // gather font information
102                org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
103                java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature )
104                                                                                      | sldFont.getWeight( feature ),
105                                                        sldFont.getSize( feature ) );
106                g.setFont( font );
107                FontRenderContext frc = g.getFontRenderContext();
108                Rectangle2D bounds = font.getStringBounds( caption, frc );
109                LineMetrics metrics = font.getLineMetrics( caption, frc );
110                int w = (int) bounds.getWidth();
111                int h = (int) bounds.getHeight();
112                // int descent = (int) metrics.getDescent ();
113    
114                LabelPlacement lPlacement = symbolizer.getLabelPlacement();
115    
116                // element is associated to a point geometry
117                if ( geometry instanceof Point ) {
118    
119                    // get screen coordinates
120                    int[] coords = LabelFactory.calcScreenCoordinates( projection, geometry );
121                    int x = coords[0];
122                    int y = coords[1];
123    
124                    // use placement information from SLD
125                    PointPlacement pPlacement = lPlacement.getPointPlacement();
126                    // double [] anchorPoint = pPlacement.getAnchorPoint( feature );
127                    double[] displacement = pPlacement.getDisplacement( feature );
128                    double rotation = pPlacement.getRotation( feature );
129    
130                    Label[] labels = new Label[8];
131                    double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
132                    labels[0] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
133                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.0,
134                                                                                                                    0.0 },
135                                                          new double[] { displacement[0], displacement[1] }, opacity );
136                    labels[1] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
137                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.0,
138                                                                                                                    1.0 },
139                                                          new double[] { displacement[0], -displacement[1] }, opacity );
140                    labels[2] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
141                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 1.0,
142                                                                                                                    1.0 },
143                                                          new double[] { -displacement[0], -displacement[1] }, opacity );
144                    labels[3] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
145                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 1.0,
146                                                                                                                    0.0 },
147                                                          new double[] { -displacement[0], displacement[1] }, opacity );
148                    labels[4] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
149                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.0,
150                                                                                                                    0.5 },
151                                                          new double[] { displacement[0], 0 }, opacity );
152                    labels[5] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
153                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.5,
154                                                                                                                    1.0 },
155                                                          new double[] { 0, -displacement[1] }, opacity );
156                    labels[6] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
157                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 1.0,
158                                                                                                                    0.5 },
159                                                          new double[] { -displacement[0], 0 }, opacity );
160                    labels[7] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
161                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.5,
162                                                                                                                    0.0 },
163                                                          new double[] { 0, displacement[1] }, opacity );
164                    float[] qualities = new float[] { 0.0f, 0.5f, 0.33f, 0.27f, 0.15f, 1.0f, 0.1f, 0.7f };
165                    choices.add( new LabelChoice( element, labels, qualities, 0, labels[1].getMaxX(), labels[1].getMaxY(),
166                                                  labels[3].getMinX(), labels[3].getMinY() ) );
167    
168                    // element is associated to a polygon geometry
169                } else if ( geometry instanceof Surface || geometry instanceof MultiSurface ) {
170    
171                    // get screen coordinates
172                    int[] coords = LabelFactory.calcScreenCoordinates( projection, geometry );
173                    int x = coords[0];
174                    int y = coords[1];
175    
176                    // use placement information from SLD
177                    PointPlacement pPlacement = lPlacement.getPointPlacement();
178                    // double [] anchorPoint = pPlacement.getAnchorPoint( feature );
179                    // double [] displacement = pPlacement.getDisplacement( feature );
180                    double rotation = pPlacement.getRotation( feature );
181    
182                    // center label within the intersection of the screen surface and the polygon
183                    // geometry
184                    Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null );
185                    Geometry intersection = null;
186    
187                    try {
188                        intersection = screenSurface.intersection( geometry );
189                    } catch ( Exception e ) {
190                        LOG.logDebug( "no intersection could be calculated because objects are to small" );
191                    }
192    
193                    if ( intersection != null && intersection.getCentroid() != null ) {
194                        Position source = intersection.getCentroid().getPosition();
195                        x = (int) ( projection.getDestX( source.getX() ) + 0.5 );
196                        y = (int) ( projection.getDestY( source.getY() ) + 0.5 );
197                        Label[] labels = new Label[3];
198                        double opacity = 1;
199                        if ( symbolizer.getFill() != null ) {
200                            opacity = symbolizer.getFill().getOpacity( feature );
201                        }
202                        labels[0] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
203                                                              symbolizer.getHalo(), x, y, w, h, rotation,
204                                                              new double[] { 0.5, 0.5 }, new double[] { 0, 0 }, opacity );
205                        labels[1] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
206                                                              symbolizer.getHalo(), x, y, w, h, rotation,
207                                                              new double[] { 0.5, 0.0 }, new double[] { 0, 0 }, opacity );
208                        labels[2] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
209                                                              symbolizer.getHalo(), x, y, w, h, rotation,
210                                                              new double[] { 0.5, 1.0 }, new double[] { 0, 0 }, opacity );
211    
212                        float[] qualities = new float[] { 0.0f, 0.25f, 0.5f };
213                        choices.add( new LabelChoice( element, labels, qualities, 0, labels[0].getMaxX(),
214                                                      labels[2].getMaxY(), labels[0].getMinX(), labels[1].getMinY() ) );
215                    }
216    
217                    // element is associated to a line geometry
218                } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) {
219    
220                    Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null );
221                    Geometry intersection = screenSurface.intersection( geometry );
222    
223                    if ( intersection != null ) {
224                        ArrayList<LabelChoice> list = null;
225                        if ( intersection instanceof Curve ) {
226                            list = createLabelChoices( (Curve) intersection, element, g, projection );
227                        } else if ( intersection instanceof MultiCurve ) {
228                            list = createLabelChoices( (MultiCurve) intersection, element, g, projection );
229                        } else {
230                            throw new Exception( "Intersection produced unexpected geometry type: '"
231                                                 + intersection.getClass().getName() + "'!" );
232                        }
233                        choices = list;
234                    }
235                }
236            } catch ( Exception e ) {
237                e.printStackTrace();
238            }
239            return choices;
240        }
241    
242        /**
243         * Determines {@link LabelChoice} candidates for the given {@link MultiCurve} where a label could be drawn.
244         * <p>
245         * Three candidates are generated:
246         * <ul>
247         * <li>on the line</li>
248         * <li>above it</li>
249         * <li>below</li>
250         * </ul>
251         * 
252         * @param multiCurve
253         * @param element
254         * @param g
255         * @param projection
256         * @return ArrayList containing <tt>LabelChoice</tt>-objects
257         * @throws FilterEvaluationException
258         */
259        static ArrayList<LabelChoice> createLabelChoices( MultiCurve multiCurve, LabelDisplayElement element, Graphics2D g,
260                                                          GeoTransform projection )
261                                throws FilterEvaluationException {
262    
263            ArrayList<LabelChoice> choices = new ArrayList<LabelChoice>( 1000 );
264            for ( int i = 0; i < multiCurve.getSize(); i++ ) {
265                Curve curve = multiCurve.getCurveAt( i );
266                choices.addAll( createLabelChoices( curve, element, g, projection ) );
267            }
268            return choices;
269        }
270    
271        /**
272         * Determines <code>LabelChoice</code>s for the given <code>Curve</code> where a <code>Label</code> could be drawn.
273         * <p>
274         * Three candidates are generated:
275         * <ul>
276         * <li>on the line</li>
277         * <li>above it</li>
278         * <li>below</li>
279         * </ul>
280         * </li>
281         * 
282         * @param curve
283         * @param element
284         * @param g
285         * @param projection
286         * @return ArrayList containing <tt>LabelChoice</tt>-objects
287         * @throws FilterEvaluationException
288         */
289        static ArrayList<LabelChoice> createLabelChoices( Curve curve, LabelDisplayElement element, Graphics2D g,
290                                                          GeoTransform projection )
291                                throws FilterEvaluationException {
292    
293            Feature feature = element.getFeature();
294    
295            // determine the placement type and parameters from the TextSymbolizer
296            double perpendicularOffset = 0.0;
297            int placementType = LinePlacement.TYPE_ABSOLUTE;
298            double lineWidth = 3.0;
299            int gap = 6;
300            TextSymbolizer symbolizer = ( (TextSymbolizer) element.getSymbolizer() );
301            if ( symbolizer.getLabelPlacement() != null ) {
302                LinePlacement linePlacement = symbolizer.getLabelPlacement().getLinePlacement();
303                if ( linePlacement != null ) {
304                    placementType = linePlacement.getPlacementType( element.getFeature() );
305                    perpendicularOffset = linePlacement.getPerpendicularOffset( element.getFeature() );
306                    lineWidth = linePlacement.getLineWidth( element.getFeature() );
307                    gap = linePlacement.getGap( element.getFeature() );
308                }
309            }
310    
311            // get width & height of the caption
312            String caption = element.getLabel().evaluate( element.getFeature() );
313            org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
314            java.awt.Font font = new java.awt.Font( sldFont.getFamily( element.getFeature() ),
315                                                    sldFont.getStyle( element.getFeature() )
316                                                                            | sldFont.getWeight( element.getFeature() ),
317                                                    sldFont.getSize( element.getFeature() ) );
318            g.setFont( font );
319            FontRenderContext frc = g.getFontRenderContext();
320            Rectangle2D bounds = font.getStringBounds( caption, frc );
321            LineMetrics metrics = font.getLineMetrics( caption, frc );
322            double width = bounds.getWidth();
323            double height = bounds.getHeight();
324    
325            // get screen coordinates of the line
326            int[][] pos = LabelFactory.calcScreenCoordinates( projection, curve );
327    
328            // ideal distance from the line
329            double delta = height / 2.0 + lineWidth / 2.0;
330    
331            // walk along the linestring and "collect" possible label positions
332            int w = (int) width;
333            int lastX = pos[0][0];
334            int lastY = pos[1][0];
335            int count = pos[2][0];
336            int boxStartX = lastX;
337            int boxStartY = lastY;
338    
339            ArrayList<LabelChoice> choices = new ArrayList<LabelChoice>( 1000 );
340            ArrayList<int[]> eCandidates = new ArrayList<int[]>( 100 );
341            int i = 0;
342            int kk = 0;
343            while ( i < count && kk < 100 ) {
344                kk++;
345                int x = pos[0][i];
346                int y = pos[1][i];
347    
348                // segment found where endpoint of label should be located?
349                if ( LabelFactory.getDistance( boxStartX, boxStartY, x, y ) >= w ) {
350    
351                    int[] p0 = new int[] { boxStartX, boxStartY };
352                    int[] p1 = new int[] { lastX, lastY };
353                    int[] p2 = new int[] { x, y };
354    
355                    int[] p = LabelFactory.findPointWithDistance( p0, p1, p2, w );
356                    x = p[0];
357                    y = p[1];
358    
359                    lastX = x;
360                    lastY = y;
361                    int boxEndX = x;
362                    int boxEndY = y;
363    
364                    // does the linesegment run from right to left?
365                    if ( x <= boxStartX ) {
366                        boxEndX = boxStartX;
367                        boxEndY = boxStartY;
368                        boxStartX = x;
369                        boxStartY = y;
370                        x = boxEndX;
371                        y = boxEndY;
372                    }
373    
374                    double rotation = LabelFactory.getRotation( boxStartX, boxStartY, x, y );
375                    double[] deviation = LabelFactory.calcDeviation( new int[] { boxStartX, boxStartY },
376                                                                     new int[] { boxEndX, boxEndY }, eCandidates );
377    
378                    switch ( placementType ) {
379                    case LinePlacement.TYPE_ABSOLUTE: {
380                        double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
381                        Label label = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
382                                                                feature, symbolizer.getHalo(), boxStartX, boxStartY,
383                                                                (int) width, (int) height, rotation, new double[] { 0.0,
384                                                                                                                   0.5 },
385                                                                new double[] { ( w - width ) / 2, perpendicularOffset },
386                                                                opacity );
387                        choices.add( new LabelChoice( element, new Label[] { label }, new float[] { 0.0f }, 0,
388                                                      label.getMaxX(), label.getMaxY(), label.getMinX(), label.getMinY() ) );
389                        break;
390                    }
391                    case LinePlacement.TYPE_ABOVE: {
392                        double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
393                        Label upperLabel = LabelFactory.createLabel(
394                                                                     caption,
395                                                                     font,
396                                                                     sldFont.getColor( feature ),
397                                                                     metrics,
398                                                                     feature,
399                                                                     symbolizer.getHalo(),
400                                                                     boxStartX,
401                                                                     boxStartY,
402                                                                     (int) width,
403                                                                     (int) height,
404                                                                     rotation,
405                                                                     new double[] { 0.0, 0.5 },
406                                                                     new double[] { ( w - width ) / 2, delta + deviation[0] },
407                                                                     opacity );
408                        choices.add( new LabelChoice( element, new Label[] { upperLabel }, new float[] { 0.0f }, 0,
409                                                      upperLabel.getMaxX(), upperLabel.getMaxY(), upperLabel.getMinX(),
410                                                      upperLabel.getMinY() ) );
411                        break;
412                    }
413                    case LinePlacement.TYPE_BELOW: {
414                        double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
415                        Label lowerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
416                                                                     feature, symbolizer.getHalo(), boxStartX, boxStartY,
417                                                                     (int) width, (int) height, rotation,
418                                                                     new double[] { 0.0, 0.5 },
419                                                                     new double[] { ( w - width ) / 2,
420                                                                                   -delta - deviation[1] }, opacity );
421                        choices.add( new LabelChoice( element, new Label[] { lowerLabel }, new float[] { 0.0f }, 0,
422                                                      lowerLabel.getMaxX(), lowerLabel.getMaxY(), lowerLabel.getMinX(),
423                                                      lowerLabel.getMinY() ) );
424                        break;
425                    }
426                    case LinePlacement.TYPE_CENTER: {
427                        double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
428                        Label centerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
429                                                                      feature, symbolizer.getHalo(), boxStartX, boxStartY,
430                                                                      (int) width, (int) height, rotation,
431                                                                      new double[] { 0.0, 0.5 },
432                                                                      new double[] { ( w - width ) / 2, 0.0 }, opacity );
433                        choices.add( new LabelChoice( element, new Label[] { centerLabel }, new float[] { 0.0f }, 0,
434                                                      centerLabel.getMaxX(), centerLabel.getMaxY(), centerLabel.getMinX(),
435                                                      centerLabel.getMinY() ) );
436                        break;
437                    }
438                    case LinePlacement.TYPE_AUTO: {
439                        double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature );
440                        Label upperLabel = LabelFactory.createLabel(
441                                                                     caption,
442                                                                     font,
443                                                                     sldFont.getColor( feature ),
444                                                                     metrics,
445                                                                     feature,
446                                                                     symbolizer.getHalo(),
447                                                                     boxStartX,
448                                                                     boxStartY,
449                                                                     (int) width,
450                                                                     (int) height,
451                                                                     rotation,
452                                                                     new double[] { 0.0, 0.5 },
453                                                                     new double[] { ( w - width ) / 2, delta + deviation[0] },
454                                                                     opacity );
455                        Label lowerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
456                                                                     feature, symbolizer.getHalo(), boxStartX, boxStartY,
457                                                                     (int) width, (int) height, rotation,
458                                                                     new double[] { 0.0, 0.5 },
459                                                                     new double[] { ( w - width ) / 2,
460                                                                                   -delta - deviation[1] }, opacity );
461                        Label centerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
462                                                                      feature, symbolizer.getHalo(), boxStartX, boxStartY,
463                                                                      (int) width, (int) height, rotation,
464                                                                      new double[] { 0.0, 0.5 },
465                                                                      new double[] { ( w - width ) / 2, 0.0 }, opacity );
466                        choices.add( new LabelChoice( element, new Label[] { lowerLabel, upperLabel, centerLabel },
467                                                      new float[] { 0.0f, 0.25f, 1.0f }, 0, centerLabel.getMaxX(),
468                                                      lowerLabel.getMaxY(), centerLabel.getMinX(), upperLabel.getMinY() ) );
469                        break;
470                    }
471                    default: {
472                        assert false;
473                    }
474                    }
475    
476                    boxStartX = lastX;
477                    boxStartY = lastY;
478                    eCandidates.clear();
479                } else {
480                    eCandidates.add( new int[] { x, y } );
481                    lastX = x;
482                    lastY = y;
483                    i++;
484                }
485            }
486    
487            // pick LabelChoices on the linestring
488            ArrayList<LabelChoice> pick = new ArrayList<LabelChoice>( choices.size() );
489            int n = choices.size();
490            for ( int j = n / 2; j < choices.size(); j += ( gap + 1 ) ) {
491                pick.add( choices.get( j ) );
492            }
493            for ( int j = n / 2 - ( gap + 1 ); j > 0; j -= ( gap + 1 ) ) {
494                pick.add( choices.get( j ) );
495            }
496            return pick;
497        }
498    }