sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 04/04: Defines a customized CoordSysBuilder (from UCAR API) as a fallback when the UCAR library could not create a coordinate system using default conventions. This is required (but not sufficient) for decoding GCOM-W files.
Date Thu, 14 Mar 2019 22:45:01 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

commit a6762e8714c933ae715ca98c787a00bec8d9d5a1
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Mar 14 23:25:35 2019 +0100

    Defines a customized CoordSysBuilder (from UCAR API) as a fallback when the UCAR library
could not create a coordinate system using default conventions.
    This is required (but not sufficient) for decoding GCOM-W files.
---
 .../org/apache/sis/internal/netcdf/Convention.java |  2 +-
 .../internal/netcdf/ucar/CSBuilderFallback.java    | 90 ++++++++++++++++++++++
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   | 11 +++
 .../sis/internal/netcdf/ucar/GridWrapper.java      | 48 +++++++++++-
 .../apache/sis/storage/netcdf/GridResource.java    | 34 ++++----
 5 files changed, 165 insertions(+), 20 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 fdb654e..0f7b8ff 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
@@ -66,7 +66,7 @@ public class Convention {
     /**
      * The convention to use when no specific conventions were found.
      */
-    static final Convention DEFAULT = new Convention();
+    public static final Convention DEFAULT = new Convention();
 
     /**
      * Names of groups where to search for metadata, in precedence order.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/CSBuilderFallback.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/CSBuilderFallback.java
new file mode 100644
index 0000000..581304e
--- /dev/null
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/CSBuilderFallback.java
@@ -0,0 +1,90 @@
+/*
+ * 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.internal.netcdf.ucar;
+
+import ucar.nc2.constants.AxisType;
+import ucar.nc2.dataset.NetcdfDataset;
+import ucar.nc2.dataset.CoordSysBuilder;
+import ucar.nc2.dataset.VariableEnhanced;
+import org.apache.sis.internal.netcdf.Convention;
+import org.apache.sis.internal.netcdf.Variable;
+import org.apache.sis.internal.netcdf.VariableRole;
+import org.apache.sis.util.CharSequences;
+
+
+/**
+ * A UCAR coordinate system builder which uses Apache SIS mechanism for identifying
+ * which variable may be an axis variable.
+ *
+ * <div class="note"><b>Note:</b>
+ * this could could be registered as a {@link ucar.nc2.dataset.CoordSysBuilderIF} service
+ * and automatically loaded by the UCAR library using {@link java.util.ServiceLoader}.
+ * The UCAR library then invoke the following method by reflection:
+ *
+ * {@preformat java
+ *     public static boolean isMine(NetcdfFile file) {…}
+ * }
+ *
+ * However we rather create instances of this class explicitly when required in order to
+ * avoid interfering with UCAR global configuration (users want to apply their own settings)
+ * and because we need to specify the {@link DecoderWrapper}.</div>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class CSBuilderFallback extends CoordSysBuilder {
+    /**
+     * The decoder for which to apply this fallback.
+     */
+    private final DecoderWrapper decoder;
+
+    /**
+     * Creates a new UCAR coordinate system builder for the given decoder.
+     */
+    CSBuilderFallback(final DecoderWrapper decoder) {
+        this.decoder = decoder;
+    }
+
+    /**
+     * Delegates to {@link Convention#roleOf(Variable)} in order to determine which variables
are axes.
+     */
+    @Override
+    protected void findCoordinateAxes(final NetcdfDataset ds) {
+        for (final VarProcess vp : varList) {
+            if (!vp.isCoordinateVariable) {
+                final VariableWrapper variable = decoder.getWrapperFor(vp.v);
+                if (variable.getRole() == VariableRole.AXIS) {
+                    vp.isCoordinateVariable = true;
+                }
+            }
+        }
+        super.findCoordinateAxes(ds);
+    }
+
+    /**
+     * Identifies what kind of axis the given variable is.
+     */
+    @Override
+    protected AxisType getAxisType(final NetcdfDataset ds, final VariableEnhanced variable)
{
+        final String name = variable.getShortName();
+        if (CharSequences.startsWith(name, "Longitude", true)) return AxisType.Lon;
+        if (CharSequences.startsWith(name, "Latitude",  true)) return AxisType.Lat;
+        return super.getAxisType(ds, variable);
+    }
+}
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
index 2ece9d3..6433e1a 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
@@ -40,6 +40,7 @@ import ucar.nc2.ft.FeatureDatasetFactoryManager;
 import ucar.nc2.ft.FeatureCollection;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.internal.netcdf.Convention;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.internal.netcdf.Grid;
