This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 97524f8379a6e1e80765793a875bd611230fd438 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Jan 11 22:37:23 2022 +0100 Make exception messages more informative for some errors happening at netCDF reading time. It requires some code for making possible to transfer information, but this is for error handling only. --- .../apache/sis/internal/referencing/Resources.java | 9 +- .../sis/internal/referencing/Resources.properties | 3 +- .../internal/referencing/Resources_fr.properties | 3 +- .../referencing/factory/FactoryDataException.java | 13 ++- .../sis/referencing/factory/package-info.java | 2 +- .../operation/builder/LinearTransformBuilder.java | 11 +- .../operation/builder/LocalizationGridBuilder.java | 2 +- .../builder/LocalizationGridException.java | 114 +++++++++++++++++++++ .../main/java/org/apache/sis/util/Exceptions.java | 5 +- .../java/org/apache/sis/internal/netcdf/Axis.java | 16 +++ .../org/apache/sis/internal/netcdf/Dimension.java | 16 ++- .../java/org/apache/sis/internal/netcdf/Grid.java | 12 ++- .../org/apache/sis/internal/netcdf/Linearizer.java | 46 +++++++-- .../org/apache/sis/internal/netcdf/Variable.java | 24 +++-- .../sis/internal/netcdf/ucar/DimensionWrapper.java | 13 ++- 15 files changed, 257 insertions(+), 32 deletions(-) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java index b3e451d..36943bc 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.java @@ -44,7 +44,7 @@ public final class Resources extends IndexedResourceBundle { * pools of compiled classes. * * @author Martin Desruisseaux (IRD, Geomatys) - * @since 0.8 + * @since 1.2 * @module */ public static final class Keys extends KeyConstants { @@ -189,7 +189,7 @@ public final class Resources extends IndexedResourceBundle { public static final short ConstantProjParameterValue_1 = 12; /** - * Coordinate conversion of transformation from system “{0}” to “{1}” has not been found. + * Coordinate operation from system “{0}” to “{1}” has not been found. */ public static final short CoordinateOperationNotFound_2 = 13; @@ -247,6 +247,11 @@ public final class Resources extends IndexedResourceBundle { public static final short GeodeticDataBase_4 = 18; /** + * The grid spans {0}° of longitude, which may be too wide for the “{1}” domain. + */ + public static final short GridLongitudeSpanTooWide_2 = 102; + + /** * More than one service provider of type ‘{0}’ are declared for “{1}”. Only the first provider * (an instance of ‘{2}’) will be used. */ diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties index 59a3282..17b98e8 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources.properties @@ -66,7 +66,7 @@ CanNotTransformEnvelopeToGeodetic = Can not transform envelope to a geodetic ref CanNotTransformGeometry = Can not transform the given geometry. CanNotUseGeodeticParameters_2 = Can not use the {0} geodetic parameters. Caused by: {1} ColinearAxisDirections_2 = Axis directions {0} and {1} are colinear. -CoordinateOperationNotFound_2 = Coordinate conversion of transformation from system \u201c{0}\u201d to \u201c{1}\u201d has not been found. +CoordinateOperationNotFound_2 = Coordinate operation from system \u201c{0}\u201d to \u201c{1}\u201d has not been found. DatumChangesDirectory_1 = Datum shift files are searched in the \u201c{0}\u201d directory. DatumOriginShallBeDate = Origin of temporal datum shall be a date. DuplicatedParameterName_4 = Name or alias for parameter \u201c{0}\u201d at index {1} conflict with name \u201c{2}\u201d at index {3}. @@ -74,6 +74,7 @@ DuplicatedSpatialComponents_1 = Compound coordinate reference systems can no EllipsoidalHeightNotAllowed_1 = Compound coordinate reference systems should not contain ellipsoidal height. Use a three-dimensional {0,choice,0#geographic|1#projected} system instead. FileNotFound_2 = Can not find {0} file named \u201c{1}\u201d. FileNotReadable_2 = Can not parse \u201c{1}\u201d as a file in the {0} format. +GridLongitudeSpanTooWide_2 = The grid spans {0}\u00b0 of longitude, which may be too wide for the \u201c{1}\u201d domain. IllegalAxisDirection_2 = Coordinate system of class \u2018{0}\u2019 can not have axis in the {1} direction. IllegalOperationDimension_3 = Dimensions of \u201c{0}\u201d operation can not be ({1} \u2192 {2}). IllegalOperationForValueClass_1 = This operation can not be applied to values of class \u2018{0}\u2019. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties index 6ef6d37..e81f00e 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Resources_fr.properties @@ -71,7 +71,7 @@ CanNotTransformEnvelopeToGeodetic = Ne peut pas transformer l\u2019enveloppe ver CanNotTransformGeometry = Ne peut pas transformer la g\u00e9om\u00e9trie donn\u00e9e. CanNotUseGeodeticParameters_2 = Ne peut pas utiliser les param\u00e8tres g\u00e9od\u00e9siques {0}. La cause est\u202f: {1} ColinearAxisDirections_2 = Les directions d\u2019axes {0} et {1} sont colin\u00e9aires. -CoordinateOperationNotFound_2 = La conversion ou transformation des coordonn\u00e9es du syst\u00e8me \u00ab\u202f{0}\u202f\u00bb vers \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e. +CoordinateOperationNotFound_2 = L\u2019op\u00e9ration sur les coordonn\u00e9es du syst\u00e8me \u00ab\u202f{0}\u202f\u00bb vers \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e. DatumChangesDirectory_1 = Les fichiers de changements de r\u00e9f\u00e9rentiel sont cherch\u00e9s dans le dossier \u00ab\u202f{0}\u202f\u00bb. DatumOriginShallBeDate = L\u2019origine d\u2019un r\u00e9f\u00e9rentiel temporel doit \u00eatre une date. DuplicatedParameterName_4 = Le nom ou un alias pour le param\u00e8tre \u00ab\u202f{0}\u202f\u00bb \u00e0 l\u2019index {1} duplique le nom \u00ab\u202f{2}\u202f\u00bb \u00e0 l\u2019index {3}. @@ -79,6 +79,7 @@ DuplicatedSpatialComponents_1 = Un syst\u00e8me de r\u00e9f\u00e9rence des c EllipsoidalHeightNotAllowed_1 = Un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es ne devrait pas contenir une hauteur ellipso\u00efdale. Utilisez plut\u00f4t un syst\u00e8me {0,choice,0#g\u00e9ographique|1#projet\u00e9} \u00e0 trois dimensions. FileNotFound_2 = Ne peut pas trouver le fichier {0} nomm\u00e9 \u00ab\u202f{1}\u202f\u00bb. FileNotReadable_2 = Ne peut pas lire \u00ab\u202f{1}\u202f\u00bb comme un fichier au format {0}. +GridLongitudeSpanTooWide_2 = La grille s\u2019\u00e9tend sur {0}\u00b0 de longitude, ce qui peut \u00eatre trop pour le domaine de \u00ab\u202f{1}\u202f\u00bb. IllegalAxisDirection_2 = Les syst\u00e8mes de coordonn\u00e9es de classe \u2018{0}\u2019 ne peuvent pas avoir d\u2019axe dans la direction \u00ab\u202f{1}\u202f\u00bb. IllegalOperationDimension_3 = Les dimensions de l\u2019op\u00e9ration \u00ab\u202f{0}\u202f\u00bb ne peuvent pas \u00eatre ({1} \u2192 {2}). IllegalOperationForValueClass_1 = Cette op\u00e9ration ne peut pas s\u2019appliquer aux valeurs de classe \u2018{0}\u2019. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java index 873387b..2cd38c8 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/FactoryDataException.java @@ -26,7 +26,7 @@ import org.opengis.util.FactoryException; * an EPSG database record containing a null value in a column where nulls should not have been allowed.</div> * * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 1.2 * @since 0.7 * @module */ @@ -52,6 +52,17 @@ public class FactoryDataException extends FactoryException { } /** + * Constructs an exception with the specified cause. + * + * @param cause the cause, saved for later retrieval by the {@link #getCause()} method. + * + * @since 1.2 + */ + public FactoryDataException(Throwable cause) { + super(cause); + } + + /** * Construct an exception with the specified detail message and cause. * * @param message the detail message, saved for later retrieval by the {@link #getMessage()} method. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java index affc539..0c2c551 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java @@ -56,7 +56,7 @@ * </table> * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 0.7 + * @version 1.2 * @since 0.6 * @module */ diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java index 0f5838f..36ee404 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LinearTransformBuilder.java @@ -270,7 +270,7 @@ public class LinearTransformBuilder extends TransformBuilder { try { return (LinearTransform) Linearizer.approximate(gridToCRS, domain); } catch (TransformException e) { - throw new FactoryException(e); + throw new LocalizationGridException(e); } } @@ -1467,10 +1467,13 @@ search: for (int j=domain(); --j >= 0;) { } /* * Finished to try all transforms. If all of them failed, wrap the `TransformException`. + * We use a sub-type of `FactoryException` which allows callers to add their own information. + * For example the caller may know that the grid was possibly out of CRS domain of validity + * and wanted to try anyway (it can be difficult to predict in advance if it will work). */ if (bestTransform == null) { - throw new FactoryException(Resources.format(Resources.Keys.CanNotLinearizeLocalizationGrid), - ProjectedTransformTry.getError(linearizers)); + throw new LocalizationGridException(Resources.format(Resources.Keys.CanNotLinearizeLocalizationGrid), + ProjectedTransformTry.getError(linearizers)); } if (needTargetReplace) { transformedArrays = appliedLinearizer.replaceTransformed(targets, transformedArrays); @@ -1547,7 +1550,7 @@ search: for (int j=domain(); --j >= 0;) { break; } default: { - throw new FactoryException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, sourceDim)); + throw new InvalidGeodeticParameterException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, sourceDim)); } } correlations[j] = c; diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java index 73383a1..aaedbe2 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridBuilder.java @@ -713,7 +713,7 @@ public class LocalizationGridBuilder extends TransformBuilder { step = InterpolatedTransform.createGeodeticTransformation(nonNull(factory), shifts); } } catch (TransformException e) { - throw new FactoryException(e); // Should never happen. + throw new LocalizationGridException(e); // Should never happen. } } /* diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridException.java new file mode 100644 index 0000000..a74c47a --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/builder/LocalizationGridException.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.referencing.operation.builder; + +import org.opengis.util.InternationalString; +import org.apache.sis.referencing.factory.FactoryDataException; + + +/** + * Thrown when a localization grid can not be computed, presumably because of a problem with grid data. + * It may be because some grid coordinates are out of CRS domain of validity, causing either + * {@link org.opengis.referencing.operation.MathTransform} to be thrown or {@link Double#NaN} + * coordinate values to be computed. + * + * <h2>Additional information on exception cause</h2> + * It is sometime difficult to determine the root cause of this exception. + * For example grid points slightly outside the CRS domain of validity will not necessarily cause a failure. + * A strategy can be to try to build the grid anyway, and in case of failure declare that the grid was maybe + * too far from CRS domain of validity. Because the potential causes are better known by the code that wants + * a localization grid instead of the {@link LocalizationGridBuilder} class, {@code LocalizationGridException} + * provides a {@link #setPotentialCause(CharSequence)} method for allowing top-level code to attach additional + * information to this exception. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.2 + * @since 1.2 + */ +public class LocalizationGridException extends FactoryDataException { + /** + * Serial number for inter-operability with different versions. + */ + private static final long serialVersionUID = -9069664783475360076L; + + /** + * Additional information about what may be the cause of this exception. + * Example: <cite>"The grid spans more than 180° of longitude"</cite>, + * which may be a cause of map projection failures. + * + * @see #getPotentialCause() + */ + private CharSequence potentialCause; + + /** + * Construct an exception with no detail message. + */ + public LocalizationGridException() { + } + + /** + * Constructs an exception with the specified detail message. + * + * @param message the detail message, saved for later retrieval by the {@link #getMessage()} method. + */ + public LocalizationGridException(String message) { + super(message); + } + + /** + * Constructs an exception with the specified cause. + * + * @param cause the cause, saved for later retrieval by the {@link #getCause()} method. + */ + public LocalizationGridException(Throwable cause) { + super(cause); + } + + /** + * Constructs an exception with the specified detail message and cause. + * The cause is the exception thrown in the underlying database + * (e.g. {@link java.io.IOException} or {@link java.sql.SQLException}). + * + * @param message the detail message, saved for later retrieval by the {@link #getMessage()} method. + * @param cause the cause, saved for later retrieval by the {@link #getCause()} method. + */ + public LocalizationGridException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Specifies additional information about what may be the cause of this exception. + * Example: <cite>"The grid spans more than 180° of longitude"</cite>, + * which may be a cause of map projection failures. + * + * @param details a potential cause, or {@code null} if none. + * The type should be {@link String} or {@link InternationalString}. + */ + public synchronized void setPotentialCause(CharSequence details) { + potentialCause = details; + } + + /** + * Returns the value given to the last call of {@link #setPotentialCause(CharSequence)}. + * + * @return potential cause, or {@code null} if none. + * The type should be {@link String} or {@link InternationalString}. + */ + public synchronized CharSequence getPotentialCause() { + return potentialCause; + } +} diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java b/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java index 1ed6359..13a90a3 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java +++ b/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java @@ -108,7 +108,10 @@ public final class Exceptions extends Static { * will be happened after the provided message. * @return a new exception with the given message, or the given exception if the exception * class does not provide public {@code Exception(String)} constructor. + * + * @deprecated To be removed with no replacement. */ + @Deprecated @SuppressWarnings("unchecked") public static <T extends Throwable> T setMessage(final T exception, String message, final boolean append) { if (append) { @@ -155,7 +158,7 @@ public final class Exceptions extends Static { } /** - * Returns a string which contain the given message on the first line, followed by the + * Returns a string which contains the given message on the first line, followed by the * {@linkplain #getLocalizedMessage(Throwable, Locale) localized message} of the given exception * on the next line. If the exception has a {@linkplain Throwable#getCause() causes}, then * the class name and the localized message of the cause are formatted on the next line diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java index a14466f..0b5c1bf 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java @@ -39,6 +39,7 @@ import org.opengis.referencing.operation.TransformException; import org.opengis.metadata.content.TransferFunctionType; import org.apache.sis.internal.referencing.AxisDirections; import org.apache.sis.internal.util.Numerics; +import org.apache.sis.referencing.operation.builder.LocalizationGridException; import org.apache.sis.referencing.operation.builder.LocalizationGridBuilder; import org.apache.sis.referencing.operation.transform.TransferFunction; import org.apache.sis.referencing.operation.transform.MathTransforms; @@ -864,6 +865,21 @@ public final class Axis extends NamedElement { tr = new GridCacheValue(linearizers, grid, factory); tr = keyLocal.cache(decoder, tr); } + } catch (LocalizationGridException ex) { + /* + * Complete the exception with a possible failure cause before to propagate the exception. + * Example: "The grid spans more than 180° of longitude", which may cause transform errors. + * The possible causes are known only by the linearizer, which is why we could not set it + * at `LocalizationGridException` construction time. + */ + for (final Linearizer linearizer : linearizers) { + final CharSequence reason = linearizer.getPotentialCause(coordinates); + if (reason != null) { + ex.setPotentialCause(reason); + break; // Take the cause of the linearizer that had the highest priority. + } + } + throw ex; } finally { handler.putAndUnlock(tr); } diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java index 26caff7..f04687f 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Dimension.java @@ -32,7 +32,7 @@ import org.apache.sis.util.resources.Vocabulary; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.2 * @since 1.0 * @module */ @@ -71,6 +71,20 @@ public abstract class Dimension extends NamedElement { protected abstract boolean isUnlimited(); /** + * Returns a dimension with its index decremented by 1. This method is invoked for trailing dimensions + * after a previous dimension has been removed from a list. This is useful only for subclasses that + * need to know the index of this dimension in a list of dimensions. + * + * <p>Note: this method may be removed in a future version if we do not need to store index anymore. + * See <a href="https://github.com/Unidata/netcdf-java/issues/951">Issue #951 on netcdf-java</a>.</p> + * + * @return a dimension equals to this one but with its list index (if any) decremented. + */ + protected Dimension decrementIndex() { + return this; + } + + /** * Writes in the given buffer the length of this dimension between bracket. * The length may be unknown (represented by {@code '?'}). */ diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java index d19b8ce..374d625 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java @@ -30,6 +30,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.metadata.spatial.DimensionNameType; import org.apache.sis.internal.referencing.AxisDirections; import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.builder.LocalizationGridException; import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.coverage.grid.IllegalGridGeometryException; @@ -47,7 +48,7 @@ import org.apache.sis.util.ArraysExt; * if a variable dimensions should considered as bands instead of spatiotemporal dimensions. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * * @see Decoder#getGrids() * @@ -563,6 +564,13 @@ findFree: for (int srcDim : axis.gridDimensionIndices) { * @param key one of {@link Resources.Keys#CanNotCreateCRS_3} or {@link Resources.Keys#CanNotCreateGridGeometry_3}. */ private void canNotCreate(final Decoder decoder, final String caller, final short key, final Exception ex) { - warning(decoder.listeners, Grid.class, caller, ex, null, key, decoder.getFilename(), getName(), ex.getLocalizedMessage()); + CharSequence message = null; + if (ex instanceof LocalizationGridException) { + message = ((LocalizationGridException) ex).getPotentialCause(); + } + if (message == null) { + message = ex.getLocalizedMessage(); + } + warning(decoder.listeners, Grid.class, caller, ex, null, key, decoder.getFilename(), getName(), message); } } diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java index 48ff2d6..d02505b 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Linearizer.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.HashMap; import java.util.List; import org.opengis.geometry.Envelope; +import org.opengis.util.InternationalString; import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.cs.CoordinateSystem; @@ -161,6 +162,14 @@ public final class Linearizer { private boolean axisSwap; /** + * The image span in degrees of longitude, or 0 if not computed. + * This is used for giving a hint about why a projection may have failed. + * + * @see #getPotentialCause() + */ + private float longitudeSpan; + + /** * Creates a new linearizer working on the specified datum. * * @param datum the datum to use. Should be consistent with {@link Convention#defaultHorizontalCRS(boolean)}. @@ -197,6 +206,23 @@ public final class Linearizer { } /** + * If this linearizer can give a probable reason why it failed to compute the localization grid, returns that reason. + * Otherwise returns {@code null}. + * + * @param owner for fetching localized resources. + * @return potential error cause, or {@code null} if unknown. + */ + final InternationalString getPotentialCause(final Node owner) { + if (longitudeSpan >= 180 - 6) { // 180° of longitude minus a UTM zone width. + final String name = IdentifiedObjects.getDisplayName(targetCRS, owner.getLocale()); + return org.apache.sis.internal.referencing.Resources.formatInternational( + org.apache.sis.internal.referencing.Resources.Keys.GridLongitudeSpanTooWide_2, + longitudeSpan, (name != null) ? name : type); + } + return null; + } + + /** * Returns a string representation for debugging purposes. */ @Override @@ -231,26 +257,32 @@ public final class Linearizer { */ case UNIVERSAL: { final Envelope bounds = grid.getSourceEnvelope(false); - double x, y, ymin, ymax; + double x, y, xmin, xmax, ymin, ymax; { // For keeping `median` variable local. final double[] median = grid.getControlPoint( (int) Math.round(bounds.getMedian(0)), (int) Math.round(bounds.getMedian(1))); - x = median[xdim]; - y = median[ydim]; - ymin = ymax = y; + x = median[xdim]; xmin = xmax = x; + y = median[ydim]; ymin = ymax = y; } final int[] gc = new int[SOURCE_DIMENSION]; for (int i=0; i<4; i++) { for (int d=0; d<SOURCE_DIMENSION; d++) { gc[d] = (int) Math.round(((i & (1 << d)) == 0) ? bounds.getMinimum(d) : bounds.getMaximum(d)); } - final double yp = grid.getControlPoint(gc[0], gc[1])[ydim]; - if (yp < ymin) ymin = yp; - if (yp > ymax) ymax = yp; + final double[] cp = grid.getControlPoint(gc[0], gc[1]); + double c = cp[xdim]; + if (c < xmin) xmin = c; + if (c > xmax) xmax = c; + c = cp[ydim]; + if (c < ymin) ymin = c; + if (c > ymax) ymax = c; } + longitudeSpan = (float) (xmax - xmin); // For providing a hint in case of failure. /* * If the image is far from equator, replace the middle point by a point close to pole. + * The intend is to avoid using UTM projection for latitudes such as 89°N, because a single + * NaN in transformed coordinates is enough for blocking creation of the localization grid. */ if (ymin >= +Type.POLAR_THRESHOLD) y = ymax; else if (ymax <= -Type.POLAR_THRESHOLD) y = ymin; diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java index 55b301a..9de8f12 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java @@ -390,7 +390,7 @@ public abstract class Variable extends Node { public final Unit<?> getUnit() { if (!unitParsed) { unitParsed = true; // Set first for avoiding to report errors many times. - final String symbols = getUnitsString(); + String symbols = getUnitsString(); Exception error = null; if (symbols != null) try { unit = parseUnit(symbols); @@ -402,6 +402,9 @@ public abstract class Variable extends Node { } catch (ParserException ex) { if (error == null) error = ex; else error.addSuppressed(ex); + if (symbols == null) { + symbols = ex.getParsedString(); + } } if (error != null) { error(Variable.class, "getUnit", error, Errors.Keys.CanNotAssignUnitToVariable_2, getName(), symbols); @@ -581,8 +584,8 @@ public abstract class Variable extends Node { } /* * Get all dimensions of this variable in netCDF order, then replace them by dimensions from an axis variable. - * If we are in the situation #1 documented in javadoc, 'isIncomplete' will be 'false' after execution of this - * loop and all dimensions should be the same than the values returned by 'Variable.getGridDimensions()'. + * If we are in the situation #1 documented in javadoc, `isIncomplete` will be `false` after execution of this + * loop and all dimensions should be the same than the values returned by `Variable.getGridDimensions()`. */ boolean isIncomplete = false; final List<Dimension> fromVariable = getGridDimensions(); @@ -605,7 +608,7 @@ public abstract class Variable extends Node { } /* * The first time that we find a label that may allow us to associate this variable dimension with a - * grid dimension, build a map of all labels associated to dimensions. We reuse the existing 'domain' + * grid dimension, build a map of all labels associated to dimensions. We reuse the existing `domain` * map; there is no confusion since the keys are not of the same class. */ if (isIncomplete) { @@ -702,10 +705,10 @@ public abstract class Variable extends Node { Dimension expected = toKeep.get(i); expected = adjustment.gridToVariable.getOrDefault(expected, expected); /* - * At this point, 'expected' is a dimension of the variable that we expect to find at - * current index 'i'. If we do not find that dimension, then the unexpected dimension + * At this point, `expected` is a dimension of the variable that we expect to find at + * current index `i`. If we do not find that dimension, then the unexpected dimension * is assumed to be a band. We usually remove at most one element. If removal results - * in a list too short, it would be a bug in the way we computed 'toKeep'. + * in a list too short, it would be a bug in the way we computed `toKeep`. */ while (!expected.equals(dimensions.get(i))) { if (!copied) { @@ -716,17 +719,20 @@ public abstract class Variable extends Node { * It is possible that we never reach this point if the unexpected dimension is last. * However in such case the dimension to declare is the last one in netCDF order, * which corresponds to the first dimension (i.e. dimension 0) in "natural" order. - * Since the 'bandDimension' field is initialized to zero, its value is correct. + * Since the `bandDimension` field is initialized to zero, its value is correct. */ bandDimension = dataDimension - 1 - i; // Convert netCDF order to "natural" order. dimensions.remove(i); + for (int j = dimensions.size(); --j >= i;) { + dimensions.set(j, dimensions.get(j).decrementIndex()); + } if (dimensions.size() < numToKeep) { throw new InternalDataStoreException(); // Should not happen (see above comment). } } } /* - * At this point 'dimensions' may still be longer than 'toKeep' but it does not matter. + * At this point `dimensions` may still be longer than `toKeep` but it does not matter. * We only need that for any index i < numToKeep, dimensions.get(i) corresponds to the * dimension at the same index in the grid. */ diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java index caa7c54..579e24e 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DimensionWrapper.java @@ -69,7 +69,7 @@ final class DimensionWrapper extends org.apache.sis.internal.netcdf.Dimension { * dimensions that are private to a variable, because those dimensions may be unnamed. * Consequently value -1 should be used only for shared dimensions. * - * <a href="https://github.com/Unidata/netcdf-java/issues/951">Issue #951 on netcdf-java</a> + * @see <a href="https://github.com/Unidata/netcdf-java/issues/951">Issue #951 on netcdf-java</a> */ private final int index; @@ -109,6 +109,17 @@ final class DimensionWrapper extends org.apache.sis.internal.netcdf.Dimension { } /** + * Returns a dimension with its index decremented by 1. This method is invoked for trailing dimensions + * after a previous dimension has been removed from a list. + * + * @return a dimension equals to this one but with its list index (if any) decremented. + */ + @Override + protected org.apache.sis.internal.netcdf.Dimension decrementIndex() { + return new DimensionWrapper(netcdf, index - 1); + } + + /** * Returns {@code true} if the given object represents the same dimension than this object. * If the dimension is shared, then it has a unique name and {@link Dimension#equals(Object)} * can distinguish dimensions based on their name. But if the dimension is private to a variable,