001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/MathTransformFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification
012     (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/)
013     SEAGIS Contacts:  Surveillance de l'Environnement Assist�e par Satellite
014     Institut de Recherche pour le D�veloppement / US-Espace
015     mailto:seasnet@teledetection.fr
016    
017    
018     This library is free software; you can redistribute it and/or
019     modify it under the terms of the GNU Lesser General Public
020     License as published by the Free Software Foundation; either
021     version 2.1 of the License, or (at your option) any later version.
022    
023     This library is distributed in the hope that it will be useful,
024     but WITHOUT ANY WARRANTY; without even the implied warranty of
025     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
026     Lesser General Public License for more details.
027    
028     You should have received a copy of the GNU Lesser General Public
029     License along with this library; if not, write to the Free Software
030     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
031    
032     Contact:
033    
034     Andreas Poth
035     lat/lon GmbH
036     Aennchenstr. 19
037     53115 Bonn
038     Germany
039     E-Mail: poth@lat-lon.de
040    
041     Klaus Greve
042     Department of Geography
043     University of Bonn
044     Meckenheimer Allee 166
045     53115 Bonn
046     Germany
047     E-Mail: klaus.greve@uni-bonn.de
048    
049     
050     ---------------------------------------------------------------------------*/
051    package org.deegree.model.csct.ct;
052    
053    // OpenGIS dependencies
054    import java.awt.geom.AffineTransform;
055    import java.util.Locale;
056    import java.util.NoSuchElementException;
057    
058    import javax.media.jai.ParameterList;
059    
060    import org.deegree.model.csct.cs.Projection;
061    import org.deegree.model.csct.pt.Matrix;
062    import org.deegree.model.csct.resources.Naming;
063    import org.deegree.model.csct.resources.WeakHashSet;
064    import org.deegree.model.csct.resources.css.ResourceKeys;
065    import org.deegree.model.csct.resources.css.Resources;
066    
067    /**
068     * Creates math transforms. <code>MathTransformFactory</code> is a low level factory that is used
069     * to create {@link MathTransform} objects. Many high level GIS applications will never need to use
070     * a <code>MathTransformFactory</code> directly; they can use a
071     * {@link CoordinateTransformationFactory} instead. However, the <code>MathTransformFactory</code>
072     * class is specified here, since it can be used directly by applications that wish to transform
073     * other types of coordinates (e.g. color coordinates, or image pixel coordinates). <br>
074     * <br>
075     * A math transform is an object that actually does the work of applying formulae to coordinate
076     * values. The math transform does not know or care how the coordinates relate to positions in the
077     * real world. This lack of semantics makes implementing <code>MathTransformFactory</code>
078     * significantly easier than it would be otherwise.
079     * 
080     * For example <code>MathTransformFactory</code> can create affine math transforms. The affine
081     * transform applies a matrix to the coordinates without knowing how what it is doing relates to the
082     * real world. So if the matrix scales <var>Z</var> values by a factor of 1000, then it could be
083     * converting meters into millimeters, or it could be converting kilometers into meters. <br>
084     * <br>
085     * Because math transforms have low semantic value (but high mathematical value), programmers who do
086     * not have much knowledge of how GIS applications use coordinate systems, or how those coordinate
087     * systems relate to the real world can implement <code>MathTransformFactory</code>.
088     * 
089     * The low semantic content of math transforms also means that they will be useful in applications
090     * that have nothing to do with GIS coordinates. For example, a math transform could be used to map
091     * color coordinates between different color spaces, such as converting (red, green, blue) colors
092     * into (hue, light, saturation) colors. <br>
093     * <br>
094     * Since a math transform does not know what its source and target coordinate systems mean, it is
095     * not necessary or desirable for a math transform object to keep information on its source and
096     * target coordinate systems.
097     * 
098     * @version 1.00
099     * @author OpenGIS (www.opengis.org)
100     * @author Martin Desruisseaux
101     * 
102     * @author last edited by: $Author: bezema $
103     * 
104     * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
105     * 
106     * @see "org.opengis.ct.CT_MathTransformFactory"
107     */
108    public class MathTransformFactory {
109        /**
110         * The default math transform factory. This factory will be constructed only when first needed.
111         */
112        private static MathTransformFactory DEFAULT;
113    
114        /**
115         * A pool of math transform. This pool is used in order to returns instance of existing math
116         * transforms when possible.
117         */
118        static final WeakHashSet pool = new WeakHashSet();
119    
120        /**
121         * List of registered math transforms.
122         */
123        private final MathTransformProvider[] providers;
124    
125        /**
126         * Construct a factory using the specified providers.
127         * 
128         * @param providers
129         */
130        public MathTransformFactory( final MathTransformProvider[] providers ) {
131            this.providers = providers.clone();
132        }
133    
134        /**
135         * Returns the default math transform factory.
136         * 
137         * @return the default math transform factory.
138         */
139        public static synchronized MathTransformFactory getDefault() {
140            if ( DEFAULT == null ) {
141                DEFAULT = new MathTransformFactory(
142                                                    new MathTransformProvider[] {
143                                                                                 new MercatorProjection.Provider(),
144                                                                                 new LambertConformalProjection.Provider(),
145                                                                                 new StereographicProjection.Provider(), // Automatic
146                                                                                 new StereographicProjection.Provider(
147                                                                                                                       true ), // Polar
148                                                                                 new StereographicProjection.Provider(
149                                                                                                                       false ), // Oblique
150                                                                                 new TransverseMercatorProjection.Provider(
151                                                                                                                            false ), // Universal
152                                                                                 new TransverseMercatorProjection.Provider(
153                                                                                                                            true ), // Modified
154                                                                                 new GeocentricTransform.Provider(
155                                                                                                                   false ), // Geographic
156                                                                                                                            // to
157                                                                                                                            // Geocentric
158                                                                                 new GeocentricTransform.Provider(
159                                                                                                                   true ) // Geocentric
160                                                                                                                            // to
161                                                                                                                            // Geographic
162                                                    } );
163                for ( int i = DEFAULT.providers.length; --i >= 0; ) {
164                    final MathTransformProvider provider = DEFAULT.providers[i];
165                    if ( provider instanceof MapProjection.Provider ) {
166                        // Register only projections.
167                        Naming.PROJECTIONS.bind( provider.getClassName(),
168                                                 provider.getParameterListDescriptor() );
169                    }
170                }
171            }
172            return DEFAULT;
173        }
174    
175        /**
176         * Creates an identity transform of the specified dimension.
177         * 
178         * @param dimension
179         *            The source and target dimension.
180         * @return The identity transform.
181         */
182        public MathTransform createIdentityTransform( final int dimension ) {
183            // Affine transform has one more row/column than dimension.
184            return createAffineTransform( new Matrix( dimension + 1 ) );
185        }
186    
187        /**
188         * Creates an affine transform from a matrix.
189         * 
190         * @param matrix
191         *            The matrix used to define the affine transform.
192         * @return The affine transform.
193         */
194        public MathTransform2D createAffineTransform( final AffineTransform matrix ) {
195            return (MathTransform2D) pool.intern( new AffineTransform2D( matrix ) );
196        }
197    
198        /**
199         * Creates an affine transform from a matrix.
200         * 
201         * @param matrix
202         *            The matrix used to define the affine transform.
203         * @return The affine transform.
204         * 
205         */
206        public MathTransform createAffineTransform( final Matrix matrix ) {
207            /*
208             * If the user is requesting a 2D transform, delegate to the highly optimized
209             * java.awt.geom.AffineTransform class.
210             */
211            if ( matrix.getNumRow() == 3 && matrix.isAffine() ) // Affine transform are square.
212            {
213                return createAffineTransform( matrix.toAffineTransform2D() );
214            }
215            /*
216             * General case (slower). May not be a real affine transform. We accept it anyway...
217             */
218            return (MathTransform) pool.intern( new MatrixTransform( matrix ) );
219        }
220    
221        /**
222         * Returns the underlying matrix for the specified transform, or <code>null</code> if the
223         * matrix is unavailable.
224         */
225        private static Matrix getMatrix( final MathTransform transform ) {
226            if ( transform instanceof AffineTransform )
227                return new Matrix( (AffineTransform) transform );
228            if ( transform instanceof MatrixTransform )
229                return ( (MatrixTransform) transform ).getMatrix();
230            return null;
231        }
232    
233        /**
234         * Tests if one math transform is the inverse of the other. This implementation can't detect
235         * every case. It just detect the case when <code>tr2</code> is an instance of
236         * {@link AbstractMathTransform.Inverse}.
237         */
238        private static boolean areInverse( final MathTransform tr1, final MathTransform tr2 ) {
239            if ( tr2 instanceof AbstractMathTransform.Inverse ) {
240                return tr1.equals( ( (AbstractMathTransform.Inverse) tr2 ).inverse() );
241                // TODO: we could make this test more general (just compare with tr2.inverse(),
242                // no matter if it is an instance of AbstractMathTransform.Inverse or not,
243                // and catch the exception if one is thrown). Would it be too expensive to
244                // create inconditionnaly the inverse transform?
245            }
246            return false;
247        }
248    
249        /**
250         * Creates a transform by concatenating two existing transforms. A concatenated transform acts
251         * in the same way as applying two transforms, one after the other. The dimension of the output
252         * space of the first transform must match the dimension of the input space in the second
253         * transform. If you wish to concatenate more than two transforms, then you can repeatedly use
254         * this method.
255         * 
256         * @param tr1
257         *            The first transform to apply to points.
258         * @param tr2
259         *            The second transform to apply to points.
260         * @return The concatenated transform.
261         * 
262         */
263        public MathTransform createConcatenatedTransform( MathTransform tr1, MathTransform tr2 ) {
264            if ( tr1.isIdentity() )
265                return tr2;
266            if ( tr2.isIdentity() )
267                return tr1;
268            /*
269             * If both transforms use matrix, then we can create a single transform using the concatened
270             * matrix.
271             */
272            final Matrix matrix1 = getMatrix( tr1 );
273            if ( matrix1 != null ) {
274                final Matrix matrix2 = getMatrix( tr2 );
275                if ( matrix2 != null ) {
276                    // May not be really affine, but work anyway...
277                    // This call will detect and optimize the special
278                    // case where an 'AffineTransform' can be used.
279                    matrix2.mul( matrix1 );
280                    return createAffineTransform( matrix2 );
281                }
282            }
283            /*
284             * If one transform is the inverse of the other, returns the identity transform.
285             */
286            if ( areInverse( tr1, tr2 ) || areInverse( tr2, tr1 ) ) {
287                return createIdentityTransform( tr1.getDimSource() );
288            }
289            /*
290             * If one or both math transform are instance of {@link ConcatenedTransform}, then maybe it
291             * is possible to efficiently concatenate <code>tr1</code> or <code>tr2</code> with one
292             * of step transforms. Try that...
293             */
294            if ( tr1 instanceof ConcatenedTransform ) {
295                final ConcatenedTransform ctr = (ConcatenedTransform) tr1;
296                tr1 = ctr.transform1;
297                tr2 = createConcatenatedTransform( ctr.transform2, tr2 );
298            }
299            if ( tr2 instanceof ConcatenedTransform ) {
300                final ConcatenedTransform ctr = (ConcatenedTransform) tr2;
301                tr1 = createConcatenatedTransform( tr1, ctr.transform1 );
302                tr2 = ctr.transform2;
303            }
304            /*
305             * The returned transform will implements {@link MathTransform2D} if source and target
306             * dimensions are equal to 2. {@link MathTransform} implementations are available in two
307             * version: direct and non-direct. The "non-direct" version use an intermediate buffer when
308             * performing transformations; they are slower and consume more memory. They are used only
309             * as a fallback when a "direct" version can't be created.
310             */
311            final MathTransform transform;
312            final int dimSource = tr1.getDimSource();
313            final int dimTarget = tr2.getDimTarget();
314            if ( dimSource == 2 && dimTarget == 2 ) {
315                if ( tr1 instanceof MathTransform2D && tr2 instanceof MathTransform2D ) {
316                    transform = new ConcatenedTransformDirect2D( this, (MathTransform2D) tr1,
317                                                                 (MathTransform2D) tr2 );
318                } else
319                    transform = new ConcatenedTransform2D( this, tr1, tr2 );
320            } else if ( dimSource == tr1.getDimTarget() && tr2.getDimSource() == dimTarget ) {
321                transform = new ConcatenedTransformDirect( this, tr1, tr2 );
322            } else
323                transform = new ConcatenedTransform( this, tr1, tr2 );
324            return (MathTransform) pool.intern( transform );
325        }
326    
327        /**
328         * Creates a transform which passes through a subset of ordinates to another transform. This
329         * allows transforms to operate on a subset of ordinates. For example, if you have (<var>latitidue</var>,<var>longitude</var>,<var>height</var>)
330         * coordinates, then you may wish to convert the height values from feet to meters without
331         * affecting the latitude and longitude values.
332         * 
333         * @param firstAffectedOrdinate
334         *            Index of the first affected ordinate.
335         * @param subTransform
336         *            The sub transform.
337         * @param numTrailingOrdinates
338         *            Number of trailing ordinates to pass through. Affected ordinates will range from
339         *            <code>firstAffectedOrdinate</code> inclusive to
340         *            <code>dimTarget-numTrailingOrdinates</code> exclusive.
341         * @return A pass through transform with the following dimensions:<br>
342         * 
343         * <pre>
344         *  Source: firstAffectedOrdinate + subTransform.getDimSource() + numTrailingOrdinates
345         *  Target: firstAffectedOrdinate + subTransform.getDimTarget() + numTrailingOrdinates
346         * </pre>
347         * 
348         */
349        public MathTransform createPassThroughTransform( final int firstAffectedOrdinate,
350                                                         final MathTransform subTransform,
351                                                         final int numTrailingOrdinates ) {
352            if ( firstAffectedOrdinate < 0 ) {
353                throw new IllegalArgumentException(
354                                                    Resources.format(
355                                                                      ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2,
356                                                                      "firstAffectedOrdinate",
357                                                                      new Integer(
358                                                                                   firstAffectedOrdinate ) ) );
359            }
360            if ( numTrailingOrdinates < 0 ) {
361                throw new IllegalArgumentException(
362                                                    Resources.format(
363                                                                      ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2,
364                                                                      "numTrailingOrdinates",
365                                                                      new Integer( numTrailingOrdinates ) ) );
366            }
367            if ( firstAffectedOrdinate == 0 && numTrailingOrdinates == 0 ) {
368                return subTransform;
369            }
370            //
371            // Optimize the "Identity transform" case.
372            //
373            if ( subTransform.isIdentity() ) {
374                final int dimension = subTransform.getDimSource();
375                if ( dimension == subTransform.getDimTarget() ) {
376                    // The AffineTransform is easier to concatenate with other transforms.
377                    return createIdentityTransform( firstAffectedOrdinate + dimension
378                                                    + numTrailingOrdinates );
379                }
380            }
381            //
382            // Optimize the "Pass through case": this is done
383            // right into PassThroughTransform's constructor.
384            //
385            return (MathTransform) pool.intern( new PassThroughTransform( firstAffectedOrdinate,
386                                                                          subTransform,
387                                                                          numTrailingOrdinates ) );
388        }
389    
390        /**
391         * Creates a transform which retains only a portion of an other transform. For example if the
392         * source coordinate system has (<var>longitude</var>, <var>latitude</var>, <var>height</var>)
393         * values, then a sub-transform may be used to keep only the (<var>longitude</var>,
394         * <var>latitude</var>) part. In most cases, the created sub-transform is non-invertible since
395         * it loose informations. <br>
396         * <br>
397         * This transform is a special case of a non-square matrix transform with less rows than
398         * columns. However, using a <code>createSubMathTransfom(...)</code> method makes it easier to
399         * optimize some common cases.
400         * 
401         * @param transform
402         *            The transform.
403         * @param lower
404         *            Index of the first ordinate to keep.
405         * @param upper
406         *            Index of the first ordinate. Must be greater than <code>lower</code>.
407         * @return
408         */
409        public MathTransform createSubMathTransform( final int lower, final int upper,
410                                                     final MathTransform transform ) {
411            if ( lower < 0 || lower >= upper ) {
412                throw new IllegalArgumentException(
413                                                    Resources.format(
414                                                                      ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2,
415                                                                      "lower", new Integer( lower ) ) );
416            }
417            final int dimTarget = transform.getDimTarget();
418            if ( upper > dimTarget ) {
419                throw new IllegalArgumentException(
420                                                    Resources.format(
421                                                                      ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2,
422                                                                      "upper", new Integer( upper ) ) );
423            }
424            if ( lower == 0 && upper == dimTarget ) {
425                return transform;
426            }
427            if ( transform instanceof PassThroughTransform ) {
428                // Special case for pass through transform:
429                // Compute lower and upper values relatives
430                // to the underlying sub-transform.
431                final PassThroughTransform passThrough = (PassThroughTransform) transform;
432                final int lowerTr = lower - passThrough.firstAffectedOrdinate;
433                final int upperTr = upper - passThrough.firstAffectedOrdinate;
434                final int passDim = passThrough.transform.getDimTarget();
435                if ( lowerTr >= 0 && upperTr <= passDim ) {
436                    return createSubMathTransform( lowerTr, upperTr, passThrough.transform );
437                }
438                if ( lowerTr <= 0 && upperTr >= passDim ) {
439                    return createPassThroughTransform( -lowerTr, passThrough.transform, upperTr
440                                                                                        - passDim );
441                }
442            }
443            // General case: use a matrix.
444            final int dimOutput = upper - lower;
445            final Matrix matrix = new Matrix( dimOutput + 1, dimTarget + 1 );
446            matrix.setZero();
447            for ( int i = lower; i < upper; i++ ) {
448                matrix.setElement( i - lower, i, 1 );
449            }
450            matrix.setElement( dimOutput, dimTarget, 1 ); // Affine transform has one more row/column
451                                                            // than dimension.
452            return createConcatenatedTransform( transform, createAffineTransform( matrix ) );
453        }
454    
455        /**
456         * Creates a transform from a classification name and parameters. The client must ensure that
457         * all the linear parameters are expressed in meters, and all the angular parameters are
458         * expressed in degrees. Also, they must supply "semi_major" and "semi_minor" parameters for
459         * cartographic projection transforms.
460         * 
461         * @param classification
462         *            The classification name of the transform (e.g. "Transverse_Mercator"). Leading and
463         *            trailing spaces are ignored, and comparaison is case-insensitive.
464         * @param parameters
465         *            The parameter values in standard units.
466         * @return The parameterized transform.
467         * @throws NoSuchElementException
468         *             if there is no transform for the specified classification.
469         * @throws MissingParameterException
470         *             if a parameter was required but not found.
471         * 
472         */
473        public MathTransform createParameterizedTransform( String classification,
474                                                           final ParameterList parameters )
475                                throws NoSuchElementException, MissingParameterException {
476            final MathTransform transform;
477            classification = classification.trim();
478            if ( classification.equalsIgnoreCase( "Affine" ) ) {
479                // Special case for "Affine", since the ParameterListDescriptor
480                // depends of the matrix size.
481                transform = MatrixTransform.Provider.staticCreate( parameters );
482            } else {
483                transform = getMathTransformProvider( classification ).create( parameters );
484            }
485            return (MathTransform) pool.intern( transform );
486        }
487    
488        /**
489         * Convenience method for creating a transform from a projection.
490         * 
491         * @param projection
492         *            The projection.
493         * @return The parameterized transform.
494         * @throws NoSuchElementException
495         *             if there is no transform for the specified projection.
496         * @throws MissingParameterException
497         *             if a parameter was required but not found.
498         */
499        public MathTransform createParameterizedTransform( final Projection projection )
500                                throws NoSuchElementException, MissingParameterException {
501            return createParameterizedTransform( projection.getClassName(), projection.getParameters() );
502        }
503    
504        /**
505         * Returns the classification names of every available transforms. The returned array may have a
506         * zero length, but will never be null.
507         * 
508         * @return the classification names of every available transforms. The returned array may have a
509         *         zero length, but will never be <code>null</code>.
510         * 
511         */
512        public String[] getAvailableTransforms() {
513            final String[] names = new String[providers.length + 1];
514            int i;
515            for ( i = 0; i < names.length; i++ ) {
516                names[i] = providers[i].getClassName();
517            }
518            // Special case for "Affine", since the ParameterListDescriptor
519            // depends of the matrix size.
520            names[i] = "Affine";
521            return names;
522        }
523    
524        /**
525         * Returns the provider for the specified classification. This provider may be used to query
526         * parameter list for a classification name (e.g.
527         * <code>getMathTransformProvider("Transverse_Mercator").getParameterList()</code>), or the
528         * transform name in a given locale (e.g.
529         * <code>getMathTransformProvider("Transverse_Mercator").getName({@link Locale#FRENCH})</code>)
530         * 
531         * @param classification
532         *            The classification name of the transform (e.g. "Transverse_Mercator"). It should
533         *            be one of the name returned by {@link #getAvailableTransforms}. Leading and
534         *            trailing spaces are ignored. Comparisons are case-insensitive.
535         * @return The provider for a math transform.
536         * @throws NoSuchElementException
537         *             if there is no provider registered with the specified classification name.
538         */
539        public MathTransformProvider getMathTransformProvider( String classification )
540                                throws NoSuchElementException {
541            classification = classification.trim();
542            for ( int i = 0; i < providers.length; i++ ) {
543                if ( classification.equalsIgnoreCase( providers[i].getClassName().trim() ) )
544                    return providers[i];
545            }
546            throw new NoSuchElementException(
547                                              Resources.format(
548                                                                ResourceKeys.ERROR_NO_TRANSFORM_FOR_CLASSIFICATION_$1,
549                                                                classification ) );
550        }
551    
552        /**
553         * Create a provider for affine transforms of the specified dimension. Created affine transforms
554         * will have a size of <code>numRow&nbsp;&times;&nbsp;numCol</code>. <br>
555         * <br>
556         * <table align="center" border='1' cellpadding='3' bgcolor="F4F8FF">
557         * <tr bgcolor="#B9DCFF">
558         * <th>Parameter</th>
559         * <th>Description</th>
560         * </tr>
561         * <tr>
562         * <td><code>Num_row</code></td>
563         * <td>Number of rows in matrix</td>
564         * </tr>
565         * <tr>
566         * <td><code>Num_col</code></td>
567         * <td>Number of columns in matrix</td>
568         * </tr>
569         * <tr>
570         * <td><code>elt_&lt;r&gt;_&lt;c&gt;</code></td>
571         * <td>Element of matrix</td>
572         * </tr>
573         * </table> <br>
574         * For the element parameters, <code>&lt;r&gt;</code> and <code>&lt;c&gt;</code> should be
575         * substituted by printed decimal numbers. The values of <var>r</var> should be from 0 to
576         * <code>(num_row-1)</code>, and the values of <var>c</var> should be from 0 to
577         * <code>(num_col-1)</code>. Any undefined matrix elements are assumed to be zero for
578         * <code>(r!=c)</code>, and one for <code>(r==c)</code>. This corresponds to the identity
579         * transformation when the number of rows and columns are the same. The number of columns
580         * corresponds to one more than the dimension of the source coordinates and the number of rows
581         * corresponds to one more than the dimension of target coordinates. The extra dimension in the
582         * matrix is used to let the affine map do a translation.
583         * 
584         * @param numRow
585         *            The number of matrix's rows.
586         * @param numCol
587         *            The number of matrix's columns.
588         * @return The provider for an affine transform.
589         * @throws IllegalArgumentException
590         *             if <code>numRow</code> or <code>numCol</code> is not a positive number.
591         */
592        public MathTransformProvider getAffineTransformProvider( final int numRow, final int numCol )
593                                throws IllegalArgumentException {
594            return new MatrixTransform.Provider( numRow, numCol );
595        }
596    
597    }