001    /*
002     *    GeoAPI - Java interfaces for OGC/ISO standards
003     *    http://www.geoapi.org
004     *
005     *    This file is hereby placed into the Public Domain.
006     *    This means anyone is free to do whatever they wish with this file.
007     *
008     *    The NetCDF wrappers are provided as code examples, in the hope to facilitate
009     *    GeoAPI implementations backed by other libraries. Implementors can take this
010     *    source code and use it for any purpose, commercial or non-commercial, copyrighted
011     *    or open-source, with no legal obligation to acknowledge the borrowing/copying
012     *    in any way.
013     */
014    package org.opengis.wrapper.netcdf;
015    
016    import java.util.Map;
017    import java.util.LinkedHashMap;
018    
019    import ucar.nc2.constants.CF;
020    import ucar.unidata.util.Parameter;
021    import ucar.unidata.geoloc.Projection;
022    import ucar.unidata.geoloc.projection.*;
023    
024    import org.opengis.util.FactoryException;
025    import org.opengis.parameter.ParameterValueGroup;
026    import org.opengis.parameter.GeneralParameterValue;
027    import org.opengis.parameter.ParameterDescriptorGroup;
028    import org.opengis.parameter.GeneralParameterDescriptor;
029    import org.opengis.referencing.operation.MathTransform;
030    import org.opengis.referencing.operation.OperationMethod;
031    import org.opengis.referencing.operation.MathTransformFactory;
032    import org.opengis.test.TestCase;
033    
034    import org.junit.Test;
035    
036    import static org.opengis.test.Assert.*;
037    
038    
039    /**
040     * Tests the {@link NetcdfTransformFactory} class.
041     *
042     * @author  Martin Desruisseaux (Geomatys)
043     * @version 3.1
044     * @since   3.1
045     */
046    public strictfp class NetcdfTransformFactoryTest extends TestCase {
047        /**
048         * The set of projection implementations to test.
049         * The default constructor initializes this array to
050         * {@link AlbersEqualArea},
051         * {@link FlatEarth},
052         * {@link LambertAzimuthalEqualArea},
053         * {@link LambertConformal},
054         * {@link LatLonProjection},
055         * {@link Mercator},
056         * {@link Orthographic},
057         * {@link RotatedLatLon},
058         * {@link RotatedPole},
059         * {@link Stereographic},
060         * {@link TransverseMercator},
061         * {@link UtmProjection} and
062         * {@link VerticalPerspectiveView}.
063         * Subclasses can modify this array before a test is run if they need to test a
064         * different set of projection implementations.
065         */
066        protected Class<? extends Projection>[] projections;
067    
068        /**
069         * The factory to test. Subclasses can modify this field before a test
070         * is run if they need to test a different factory implementation.
071         */
072        protected MathTransformFactory factory;
073    
074        /**
075         * The default factory, created when first needed.
076         */
077        private static MathTransformFactory defaultFactory;
078    
079        /**
080         * Creates a new test case for {@link NetcdfTransformFactory}.
081         */
082        public NetcdfTransformFactoryTest() {
083            this(getDefaultFactory());
084        }
085    
086        /**
087         * Creates a new test case for the given factory.
088         *
089         * @param factory The factory to test.
090         */
091        @SuppressWarnings({"unchecked","rawtypes"})
092        protected NetcdfTransformFactoryTest(final MathTransformFactory factory) {
093            super(factory);
094            this.factory = factory;
095            projections = new Class[] {
096                AlbersEqualArea.class,
097                FlatEarth.class,
098                LambertAzimuthalEqualArea.class,
099                LambertConformal.class,
100                LatLonProjection.class,
101                Mercator.class,
102                Orthographic.class,
103                RotatedLatLon.class,
104                RotatedPole.class,
105                Stereographic.class,
106                TransverseMercator.class,
107                UtmProjection.class,
108                VerticalPerspectiveView.class
109            };
110        }
111    
112        /**
113         * Returns the default factory to test.
114         */
115        static synchronized MathTransformFactory getDefaultFactory() {
116            if (defaultFactory == null) {
117                defaultFactory = new NetcdfTransformFactory();
118            }
119            return defaultFactory;
120        }
121    
122        /**
123         * Returns {@code true} if the parameter of the given name can be ignored.
124         * This method is invoked when a parameter is not always declared by the
125         * NetCDF projection constructor.
126         *
127         * @param parameterName The parameter name.
128         * @return {@code true} if the parameter of the given name is not always declared
129         *         by the NetCDF projection constructor.
130         */
131        private static boolean isIgnorable(final String parameterName) {
132            return CF.FALSE_EASTING  .equals(parameterName) ||
133                   CF.FALSE_NORTHING .equals(parameterName) ||
134                   "north_hemisphere".equals(parameterName);
135        }
136    
137        /**
138         * Ensures that the parameter names are the same than the ones created by the UCAR library.
139         *
140         * @throws FactoryException If an error occurred while using the {@linkplain #factory}.
141         */
142        @Test
143        public void testParameterNames() throws FactoryException {
144            final Map<String,Integer> names = new LinkedHashMap<String,Integer>();
145            for (final Class<? extends Projection> type : projections) {
146                final Projection projection;
147                try {
148                    projection = type.newInstance();
149                } catch (InstantiationException e) {
150                    throw new AssertionError(e);
151                } catch (IllegalAccessException e) {
152                    throw new AssertionError(e);
153                }
154                final String projectionName = projection.getClassName();
155                /*
156                 * Collect the NetCDF parameter names. This code ensures that the same NetCDF
157                 * parameter name is not declared twice. A failure in this test would be more
158                 * a NetCDF issue than a GeoAPI-wrapper one.
159                 *
160                 * The values in the map are the length of parameter value arrays, or 1 if
161                 * the parameter values are scalar.
162                 */
163                names.clear();
164                for (final Parameter param : projection.getProjectionParameters()) {
165                    final String parameterName = param.getName();
166                    if (names.put(parameterName, param.isString() ? 1 : param.getLength()) != null) {
167                        fail("Duplicated \"" + parameterName + "\" parameter in \"" + projectionName + "\" projection.");
168                    }
169                }
170                if (names.remove(CF.GRID_MAPPING_NAME) == null) {
171                    fail("Missing \"" + CF.GRID_MAPPING_NAME + "\" parameter in \"" + projectionName + "\" projection.");
172                }
173                /*
174                 * Ensures that all parameter names known to GeoAPI-wrapper
175                 * are known to the current NetCDF projection implementation.
176                 */
177                final ParameterValueGroup group = factory.getDefaultParameters(projectionName);
178                validators.validate(group);
179                for (final GeneralParameterValue param : group.values()) {
180                    String parameterName = param.getDescriptor().getName().getCode();
181                    final int s = parameterName.lastIndexOf('[');
182                    if (s >= 0) {
183                        parameterName = parameterName.substring(0, s);
184                    }
185                    final Integer count = names.remove(parameterName);
186                    if (count != null) {
187                        final int n = count - 1;
188                        if (n != 0) {
189                            assertNull(names.put(parameterName, n));
190                        }
191                    } else if (!isIgnorable(parameterName)) {
192                        fail("Unknown \"" + parameterName + "\" parameter in \"" + projectionName + "\" projection.");
193                    }
194                }
195                /*
196                 * Any remaining parameters in the set are parameter that should have been
197                 * declared in the GeoAPI wrappers but are not.
198                 */
199                assertTrue("Missing parameter in \"" + projectionName + "\" projection: " + names, names.isEmpty());
200            }
201        }
202    
203        /**
204         * Tests the creation of {@link NetcdfProjection} instances.
205         *
206         * @throws FactoryException If an error occurred while using the {@linkplain #factory}.
207         */
208        @Test
209        public void testProjectionCreation() throws FactoryException {
210            for (final Class<? extends Projection> type : projections) {
211                final String projectionName = type.getSimpleName();
212                final ParameterValueGroup group = factory.getDefaultParameters(projectionName);
213                final MathTransform projection = factory.createParameterizedTransform(group);
214                assertInstanceOf("Expected a NetCDF wrapper.", NetcdfProjection.class, projection);
215                assertInstanceOf("Expected a NetCDF projection.", type, ((NetcdfProjection) projection).delegate());
216            }
217        }
218    
219        /**
220         * Generates a list of all supported projections and their parameters in Javadoc format.
221         * The output of this method can be copy-and-pasted in the {@link NetcdfTransformFactory}
222         * class javadoc.
223         *
224         * <p>This method is not a real test case. Users need to invoke this method explicitely,
225         * or to declare this method as a test method, in order to get the output.</p>
226         */
227        public void printParametersJavadoc() {
228            System.out.println(" * <table cellspacing=\"0\" cellpadding=\"0\">");
229            System.out.println(" *   <tr><th>NetCDF</th><th>OGC</th><th>EPSG</th></tr>");
230            for (final OperationMethod method : factory.getAvailableMethods(null)) {
231                final ParameterDescriptorGroup parameters = method.getParameters();
232                System.out.println(" *   <tr><td colspan=\"3\"><hr></td></tr>");
233                printParametersJavadocRow(parameters, (method instanceof ProjectionProvider<?>) ?
234                        ((ProjectionProvider<?>) method).delegate() : null, "&nbsp;&nbsp;<i>", "</i>");
235                for (final GeneralParameterDescriptor param : parameters.descriptors()) {
236                    printParametersJavadocRow(param, null, "&nbsp;&nbsp;&nbsp;&nbsp;&bull;&nbsp;", "");
237                }
238            }
239            System.out.println(" * </table>");
240        }
241    
242        /**
243         * Prints a single row in the table of NetCDF parameters.
244         * This method expects a {@code geoapi-netcdf} implementation.
245         */
246        private static void printParametersJavadocRow(final GeneralParameterDescriptor param,
247                final Class<? extends Projection> type, final String prefix, final String suffix)
248        {
249            final AliasList names = (AliasList) param.getAlias();
250            String name = names.name;
251            if (type != null) {
252                final String cn = type.getSimpleName();
253                if (cn.equals(name)) {
254                    name = "{@linkplain " + cn + '}';
255                } else {
256                    name = "{@linkplain " + cn + ' ' + name + '}';
257                }
258            }
259            System.out.print(" *   <tr><td>");                         {System.out.print(prefix); System.out.print(name);            System.out.print(suffix);}
260            System.out.print(    "</td><td>"); if (names.ogc  != null) {System.out.print(prefix); System.out.print(names.ogc .name); System.out.print(suffix);}
261            System.out.print(    "</td><td>"); if (names.epsg != null) {System.out.print(prefix); System.out.print(names.epsg.name); System.out.print(suffix);}
262            System.out.println("</td></tr>");
263        }
264    }