    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }