| /* |
| * 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.storage.netcdf.base; |
| |
| import java.util.Map; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import ucar.nc2.constants.CF; // String constants are copied by the compiler with no UCAR reference left. |
| import javax.measure.Unit; |
| import org.opengis.referencing.cs.AxisDirection; |
| import org.apache.sis.referencing.util.AxisDirections; |
| import org.apache.sis.measure.Units; |
| |
| |
| /** |
| * Type of coordinate system axis, in the order they should appears for a "normalized" coordinate reference system. |
| * The enumeration name matches the name of the {@code "axis"} attribute in CF-convention. |
| * Enumeration order is the desired order of coordinate values. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| */ |
| public enum AxisType { |
| /** |
| * X (usually longitude) coordinate axis. |
| */ |
| X, |
| |
| /** |
| * Y (usually latitude) coordinate axis. |
| */ |
| Y, |
| |
| /** |
| * Z (usually height) coordinate axis. |
| */ |
| Z, |
| |
| /** |
| * Time coordinate axis. |
| */ |
| T; |
| |
| /** |
| * Mapping from values of the {@code "_CoordinateAxisType"} attribute or axis name to the abbreviation. |
| * Keys are lower cases and values are controlled vocabulary documented in {@link Axis#abbreviation}. |
| * |
| * <div class="note">"GeoX" and "GeoY" stands for projected coordinates, not geocentric coordinates.</div> |
| * |
| * @see abbreviation(String) |
| */ |
| private static final Map<String,Character> TYPES = new HashMap<>(26); |
| |
| /** |
| * The enumeration values for given abbreviations. |
| */ |
| private static final Map<Character,AxisType> VALUES = new HashMap<>(13); |
| static { |
| addAxisTypes(X, 'λ', "longitude", "lon", "long"); |
| addAxisTypes(Y, 'φ', "latitude", "lat"); |
| addAxisTypes(Z, 'H', "pressure", "height", "altitude", "barometric_altitude", "elevation", "elev", "geoz"); |
| addAxisTypes(Z, 'D', "depth", "depth_below_geoid"); |
| addAxisTypes(X, 'E', "geox", "projection_x_coordinate"); |
| addAxisTypes(Y, 'N', "geoy", "projection_y_coordinate"); |
| addAxisTypes(T, 't', "t", "time", "runtime"); |
| addAxisTypes(X, 'x', "x"); |
| addAxisTypes(Y, 'y', "y"); |
| addAxisTypes(Z, 'z', "z"); |
| } |
| |
| /** |
| * Adds a sequence of axis types or variable names for the given abbreviation. |
| */ |
| private static void addAxisTypes(final AxisType value, final char abbreviation, final String... names) { |
| final Character c = abbreviation; |
| for (final String name : names) { |
| TYPES.put(name, c); |
| } |
| VALUES.put(c, value); |
| } |
| |
| /** |
| * Returns the axis type (identified by its abbreviation) for an axis of the given name, or null if unknown. |
| * The returned code is one of the controlled vocabulary documented in {@link Axis#abbreviation}. |
| * |
| * @param type the {@code "_CoordinateAxisType"} attribute value or another description used as fallback. |
| * @return axis abbreviation for the given type or name, or {@code null} if none. |
| */ |
| private static Character abbreviation(final String type) { |
| return (type != null) ? TYPES.get(type.toLowerCase(Locale.US)) : null; |
| } |
| |
| /** |
| * Returns {@code true} if the given abbreviation is null or is ambiguous. |
| * The latter happens when the {@code axis} attribute value or the variable name is "X", "Y" or "Z", |
| * which could be longitude, latitude or height as well as axes in any other coordinate system. |
| * |
| * @param abbreviation the axis abbreviation, or {@code null}. |
| * @return whether the given abbreviation is considered ambiguous. |
| */ |
| private static boolean isNullOrAmbiguous(final Character abbreviation) { |
| return (abbreviation == null) || (abbreviation >= 'x' && abbreviation <= 'z'); |
| } |
| |
| /** |
| * Returns the axis type (identified by its abbreviation) for the given axis, or 0 if unknown. |
| * The returned code is one of the controlled vocabulary documented in {@link Axis#abbreviation}. |
| * |
| * @param axis axis for which to get an abbreviation. |
| * @return abbreviation for the given axis, or 0 if none. |
| * |
| * @see <a href="https://issues.apache.org/jira/browse/SIS-552">SIS-552</a> |
| */ |
| public static char abbreviation(final Variable axis) { |
| /* |
| * In Apache SIS implementation, the abbreviation determines the axis type. If a "_CoordinateAxisType" attribute |
| * exists, il will have precedence over all other heuristic rules in this method because it is the most specific |
| * information about axis type. Otherwise the "standard_name" attribute is our first fallback since valid values |
| * are standardized to "longitude" and "latitude" among others. |
| */ |
| Character abbreviation = abbreviation(axis.getAxisType()); |
| if (isNullOrAmbiguous(abbreviation)) { |
| Character fallback = abbreviation; |
| abbreviation = abbreviation(axis.getAttributeAsString(CF.STANDARD_NAME)); // No fallback on variable name. |
| if (fallback == null) fallback = abbreviation; |
| /* |
| * If the abbreviation is still unknown, look at the "long_name", "description" or "title" attribute. Those |
| * attributes are not standardized, so they are less reliable than "standard_name". But they are still more |
| * reliable that the variable name since the long name may be "Longitude" or "Latitude" while the variable |
| * name is only "x" or "y". |
| */ |
| if (isNullOrAmbiguous(abbreviation)) { |
| abbreviation = abbreviation(axis.getDescription()); |
| if (fallback == null) fallback = abbreviation; |
| if (isNullOrAmbiguous(abbreviation)) { |
| /* |
| * Actually the "degree_east" and "degree_north" units of measurement are the most reliable way to |
| * identify geographic system, but we nevertheless check them almost last because the direction is |
| * already verified by Axis constructor. By checking the variable attributes first, we give a chance |
| * to Axis constructor to report a warning if there is an inconsistency. |
| */ |
| if (Units.isAngular(axis.getUnit())) { |
| final AxisDirection direction = AxisDirections.absolute(Axis.direction(axis.getUnitsString())); |
| if (AxisDirection.EAST.equals(direction)) { |
| return 'λ'; |
| } else if (AxisDirection.NORTH.equals(direction)) { |
| return 'φ'; |
| } |
| } |
| /* |
| * We test the variable name last because that name is more at risk of being an uninformative "x" or "y" name. |
| * If even the variable name is not sufficient, we use some easy to recognize units. |
| */ |
| abbreviation = abbreviation(axis.getName()); |
| if (fallback == null) fallback = abbreviation; |
| if (isNullOrAmbiguous(abbreviation)) { |
| final Unit<?> unit = axis.getUnit(); |
| if (Units.isTemporal(unit)) { |
| return 't'; |
| } else if (Units.isPressure(unit)) { |
| return 'z'; |
| } else if (fallback != null) { |
| return fallback; |
| } else { |
| return 0; |
| } |
| } |
| } |
| } |
| } |
| return abbreviation; |
| } |
| |
| /** |
| * Returns the enumeration value for the given variable, or {@code null} if none. |
| */ |
| static AxisType valueOf(final Variable axis) { |
| final char abbreviation = abbreviation(axis); |
| return (abbreviation != 0) ? VALUES.get(abbreviation) : null; |
| } |
| } |