sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: First draft of a Variable.getGridDimensions(…) capable to handle grid dimensions that are not the same than the variable dimensions.
Date Sun, 17 Feb 2019 23:41:00 GMT
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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 1cf91df  First draft of a Variable.getGridDimensions(…) capable to handle grid
dimensions that are not the same than the variable dimensions.
1cf91df is described below

commit 1cf91df34a58928c8f6527378268255946f51912
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Feb 18 00:40:07 2019 +0100

    First draft of a Variable.getGridDimensions(…) capable to handle grid dimensions that
are not the same than the variable dimensions.
---
 .../org/apache/sis/internal/netcdf/Convention.java | 81 ++++++++++++++++----
 .../org/apache/sis/internal/netcdf/Variable.java   | 87 ++++++++++++++++++++++
 2 files changed, 154 insertions(+), 14 deletions(-)

diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
index 26ae579..76a80d6 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
@@ -130,15 +130,66 @@ public class Convention {
      *
      * <p>The default implementation returns {@code null}.</p>
      *
-     * @param  variable  the variable for which the list of axis variables are desired, in
CRS order.
+     * @param  data  the variable for which the list of axis variables are desired, in CRS
order.
      * @return names of the variables containing axis values, or {@code null} if this
      *         method performs applies no special convention for the given variable.
      */
-    public String[] namesOfAxisVariables(Variable variable) {
+    public String[] namesOfAxisVariables(Variable data) {
         return null;
     }
 
     /**
+     * Returns the attribute-specified name of the dimension at the given index, or {@code
null} if unspecified.
+     * This is not the name of the dimension encoded in netCDF binary file format, but rather
a name specified
+     * by a customized attribute. This customized name can be used when the dimensions of
the raster data are
+     * not the same than the dimensions of the localization grid. In such case, the names
returned by this method
+     * are used for mapping the raster dimensions to the localization grid dimensions.
+     *
+     * <div class="note"><b>Example:</b>
+     * consider the following netCDF file (simplified):
+     *
+     * {@preformat netcdf
+     *   dimensions:
+     *     grid_y =  161 ;
+     *     grid_x =  126 ;
+     *     data_y = 1599 ;
+     *     data_x = 1250 ;
+     *   variables:
+     *     float Latitude(grid_y, grid_x) ;
+     *       long_name = "Latitude (degree)" ;
+     *       dim0 = "Line grids" ;
+     *       dim1 = "Pixel grids" ;
+     *       resampling_interval = 10 ;
+     *     float Longitude(grid_y, grid_x) ;
+     *       long_name = "Longitude (degree)" ;
+     *       dim0 = "Line grids" ;
+     *       dim1 = "Pixel grids" ;
+     *       resampling_interval = 10 ;
+     *     ushort SST(data_y, data_x) ;
+     *       long_name = "Sea Surface Temperature" ;
+     *       dim0 = "Line grids" ;
+     *       dim1 = "Pixel grids" ;
+     * }
+     *
+     * In this case, even if {@link #namesOfAxisVariables(Variable)} explicitly returns {@code
{"Latitude", "Longitude"}}
+     * we are still unable to associate the {@code SST} variable to those axes because they
have no dimension in common.
+     * However if we interpret {@code dim0} and {@code dim1} attributes as <cite>"Name
of dimension 0"</cite> and
+     * <cite>"Name of dimension 1"</cite> respectively, then we can associate
the same dimension <strong>names</strong>
+     * to all those variables: namely {@code "Line grids"} and {@code "Pixel grids"}. Using
those names, we deduce that
+     * the {@code (data_y, data_x)} dimensions in the {@code SST} variable are mapped to
the {@code (grid_y, grid_x)}
+     * dimensions in the localization grid.</div>
+     *
+     * This feature is an extension to CF-conventions.
+     *
+     * @param  dataOrAxis  the variable for which to get the attribute-specified name of
the dimension.
+     * @param  index       zero-based index of the dimension for which to get the name.
+     * @return dimension name as specified by attributes, or {@code null} if none.
+     */
+    public String nameOfDimension(final Variable dataOrAxis, final int index) {
+        return dataOrAxis.getAttributeAsString("dim" + index);
+    }
+
+    /**
      * Returns the range of valid values, or {@code null} if unknown.
      * The default implementation takes the range of values from the following properties,
in precedence order:
      *
@@ -155,17 +206,18 @@ public class Convention {
      * Otherwise if this method returns the range of real values, then that range shall be
an instance
      * of {@link MeasurementRange} for allowing the caller to distinguish the two cases.
      *
-     * @param  source  the variable to get valid range of values for.
+     * @param  data  the variable to get valid range of values for.
+     *               This is usually a variable containing raster data.
      * @return the range of valid values, or {@code null} if unknown.
      *
      * @see Variable#getRangeFallback()
      */
-    public NumberRange<?> getValidValues(final Variable source) {
+    public NumberRange<?> getValidValues(final Variable data) {
         Number minimum = null;
         Number maximum = null;
         Class<? extends Number> type = null;
         for (final String attribute : RANGE_ATTRIBUTES) {
-            for (final Object element : source.getAttributeValues(attribute, true)) {
+            for (final Object element : data.getAttributeValues(attribute, true)) {
                 if (element instanceof Number) {
                     Number value = (Number) element;
                     if (element instanceof Float) {
@@ -192,12 +244,12 @@ public class Convention {
                  * data is not wider, then assume that the minimum and maximum are real values.
                  */
                 final int rangeType = Numbers.getEnumConstant(type);
-                if (rangeType >= source.getDataType().number &&
-                    rangeType >= Math.max(Numbers.getEnumConstant(source.getAttributeType(CDM.SCALE_FACTOR)),
-                                          Numbers.getEnumConstant(source.getAttributeType(CDM.ADD_OFFSET))))
+                if (rangeType >= data.getDataType().number &&
+                    rangeType >= Math.max(Numbers.getEnumConstant(data.getAttributeType(CDM.SCALE_FACTOR)),
+                                          Numbers.getEnumConstant(data.getAttributeType(CDM.ADD_OFFSET))))
                 {
                     @SuppressWarnings({"unchecked", "rawtypes"})
-                    final NumberRange<?> range = new MeasurementRange(type, minimum,
true, maximum, true, source.getUnit());
+                    final NumberRange<?> range = new MeasurementRange(type, minimum,
true, maximum, true, data.getUnit());
                     return range;
                 } else {
                     @SuppressWarnings({"unchecked", "rawtypes"})
@@ -225,20 +277,21 @@ public class Convention {
      * The returned function will be a component of the {@link org.apache.sis.coverage.SampleDimension}
      * to be created for each variable.
      *
-     * @param  source  the variable from which to determine the transfer function.
+     * @param  data  the variable from which to determine the transfer function.
+     *               This is usually a variable containing raster data.
      *
      * @return a transfer function built from the attributes defined in the given variable.
Never null;
-     *         if no information is found in the given {@code source} variable, then the
return value
+     *         if no information is found in the given {@code data} variable, then the return
value
      *         shall be an identity function.
      */
-    public TransferFunction getTransferFunction(final Variable source) {
+    public TransferFunction getTransferFunction(final Variable data) {
         /*
          * If scale_factor and/or add_offset variable attributes are present, then this is
          * a "packed" variable. Otherwise the transfer function is the identity transform.
          */
         final TransferFunction tr = new TransferFunction();
-        final double scale  = source.getAttributeAsNumber(CDM.SCALE_FACTOR);
-        final double offset = source.getAttributeAsNumber(CDM.ADD_OFFSET);
+        final double scale  = data.getAttributeAsNumber(CDM.SCALE_FACTOR);
+        final double offset = data.getAttributeAsNumber(CDM.ADD_OFFSET);
         if (!Double.isNaN(scale))  tr.setScale (scale);
         if (!Double.isNaN(offset)) tr.setOffset(offset);
         return tr;
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 240c967..7b47574 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
@@ -17,9 +17,11 @@
 package org.apache.sis.internal.netcdf;
 
 import java.util.Map;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Collection;
 import java.util.List;
+import java.util.Arrays;
 import java.util.Locale;
 import java.util.regex.Pattern;
 import java.io.IOException;
@@ -351,6 +353,91 @@ public abstract class Variable extends NamedElement {
     public abstract List<Dimension> getGridDimensions();
 
     /**
+     * Returns the dimensions of the grid used with this variable, or {@code null} if it
can not be determined.
+     * Usually this is the same as {@link #getGridDimensions()} and this method does not
need to be invoked.
+     * This method is useful only if the localization grid does not use the same dimensions
than this variable.
+     * It happens if the netCDF files provides a decimated localization grid, for example
where the longitudes
+     * and latitudes variables specify the values of only 1/10 of cells.
+     *
+     * <p>This method is invoked if we failed to build a localization grid with the
usual CF-conventions.
+     * In that case, {@code axes} should list all variables that may potentially an axis
for this variable
+     * even if they do not use the same dimensions. If this method can map all dimensions
of this variable
+     * to dimensions of the given {@code axes}, then the corresponding axis dimensions are
returned in the
+     * order they would have if they were dimensions of this variable. If a mapping can not
be established
+     * for all dimensions, this method return {@code null}.</p>
+     *
+     * <p>This method considers that we have a mapping when two dimensions have the
same "name". That name
+     * is not the usual {@link Dimension#getName()} string encoded in netCDF format, but
rather the value
+     * of one of the attributes. That name is defined by {@link Convention#nameOfDimension(Variable,
int)};
+     * see its javadoc for examples.</p>
+     *
+     * @param  axes        the variables that may define axes of the grid, in no particular
order.
+     *                     This collection may contain more axes than necessary.
+     * @param  convention  the convention to use for assigning names to dimensions.
+     * @return dimensions of the grid in netCDF order, or {@code null} if some dimensions
could not be mapped.
+     *         If non-null, all dimensions come from {@code axes} variables.
+     *
+     * @see Convention#nameOfDimension(Variable, int)
+     */
+    final List<Dimension> getGridDimensions(final Collection<Variable> axes,
final Convention convention) {
+        /*
+         * Collect all axis dimensions, in no particular order. We use this map for determining
+         * if a dimension of this variable can be used as-is, without the need to search
for an
+         * association through Convention.nameOfDimension(…). It may be the case for example
if
+         * the variable has a vertical or temporal axis which has not been decimated contrarily
+         * to longitude and latitude axes. Note that this map is recycled later for other
use.
+         */
+        final Map<Object,Dimension> domain = new HashMap<>(axes.size() * 3);
+        for (final Variable axis : axes) {
+            for (final Dimension dim : axis.getGridDimensions()) {
+                domain.put(dim, dim);
+            }
+        }
+        /*
+         * Get all dimensions of this variable in netCDF order, then set to null the dimensions
+         * that are not a dimension of the given axes. The non-null dimensions are removed
from
+         * 'domain', so we do not try to use them twice.
+         */
+        boolean isIncomplete = false;
+        final Dimension[] dimensions = CollectionsExt.toArray(getGridDimensions(), Dimension.class);
+        for (int i=0; i<dimensions.length; i++) {
+            isIncomplete |= (dimensions[i] = domain.remove(dimensions[i])) == null;
+        }
+        /*
+         * If there is at least one variable dimension that we did not found directly in
the axes dimensions,
+         * check if we can relate two dimensions together by their name. Following code is
actually the main
+         * purpose of this method, otherwise the result is identical to 'getGridDimensions()'.
+         */
+        if (isIncomplete) {
+            for (final Variable axis : axes) {
+                final List<Dimension> gd = axis.getGridDimensions();
+                for (int i=gd.size(); --i >= 0;) {
+                    final Dimension dim = gd.get(i);
+                    if (domain.containsKey(dim)) {
+                        final String name = convention.nameOfDimension(axis, i);
+                        if (name != null) {
+                            final Dimension existing = domain.put(name, dim);
+                            if (existing != null && !existing.equals(dim)) {
+                                return null;                                        // Name
collision.
+                            }
+                        }
+                    }
+                }
+            }
+            for (int i=0; i<dimensions.length; i++) {
+                if (dimensions[i] == null) {
+                    final String label = convention.nameOfDimension(this, i);       // May
be null.
+                    if ((dimensions[i] = domain.remove(label)) != null) {
+                        continue;
+                    }
+                    return null;        // Can not to relate that variable dimension to a
grid dimension.
+                }
+            }
+        }
+        return Arrays.asList(dimensions);
+    }
+
+    /**
      * Returns the names of all attributes associated to this variable.
      *
      * @return names of all attributes associated to this variable.


Mime
View raw message