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 <[email protected]> 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(); } /**
