001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/graphics/optimizers/LabelChoiceFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/   
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     ---------------------------------------------------------------------------*/
043    package org.deegree.graphics.optimizers;
044    
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    
051    import org.deegree.framework.log.ILogger;
052    import org.deegree.framework.log.LoggerFactory;
053    import org.deegree.graphics.displayelements.Label;
054    import org.deegree.graphics.displayelements.LabelDisplayElement;
055    import org.deegree.graphics.displayelements.LabelFactory;
056    import org.deegree.graphics.sld.LabelPlacement;
057    import org.deegree.graphics.sld.LinePlacement;
058    import org.deegree.graphics.sld.PointPlacement;
059    import org.deegree.graphics.sld.TextSymbolizer;
060    import org.deegree.graphics.transformation.GeoTransform;
061    import org.deegree.model.feature.Feature;
062    import org.deegree.model.filterencoding.FilterEvaluationException;
063    import org.deegree.model.spatialschema.Curve;
064    import org.deegree.model.spatialschema.Geometry;
065    import org.deegree.model.spatialschema.GeometryFactory;
066    import org.deegree.model.spatialschema.MultiCurve;
067    import org.deegree.model.spatialschema.MultiSurface;
068    import org.deegree.model.spatialschema.Point;
069    import org.deegree.model.spatialschema.Position;
070    import org.deegree.model.spatialschema.Surface;
071    
072    /**
073     * Factory class for <tt>LabelChoice</tt>-objects.
074     * <p>
075     * 
076     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
077     * @version $Revision: 9340 $ $Date: 2007-12-27 13:32:12 +0100 (Do, 27 Dez 2007) $
078     */
079    public class LabelChoiceFactory {
080    
081        private static ILogger LOG = LoggerFactory.getLogger( LabelChoiceFactory.class );
082    
083        /**
084         * Determines <tt>LabelChoice</tt>s for the given <tt>LabelDisplayElement</tt>.
085         * <p>
086         * 
087         * @param element
088         * @param g
089         * @param projection
090         * @return
091         */
092        static ArrayList<LabelChoice> createLabelChoices( LabelDisplayElement element, Graphics2D g, GeoTransform projection ) {
093    
094            ArrayList<LabelChoice> choices = new ArrayList<LabelChoice>();
095    
096            try {
097                Feature feature = element.getFeature();
098                String caption = element.getLabel().evaluate( feature );
099    
100                // sanity check: empty labels are ignored
101                if ( caption == null || caption.trim().equals( "" ) ) {
102                    return choices;
103                }
104    
105                Geometry geometry = element.getGeometry();
106                TextSymbolizer symbolizer = (TextSymbolizer) element.getSymbolizer();
107    
108                // gather font information
109                org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
110                java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature )
111                                                                                      | sldFont.getWeight( feature ),
112                                                        sldFont.getSize( feature ) );
113                g.setFont( font );
114                FontRenderContext frc = g.getFontRenderContext();
115                Rectangle2D bounds = font.getStringBounds( caption, frc );
116                LineMetrics metrics = font.getLineMetrics( caption, frc );
117                int w = (int) bounds.getWidth();
118                int h = (int) bounds.getHeight();
119                // int descent = (int) metrics.getDescent ();
120    
121                LabelPlacement lPlacement = symbolizer.getLabelPlacement();
122    
123                // element is associated to a point geometry
124                if ( geometry instanceof Point ) {
125    
126                    // get screen coordinates
127                    int[] coords = LabelFactory.calcScreenCoordinates( projection, geometry );
128                    int x = coords[0];
129                    int y = coords[1];
130    
131                    // use placement information from SLD
132                    PointPlacement pPlacement = lPlacement.getPointPlacement();
133                    // double [] anchorPoint = pPlacement.getAnchorPoint( feature );
134                    double[] displacement = pPlacement.getDisplacement( feature );
135                    double rotation = pPlacement.getRotation( feature );
136    
137                    Label[] labels = new Label[8];
138                    labels[0] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
139                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.0,
140                                                                                                                    0.0 },
141                                                          new double[] { displacement[0], displacement[1] } );
142                    labels[1] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
143                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.0,
144                                                                                                                    1.0 },
145                                                          new double[] { displacement[0], -displacement[1] } );
146                    labels[2] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
147                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 1.0,
148                                                                                                                    1.0 },
149                                                          new double[] { -displacement[0], -displacement[1] } );
150                    labels[3] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
151                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 1.0,
152                                                                                                                    0.0 },
153                                                          new double[] { -displacement[0], displacement[1] } );
154                    labels[4] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
155                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.0,
156                                                                                                                    0.5 },
157                                                          new double[] { displacement[0], 0 } );
158                    labels[5] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
159                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.5,
160                                                                                                                    1.0 },
161                                                          new double[] { 0, -displacement[1] } );
162                    labels[6] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
163                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 1.0,
164                                                                                                                    0.5 },
165                                                          new double[] { -displacement[0], 0 } );
166                    labels[7] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
167                                                          symbolizer.getHalo(), x, y, w, h, rotation, new double[] { 0.5,
168                                                                                                                    0.0 },
169                                                          new double[] { 0, displacement[1] } );
170                    float[] qualities = new float[] { 0.0f, 0.5f, 0.33f, 0.27f, 0.15f, 1.0f, 0.1f, 0.7f };
171                    choices.add( new LabelChoice( element, labels, qualities, 0, labels[1].getMaxX(), labels[1].getMaxY(),
172                                                  labels[3].getMinX(), labels[3].getMinY() ) );
173    
174                    // element is associated to a polygon geometry
175                } else if ( geometry instanceof Surface || geometry instanceof MultiSurface ) {
176    
177                    // get screen coordinates
178                    int[] coords = LabelFactory.calcScreenCoordinates( projection, geometry );
179                    int x = coords[0];
180                    int y = coords[1];
181    
182                    // use placement information from SLD
183                    PointPlacement pPlacement = lPlacement.getPointPlacement();
184                    // double [] anchorPoint = pPlacement.getAnchorPoint( feature );
185                    // double [] displacement = pPlacement.getDisplacement( feature );
186                    double rotation = pPlacement.getRotation( feature );
187    
188                    // center label within the intersection of the screen surface and the polygon
189                    // geometry
190                    Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null );
191                    Geometry intersection = null;
192    
193                    try {
194                        intersection = screenSurface.intersection( geometry );
195                    } catch ( Exception e ) {
196                        LOG.logDebug( "no intersection could be calculated because objects are to small" );
197                    }
198    
199                    if ( intersection != null && intersection.getCentroid() != null ) {
200                        Position source = intersection.getCentroid().getPosition();
201                        x = (int) ( projection.getDestX( source.getX() ) + 0.5 );
202                        y = (int) ( projection.getDestY( source.getY() ) + 0.5 );
203                        Label[] labels = new Label[3];
204                        labels[0] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
205                                                              symbolizer.getHalo(), x, y, w, h, rotation,
206                                                              new double[] { 0.5, 0.5 }, new double[] { 0, 0 } );
207                        labels[1] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
208                                                              symbolizer.getHalo(), x, y, w, h, rotation,
209                                                              new double[] { 0.5, 0.0 }, new double[] { 0, 0 } );
210                        labels[2] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics, feature,
211                                                              symbolizer.getHalo(), x, y, w, h, rotation,
212                                                              new double[] { 0.5, 1.0 }, new double[] { 0, 0 } );
213    
214                        float[] qualities = new float[] { 0.0f, 0.25f, 0.5f };
215                        choices.add( new LabelChoice( element, labels, qualities, 0, labels[0].getMaxX(),
216                                                      labels[2].getMaxY(), labels[0].getMinX(), labels[1].getMinY() ) );
217                    }
218    
219                    // element is associated to a line geometry
220                } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) {
221    
222                    Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null );
223                    Geometry intersection = screenSurface.intersection( geometry );
224    
225                    if ( intersection != null ) {
226                        ArrayList<LabelChoice> list = null;
227                        if ( intersection instanceof Curve ) {
228                            list = createLabelChoices( (Curve) intersection, element, g, projection );
229                        } else if ( intersection instanceof MultiCurve ) {
230                            list = createLabelChoices( (MultiCurve) intersection, element, g, projection );
231                        } else {
232                            throw new Exception( "Intersection produced unexpected geometry type: '"
233                                                 + intersection.getClass().getName() + "'!" );
234                        }
235                        choices = list;
236                    }
237                }
238            } catch ( Exception e ) {
239                e.printStackTrace();
240            }
241            return choices;
242        }
243    
244        /**
245         * Determines <tt>LabelChoice</tt>s for the given <tt>MultiCurve</tt> where a
246         * <tt>Label</tt> could be drawn. For each <tt>LabelChoice</tt>, three candidates are
247         * generated: one on the line, one above it and one below.
248         * <p>
249         * 
250         * @param element
251         * @param g
252         * @param projection
253         * @return ArrayList containing <tt>LabelChoice</tt>-objects
254         * @throws FilterEvaluationException
255         */
256        static ArrayList<LabelChoice> createLabelChoices( MultiCurve multiCurve, LabelDisplayElement element, Graphics2D g,
257                                                          GeoTransform projection )
258                                throws FilterEvaluationException {
259    
260            ArrayList<LabelChoice> choices = new ArrayList<LabelChoice>( 1000 );
261            for ( int i = 0; i < multiCurve.getSize(); i++ ) {
262                Curve curve = multiCurve.getCurveAt( i );
263                choices.addAll( createLabelChoices( curve, element, g, projection ) );
264            }
265            return choices;
266        }
267    
268        /**
269         * Determines <tt>LabelChoice</tt>s for the given <tt>Curve</tt> where a <tt>Label</tt>
270         * could be drawn. For each <tt>LabelChoice</tt>, three candidates are generated: one on the
271         * line, one above it and one below.
272         * <p>
273         * 
274         * @param curve
275         * @param element
276         * @param g
277         * @param projection
278         * @return ArrayList containing <tt>LabelChoice</tt>-objects
279         * @throws FilterEvaluationException
280         */
281        static ArrayList<LabelChoice> createLabelChoices( Curve curve, LabelDisplayElement element, Graphics2D g,
282                                                          GeoTransform projection )
283                                throws FilterEvaluationException {
284    
285            Feature feature = element.getFeature();
286    
287            // determine the placement type and parameters from the TextSymbolizer
288            double perpendicularOffset = 0.0;
289            int placementType = LinePlacement.TYPE_ABSOLUTE;
290            double lineWidth = 3.0;
291            int gap = 6;
292            TextSymbolizer symbolizer = ( (TextSymbolizer) element.getSymbolizer() );
293            if ( symbolizer.getLabelPlacement() != null ) {
294                LinePlacement linePlacement = symbolizer.getLabelPlacement().getLinePlacement();
295                if ( linePlacement != null ) {
296                    placementType = linePlacement.getPlacementType( element.getFeature() );
297                    perpendicularOffset = linePlacement.getPerpendicularOffset( element.getFeature() );
298                    lineWidth = linePlacement.getLineWidth( element.getFeature() );
299                    gap = linePlacement.getGap( element.getFeature() );
300                }
301            }
302    
303            // get width & height of the caption
304            String caption = element.getLabel().evaluate( element.getFeature() );
305            org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
306            java.awt.Font font = new java.awt.Font( sldFont.getFamily( element.getFeature() ),
307                                                    sldFont.getStyle( element.getFeature() )
308                                                                            | sldFont.getWeight( element.getFeature() ),
309                                                    sldFont.getSize( element.getFeature() ) );
310            g.setFont( font );
311            FontRenderContext frc = g.getFontRenderContext();
312            Rectangle2D bounds = font.getStringBounds( caption, frc );
313            LineMetrics metrics = font.getLineMetrics( caption, frc );
314            double width = bounds.getWidth();
315            double height = bounds.getHeight();
316    
317            // get screen coordinates of the line
318            int[][] pos = LabelFactory.calcScreenCoordinates( projection, curve );
319    
320            // ideal distance from the line
321            double delta = height / 2.0 + lineWidth / 2.0;
322    
323            // walk along the linestring and "collect" possible label positions
324            int w = (int) width;
325            int lastX = pos[0][0];
326            int lastY = pos[1][0];
327            int count = pos[2][0];
328            int boxStartX = lastX;
329            int boxStartY = lastY;
330    
331            ArrayList<LabelChoice> choices = new ArrayList<LabelChoice>( 1000 );
332            ArrayList<int[]> eCandidates = new ArrayList<int[]>( 100 );
333            int i = 0;
334            int kk = 0;
335            while ( i < count && kk < 100 ) {
336                kk++;
337                int x = pos[0][i];
338                int y = pos[1][i];
339    
340                // segment found where endpoint of label should be located?
341                if ( LabelFactory.getDistance( boxStartX, boxStartY, x, y ) >= w ) {
342    
343                    int[] p0 = new int[] { boxStartX, boxStartY };
344                    int[] p1 = new int[] { lastX, lastY };
345                    int[] p2 = new int[] { x, y };
346    
347                    int[] p = LabelFactory.findPointWithDistance( p0, p1, p2, w );
348                    x = p[0];
349                    y = p[1];
350    
351                    lastX = x;
352                    lastY = y;
353                    int boxEndX = x;
354                    int boxEndY = y;
355    
356                    // does the linesegment run from right to left?
357                    if ( x <= boxStartX ) {
358                        boxEndX = boxStartX;
359                        boxEndY = boxStartY;
360                        boxStartX = x;
361                        boxStartY = y;
362                        x = boxEndX;
363                        y = boxEndY;
364                    }
365    
366                    double rotation = LabelFactory.getRotation( boxStartX, boxStartY, x, y );
367                    double[] deviation = LabelFactory.calcDeviation( new int[] { boxStartX, boxStartY },
368                                                                     new int[] { boxEndX, boxEndY }, eCandidates );
369    
370                    switch ( placementType ) {
371                    case LinePlacement.TYPE_ABSOLUTE: {
372                        Label label = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
373                                                                feature, symbolizer.getHalo(), boxStartX, boxStartY,
374                                                                (int) width, (int) height, rotation, new double[] { 0.0,
375                                                                                                                   0.5 },
376                                                                new double[] { ( w - width ) / 2, perpendicularOffset } );
377                        choices.add( new LabelChoice( element, new Label[] { label }, new float[] { 0.0f }, 0,
378                                                      label.getMaxX(), label.getMaxY(), label.getMinX(), label.getMinY() ) );
379                        break;
380                    }
381                    case LinePlacement.TYPE_ABOVE: {
382                        Label upperLabel = LabelFactory.createLabel(
383                                                                     caption,
384                                                                     font,
385                                                                     sldFont.getColor( feature ),
386                                                                     metrics,
387                                                                     feature,
388                                                                     symbolizer.getHalo(),
389                                                                     boxStartX,
390                                                                     boxStartY,
391                                                                     (int) width,
392                                                                     (int) height,
393                                                                     rotation,
394                                                                     new double[] { 0.0, 0.5 },
395                                                                     new double[] { ( w - width ) / 2, delta + deviation[0] } );
396                        choices.add( new LabelChoice( element, new Label[] { upperLabel }, new float[] { 0.0f }, 0,
397                                                      upperLabel.getMaxX(), upperLabel.getMaxY(), upperLabel.getMinX(),
398                                                      upperLabel.getMinY() ) );
399                        break;
400                    }
401                    case LinePlacement.TYPE_BELOW: {
402                        Label lowerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
403                                                                     feature, symbolizer.getHalo(), boxStartX, boxStartY,
404                                                                     (int) width, (int) height, rotation,
405                                                                     new double[] { 0.0, 0.5 },
406                                                                     new double[] { ( w - width ) / 2,
407                                                                                   -delta - deviation[1] } );
408                        choices.add( new LabelChoice( element, new Label[] { lowerLabel }, new float[] { 0.0f }, 0,
409                                                      lowerLabel.getMaxX(), lowerLabel.getMaxY(), lowerLabel.getMinX(),
410                                                      lowerLabel.getMinY() ) );
411                        break;
412                    }
413                    case LinePlacement.TYPE_CENTER: {
414                        Label centerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
415                                                                      feature, symbolizer.getHalo(), boxStartX, boxStartY,
416                                                                      (int) width, (int) height, rotation,
417                                                                      new double[] { 0.0, 0.5 },
418                                                                      new double[] { ( w - width ) / 2, 0.0 } );
419                        choices.add( new LabelChoice( element, new Label[] { centerLabel }, new float[] { 0.0f }, 0,
420                                                      centerLabel.getMaxX(), centerLabel.getMaxY(), centerLabel.getMinX(),
421                                                      centerLabel.getMinY() ) );
422                        break;
423                    }
424                    case LinePlacement.TYPE_AUTO: {
425                        Label upperLabel = LabelFactory.createLabel(
426                                                                     caption,
427                                                                     font,
428                                                                     sldFont.getColor( feature ),
429                                                                     metrics,
430                                                                     feature,
431                                                                     symbolizer.getHalo(),
432                                                                     boxStartX,
433                                                                     boxStartY,
434                                                                     (int) width,
435                                                                     (int) height,
436                                                                     rotation,
437                                                                     new double[] { 0.0, 0.5 },
438                                                                     new double[] { ( w - width ) / 2, delta + deviation[0] } );
439                        Label lowerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
440                                                                     feature, symbolizer.getHalo(), boxStartX, boxStartY,
441                                                                     (int) width, (int) height, rotation,
442                                                                     new double[] { 0.0, 0.5 },
443                                                                     new double[] { ( w - width ) / 2,
444                                                                                   -delta - deviation[1] } );
445                        Label centerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
446                                                                      feature, symbolizer.getHalo(), boxStartX, boxStartY,
447                                                                      (int) width, (int) height, rotation,
448                                                                      new double[] { 0.0, 0.5 },
449                                                                      new double[] { ( w - width ) / 2, 0.0 } );
450                        choices.add( new LabelChoice( element, new Label[] { lowerLabel, upperLabel, centerLabel },
451                                                      new float[] { 0.0f, 0.25f, 1.0f }, 0, centerLabel.getMaxX(),
452                                                      lowerLabel.getMaxY(), centerLabel.getMinX(), upperLabel.getMinY() ) );
453                        break;
454                    }
455                    default: {
456                    }
457                    }
458    
459                    boxStartX = lastX;
460                    boxStartY = lastY;
461                    eCandidates.clear();
462                } else {
463                    eCandidates.add( new int[] { x, y } );
464                    lastX = x;
465                    lastY = y;
466                    i++;
467                }
468            }
469    
470            // pick LabelChoices on the linestring
471            ArrayList<LabelChoice> pick = new ArrayList<LabelChoice>( choices.size() );
472            int n = choices.size();
473            for ( int j = n / 2; j < choices.size(); j += ( gap + 1 ) ) {
474                pick.add( choices.get( j ) );
475            }
476            for ( int j = n / 2 - ( gap + 1 ); j > 0; j -= ( gap + 1 ) ) {
477                pick.add( choices.get( j ) );
478            }
479            return pick;
480        }
481    }