blob: c9c8ac9a0dff13d376a3cf774633544e02803388 [file] [log] [blame]
/*
* 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;
}
}