@@ -453,6 +454,16 @@ public final class DecoderWrapper extends Decoder implements CancelTask
{
                     ds.enhance(mode);
                 }
                 systems = ds.getCoordinateSystems();
+                /*
+                 * If the UCAR library does not see any coordinate system in the file, verify
if there is
+                 * a custom convention recognizing the axes. CSBuilderFallback uses the mechanism
defined
+                 * by Apache SIS for determining variable role.
+                 */
+                if (systems.isEmpty() && convention() != Convention.DEFAULT) {
+                    final CSBuilderFallback builder = new CSBuilderFallback(this);
+                    builder.buildCoordinateSystems(ds);
+                    systems = ds.getCoordinateSystems();
+                }
             }
             geometries = new Grid[(systems != null) ? systems.size() : 0];
             for (int i=0; i<geometries.length; i++) {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java
index e9eb3c0..0beb241 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java
@@ -17,8 +17,10 @@
 package org.apache.sis.internal.netcdf.ucar;
 
 import java.util.List;
+import java.util.ArrayList;
 import java.util.Map;
 import java.util.HashMap;
+import java.util.Objects;
 import java.io.IOException;
 import ucar.nc2.Dimension;
 import ucar.nc2.VariableIF;
@@ -115,12 +117,12 @@ final class GridWrapper extends Grid {
      * @return localization grid with given dimension order (may be {@code this}), or {@code
null}.
      */
     private GridWrapper derive(final List<Dimension> dimensions) {
-        if (domain.equals(dimensions)) {
+        if (containsAll(dimensions, true)) {
             return this;
         }
         return reordered.computeIfAbsent(dimensions, k -> {
             // Want same set of dimensions in different order.
-            if (domain.size() == k.size() && domain.containsAll(k)) {
+            if (containsAll(k, false)) {
                 return new GridWrapper(this, k);
             }
             return null;
@@ -143,6 +145,48 @@ final class GridWrapper extends Grid {
     }
 
     /**
+     * Returns {@code true} if this grid contains all given dimensions. The {@code ordered}
argument
+     * specifies whether the dimensions must be in exact same order or can be in any order.
+     */
+    private boolean containsAll(List<Dimension> dimensions, final boolean ordered)
{
+        final int n = domain.size();
+        if (dimensions.size() != n) {
+            return false;
+        }
+        boolean copied = false;
+next:   for (int i=n; --i >= 0;) {
+            final Dimension d1 = domain.get(i);
+            if (ordered) {
+                if (equals(d1, dimensions.get(i))) {
+                    continue;
+                }
+            } else {
+                for (int j = dimensions.size(); --j >= 0;) {
+                    if (equals(d1, dimensions.get(j))) {
+                        if (!copied) {
+                            dimensions = new ArrayList<>(dimensions);
+                            copied = true;
+                        }
+                        dimensions.remove(j);
+                        continue next;
+                    }
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns {@code true} if the given dimensions are equal, comparing only names and lengths.
+     * This is different than {@link Dimension#equals(Object)} which compares more aspects
like
+     * whether the dimension are unlimited.
+     */
+    private static boolean equals(final Dimension d1, final Dimension d2) {
+        return Objects.equals(d1.getShortName(), d2.getShortName()) && d1.getLength()
== d2.getLength();
+    }
+
+    /**
      * Returns a name for this grid geometry, for information purpose only.
      */
     @Override
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
index 8f59aff..9868ecc 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
@@ -174,14 +174,14 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
             final DataType type = variable.getDataType();
             /*
              * At this point we found a variable for which to create a resource. Most of
the time, there is nothing else to do;
-             * the resource will have a single variable and the same name than that unique
variable. However in some cases, the
-             * we should put other variables together with the one we just found. Example:
+             * the resource will have a single variable and the same name than that unique
variable.  However in some cases, we
+             * should put other variables together with the one we just found. Example:
              *
              *    1) baroclinic_eastward_sea_water_velocity
              *    2) baroclinic_northward_sea_water_velocity
              *
              * We use the "eastward" and "northward" keywords for recognizing such pairs,
providing that everything else in the
-             * name is the same and the grid geometry are the same.
+             * name is the same and the grid geometries are the same.
              */
             for (final String keyword : VECTOR_COMPONENT_NAMES) {
                 final int prefixLength = name.indexOf(keyword);
@@ -276,21 +276,21 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
      * Creates a single sample dimension for the given variable.
      *
      * @param  builder  the builder to use for creating the sample dimension.
-     * @param  data     the data for which to create a sample dimension.
+     * @param  band     the data for which to create a sample dimension.
      * @throws TransformException if an error occurred while using the transfer function.
      */
-    private SampleDimension createSampleDimension(final SampleDimension.Builder builder,
final Variable data) throws TransformException {
+    private SampleDimension createSampleDimension(final SampleDimension.Builder builder,
final Variable band) throws TransformException {
         /*
          * Take the minimum and maximum values as determined by Apache SIS through the Convention
class.  The UCAR library
          * is used only as a fallback. We give precedence to the range computed by Apache
SIS instead than the range given
          * by UCAR because we need the range of packed values instead than the range of converted
values.
          */
-        NumberRange<?> range = convention.validRange(data);
+        NumberRange<?> range = convention.validRange(band);
         if (range == null) {
-            range = data.getRangeFallback();                                // Fallback to
UCAR library may happen here.
+            range = band.getRangeFallback();                                // Fallback to
UCAR library may happen here.
         }
         if (range != null) {
-            final MathTransform1D mt = convention.transferFunction(data).getTransform();
+            final MathTransform1D mt = convention.transferFunction(band).getTransform();
             if (!mt.isIdentity() && range instanceof MeasurementRange<?>) {
                 /*
                  * Heuristic rule defined in UCAR documentation (see EnhanceScaleMissing
interface):
@@ -311,7 +311,7 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
                     isMaxIncluded = isMinIncluded;
                     isMinIncluded = sb;
                 }
-                if (data.getDataType().number < Numbers.FLOAT && minimum >=
Long.MIN_VALUE && maximum <= Long.MAX_VALUE) {
+                if (band.getDataType().number < Numbers.FLOAT && minimum >=
Long.MIN_VALUE && maximum <= Long.MAX_VALUE) {
                     range = NumberRange.create(Math.round(minimum), isMinIncluded, Math.round(maximum),
isMaxIncluded);
                 } else {
                     range = NumberRange.create(minimum, isMinIncluded, maximum, isMaxIncluded);
@@ -323,12 +323,12 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
              * unsigned integer with a signed one).
              */
             if (range.isEmpty()) {
-                data.warning(GridResource.class, "getSampleDimensions", Resources.Keys.IllegalValueRange_4,
-                        data.getFilename(), data.getName(), range.getMinValue(), range.getMaxValue());
+                band.warning(GridResource.class, "getSampleDimensions", Resources.Keys.IllegalValueRange_4,
+                        band.getFilename(), band.getName(), range.getMinValue(), range.getMaxValue());
             } else {
-                String name = data.getDescription();
-                if (name == null) name = data.getName();
-                builder.addQuantitative(name, range, mt, data.getUnit());
+                String name = band.getDescription();
+                if (name == null) name = band.getName();
+                builder.addQuantitative(name, range, mt, band.getUnit());
             }
         }
         /*
@@ -338,9 +338,9 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
          * values created by 'replaceNaN'.
          */
         boolean setBackground = true;
-        int ordinal = data.hasRealValues() ? 0 : -1;
+        int ordinal = band.hasRealValues() ? 0 : -1;
         final CharSequence[] names = new CharSequence[2];
-        for (final Map.Entry<Number,Object> entry : data.getNodataValues().entrySet())
{
+        for (final Map.Entry<Number,Object> entry : band.getNodataValues().entrySet())
{
             final Number n;
             if (ordinal >= 0) {
                 n = MathFunctions.toNanFloat(ordinal++);        // Must be consistent with
Variable.replaceNaN(Object).
@@ -367,7 +367,7 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
             }
             builder.addQualitative(name, n, n);
         }
-        return builder.setName(data.getName()).build();
+        return builder.setName(band.getName()).build();
     }
 
     /**


Mime
View raw message