001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/graphics/optimizers/LabelChoiceFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 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: 6796 $ $Date: 2007-05-03 17:16:23 +0200 (Do, 03 Mai 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, GeoTransform projection )
282                                throws FilterEvaluationException {
283    
284            Feature feature = element.getFeature();
285    
286            // determine the placement type and parameters from the TextSymbolizer
287            double perpendicularOffset = 0.0;
288            int placementType = LinePlacement.TYPE_ABSOLUTE;
289            double lineWidth = 3.0;
290            int gap = 6;
291            TextSymbolizer symbolizer = ( (TextSymbolizer) element.getSymbolizer() );
292            if ( symbolizer.getLabelPlacement() != null ) {
293                LinePlacement linePlacement = symbolizer.getLabelPlacement().getLinePlacement();
294                if ( linePlacement != null ) {
295                    placementType = linePlacement.getPlacementType( element.getFeature() );
296                    perpendicularOffset = linePlacement.getPerpendicularOffset( element.getFeature() );
297                    lineWidth = linePlacement.getLineWidth( element.getFeature() );
298                    gap = linePlacement.getGap( element.getFeature() );
299                }
300            }
301    
302            // get width & height of the caption
303            String caption = element.getLabel().evaluate( element.getFeature() );
304            org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
305            java.awt.Font font = new java.awt.Font( sldFont.getFamily( element.getFeature() ),
306                                                    sldFont.getStyle( element.getFeature() )
307                                                                            | sldFont.getWeight( element.getFeature() ),
308                                                    sldFont.getSize( element.getFeature() ) );
309            g.setFont( font );
310            FontRenderContext frc = g.getFontRenderContext();
311            Rectangle2D bounds = font.getStringBounds( caption, frc );
312            LineMetrics metrics = font.getLineMetrics( caption, frc );
313            double width = bounds.getWidth();
314            double height = bounds.getHeight();
315    
316            // get screen coordinates of the line
317            int[][] pos = LabelFactory.calcScreenCoordinates( projection, curve );
318    
319            // ideal distance from the line
320            double delta = height / 2.0 + lineWidth / 2.0;
321    
322            // walk along the linestring and "collect" possible label positions
323            int w = (int) width;
324            int lastX = pos[0][0];
325            int lastY = pos[1][0];
326            int count = pos[2][0];
327            int boxStartX = lastX;
328            int boxStartY = lastY;
329    
330            ArrayList<LabelChoice> choices = new ArrayList<LabelChoice>( 1000 );
331            ArrayList<int[]> eCandidates = new ArrayList<int[]>( 100 );
332            int i = 0;
333            int kk = 0;
334            while ( i < count && kk < 100 ) {
335                kk++;
336                int x = pos[0][i];
337                int y = pos[1][i];
338    
339                // segment found where endpoint of label should be located?
340                if ( LabelFactory.getDistance( boxStartX, boxStartY, x, y ) >= w ) {
341    
342                    int[] p0 = new int[] { boxStartX, boxStartY };
343                    int[] p1 = new int[] { lastX, lastY };
344                    int[] p2 = new int[] { x, y };
345    
346                    int[] p = LabelFactory.findPointWithDistance( p0, p1, p2, w );
347                    x = p[0];
348                    y = p[1];
349    
350                    lastX = x;
351                    lastY = y;
352                    int boxEndX = x;
353                    int boxEndY = y;
354    
355                    // does the linesegment run from right to left?
356                    if ( x <= boxStartX ) {
357                        boxEndX = boxStartX;
358                        boxEndY = boxStartY;
359                        boxStartX = x;
360                        boxStartY = y;
361                        x = boxEndX;
362                        y = boxEndY;
363                    }
364    
365                    double rotation = LabelFactory.getRotation( boxStartX, boxStartY, x, y );
366                    double[] deviation = LabelFactory.calcDeviation( new int[] { boxStartX, boxStartY },
367                                                                     new int[] { boxEndX, boxEndY }, eCandidates );
368    
369                    switch ( placementType ) {
370                    case LinePlacement.TYPE_ABSOLUTE: {
371                        Label label = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
372                                                                feature, symbolizer.getHalo(), boxStartX, boxStartY,
373                                                                (int) width, (int) height, rotation, new double[] { 0.0,
374                                                                                                                   0.5 },
375                                                                new double[] { ( w - width ) / 2, perpendicularOffset } );
376                        choices.add( new LabelChoice( element, new Label[] { label }, new float[] { 0.0f }, 0,
377                                                      label.getMaxX(), label.getMaxY(), label.getMinX(), label.getMinY() ) );
378                        break;
379                    }
380                    case LinePlacement.TYPE_ABOVE: {
381                        Label upperLabel = LabelFactory.createLabel(
382                                                                     caption,
383                                                                     font,
384                                                                     sldFont.getColor( feature ),
385                                                                     metrics,
386                                                                     feature,
387                                                                     symbolizer.getHalo(),
388                                                                     boxStartX,
389                                                                     boxStartY,
390                                                                     (int) width,
391                                                                     (int) height,
392                                                                     rotation,
393                                                                     new double[] { 0.0, 0.5 },
394                                                                     new double[] { ( w - width ) / 2, delta + deviation[0] } );
395                        choices.add( new LabelChoice( element, new Label[] { upperLabel }, new float[] { 0.0f }, 0,
396                                                      upperLabel.getMaxX(), upperLabel.getMaxY(), upperLabel.getMinX(),
397                                                      upperLabel.getMinY() ) );
398                        break;
399                    }
400                    case LinePlacement.TYPE_BELOW: {
401                        Label lowerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
402                                                                     feature, symbolizer.getHalo(), boxStartX, boxStartY,
403                                                                     (int) width, (int) height, rotation,
404                                                                     new double[] { 0.0, 0.5 },
405                                                                     new double[] { ( w - width ) / 2,
406                                                                                   -delta - deviation[1] } );
407                        choices.add( new LabelChoice( element, new Label[] { lowerLabel }, new float[] { 0.0f }, 0,
408                                                      lowerLabel.getMaxX(), lowerLabel.getMaxY(), lowerLabel.getMinX(),
409                                                      lowerLabel.getMinY() ) );
410                        break;
411                    }
412                    case LinePlacement.TYPE_CENTER: {
413                        Label centerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
414                                                                      feature, symbolizer.getHalo(), boxStartX, boxStartY,
415                                                                      (int) width, (int) height, rotation,
416                                                                      new double[] { 0.0, 0.5 },
417                                                                      new double[] { ( w - width ) / 2, 0.0 } );
418                        choices.add( new LabelChoice( element, new Label[] { centerLabel }, new float[] { 0.0f }, 0,
419                                                      centerLabel.getMaxX(), centerLabel.getMaxY(), centerLabel.getMinX(),
420                                                      centerLabel.getMinY() ) );
421                        break;
422                    }
423                    case LinePlacement.TYPE_AUTO: {
424                        Label upperLabel = LabelFactory.createLabel(
425                                                                     caption,
426                                                                     font,
427                                                                     sldFont.getColor( feature ),
428                                                                     metrics,
429                                                                     feature,
430                                                                     symbolizer.getHalo(),
431                                                                     boxStartX,
432                                                                     boxStartY,
433                                                                     (int) width,
434                                                                     (int) height,
435                                                                     rotation,
436                                                                     new double[] { 0.0, 0.5 },
437                                                                     new double[] { ( w - width ) / 2, delta + deviation[0] } );
438                        Label lowerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
439                                                                     feature, symbolizer.getHalo(), boxStartX, boxStartY,
440                                                                     (int) width, (int) height, rotation,
441                                                                     new double[] { 0.0, 0.5 },
442                                                                     new double[] { ( w - width ) / 2,
443                                                                                   -delta - deviation[1] } );
444                        Label centerLabel = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ), metrics,
445                                                                      feature, symbolizer.getHalo(), boxStartX, boxStartY,
446                                                                      (int) width, (int) height, rotation,
447                                                                      new double[] { 0.0, 0.5 },
448                                                                      new double[] { ( w - width ) / 2, 0.0 } );
449                        choices.add( new LabelChoice( element, new Label[] { lowerLabel, upperLabel, centerLabel },
450                                                      new float[] { 0.0f, 0.25f, 1.0f }, 0, centerLabel.getMaxX(),
451                                                      lowerLabel.getMaxY(), centerLabel.getMinX(), upperLabel.getMinY() ) );
452                        break;
453                    }
454                    default: {
455                    }
456                    }
457    
458                    boxStartX = lastX;
459                    boxStartY = lastY;
460                    eCandidates.clear();
461                } else {
462                    eCandidates.add( new int[] { x, y } );
463                    lastX = x;
464                    lastY = y;
465                    i++;
466                }
467            }
468    
469            // pick LabelChoices on the linestring
470            ArrayList<LabelChoice> pick = new ArrayList<LabelChoice>(choices.size());
471            int n = choices.size();
472            for ( int j = n / 2; j < choices.size(); j += ( gap + 1 ) ) {
473                pick.add( choices.get( j ) );
474            }
475            for ( int j = n / 2 - ( gap + 1 ); j > 0; j -= ( gap + 1 ) ) {
476                pick.add( choices.get( j ) );
477            }
478            return pick;
479        }
480    }