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, " <i>", "</i>");
235 for (final GeneralParameterDescriptor param : parameters.descriptors()) {
236 printParametersJavadocRow(param, null, " • ", "");
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 }