| /* |
| * 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.referencing; |
| |
| import java.util.Collection; |
| import java.util.function.Predicate; |
| import javax.measure.Unit; |
| import org.opengis.util.CodeList; |
| import org.opengis.util.GenericName; |
| import org.opengis.referencing.datum.VerticalDatumType; |
| import org.opengis.referencing.cs.CoordinateSystemAxis; |
| import org.opengis.referencing.cs.AxisDirection; |
| import org.apache.sis.util.StringBuilders; |
| import org.apache.sis.measure.Units; |
| import org.apache.sis.util.CharSequences; |
| import org.apache.sis.util.Characters; |
| |
| |
| /** |
| * Extensions to the standard set of {@link VerticalDatumType}. |
| * Those constants are not in public API because they were intentionally omitted from ISO 19111, |
| * and the ISO experts said that they should really not be public. |
| * |
| * <p>This class implements {@link Predicate} for opportunist reasons. |
| * This implementation convenience may change in any future SIS version.</p> |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 1.0 |
| * @since 0.4 |
| * @module |
| */ |
| public final class VerticalDatumTypes implements Predicate<CodeList<?>> { |
| /** |
| * A vertical datum for ellipsoidal heights that are measured along the |
| * normal to the ellipsoid used in the definition of horizontal datum. |
| * |
| * <p>Identifier: {@code CS_DatumType.CS_VD_Ellipsoidal}</p> |
| */ |
| public static final VerticalDatumType ELLIPSOIDAL = VerticalDatumType.valueOf("ELLIPSOIDAL"); |
| |
| /** |
| * A vertical datum for orthometric heights that are measured along the plumb line. |
| * |
| * <p>Identifier: {@code CS_DatumType.CS_VD_Orthometric}</p> |
| */ |
| public static final VerticalDatumType ORTHOMETRIC = VerticalDatumType.valueOf("ORTHOMETRIC"); |
| |
| /** |
| * Mapping from the numeric values used in legacy specification (OGC 01-009) to {@link VerticalDatumType}. |
| * Indices in this array are the legacy codes minus 2000. |
| * |
| * <strong>This array shall not be fill before the above static constants.</strong> |
| */ |
| private static final VerticalDatumType[] TYPES; |
| |
| /** |
| * Mapping from {@link VerticalDatumType} to the numeric values used in legacy specification (OGC 01-009). |
| */ |
| private static final short[] LEGACY_CODES; |
| static { |
| TYPES = new VerticalDatumType[7]; |
| LEGACY_CODES = new short[Math.max(ELLIPSOIDAL.ordinal(), ORTHOMETRIC.ordinal()) + 1]; |
| for (short code = 2000; code <= 2006; code++) { |
| final VerticalDatumType type; |
| switch (code) { |
| case 2000: type = VerticalDatumType .OTHER_SURFACE; break; // CS_VD_Other |
| case 2001: type = VerticalDatumTypes.ORTHOMETRIC; break; // CS_VD_Orthometric |
| case 2002: type = VerticalDatumTypes.ELLIPSOIDAL; break; // CS_VD_Ellipsoidal |
| case 2003: type = VerticalDatumType .BAROMETRIC; break; // CS_VD_AltitudeBarometric |
| case 2005: type = VerticalDatumType .GEOIDAL; break; // CS_VD_GeoidModelDerived |
| case 2006: type = VerticalDatumType .DEPTH; break; // CS_VD_Depth |
| default: continue; |
| } |
| TYPES[code - 2000] = type; |
| LEGACY_CODES[type.ordinal()] = code; |
| } |
| } |
| |
| /** |
| * Returns the vertical datum type from a legacy code. The legacy codes were defined in |
| * <a href="http://www.opengeospatial.org/standards/ct">OGC 01-009</a> |
| * (<cite>Coordinate Transformation Services)</cite>, which also defined the version 1 of |
| * <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well |
| * Known Text</cite></a> format (WKT 1). This method is used for WKT 1 parsing. |
| * |
| * @param code the legacy vertical datum code. |
| * @return the vertical datum type, or {@code null} if the code is unrecognized. |
| */ |
| public static VerticalDatumType fromLegacy(int code) { |
| code -= 2000; |
| return (code >= 0 && code < TYPES.length) ? TYPES[code] : null; |
| } |
| |
| /** |
| * Returns the legacy code for the datum type, or 0 if unknown. |
| * This method is used for WKT 1 formatting. |
| * |
| * @param type the vertical datum type, or {@code null} if unknown. |
| * @return the legacy code for the given datum type, or 0 if unknown. |
| */ |
| public static int toLegacy(final VerticalDatumType type) { |
| if (type != null) { |
| final int ordinal = type.ordinal(); |
| if (ordinal >= 0 && ordinal < LEGACY_CODES.length) { |
| return LEGACY_CODES[ordinal]; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * Guesses the type of a datum from its name, aliases or a given vertical axis. This is sometime needed |
| * after XML unmarshalling or WKT parsing, since GML 3.2 and ISO 19162 do not contain any attribute for |
| * the datum type. |
| * |
| * <p>This method uses heuristic rules and may be changed in any future SIS version. |
| * If the type can not be determined, defaults to {@link VerticalDatumType#OTHER_SURFACE}.</p> |
| * |
| * @param name the name of the datum for which to guess a type, or {@code null} if unknown. |
| * @param aliases the aliases of the datum for which to guess a type, or {@code null} if unknown. |
| * @param axis the vertical axis for which to guess a type, or {@code null} if unknown. |
| * @return a datum type, or {@link VerticalDatumType#OTHER_SURFACE} if none can be guessed. |
| */ |
| public static VerticalDatumType guess(final String name, final Collection<? extends GenericName> aliases, |
| final CoordinateSystemAxis axis) |
| { |
| VerticalDatumType type = guess(name); |
| if (type != null) { |
| return type; |
| } |
| if (aliases != null) { |
| for (final GenericName alias : aliases) { |
| type = guess(alias.tip().toString()); |
| if (type != null) { |
| return type; |
| } |
| } |
| } |
| if (axis != null) { |
| final Unit<?> unit = axis.getUnit(); |
| if (Units.isLinear(unit)) { |
| final String abbreviation = axis.getAbbreviation(); |
| if (abbreviation.length() == 1) { |
| AxisDirection dir = AxisDirection.UP; // Expected direction for accepting the type. |
| switch (abbreviation.charAt(0)) { |
| case 'h': type = ELLIPSOIDAL; break; |
| case 'H': type = VerticalDatumType.GEOIDAL; break; |
| case 'D': type = VerticalDatumType.DEPTH; dir = AxisDirection.DOWN; break; |
| default: return VerticalDatumType.OTHER_SURFACE; |
| } |
| if (dir.equals(axis.getDirection())) { |
| return type; |
| } |
| } |
| } else if (Units.isPressure(unit)) { |
| return VerticalDatumType.BAROMETRIC; |
| } |
| } |
| return VerticalDatumType.OTHER_SURFACE; |
| } |
| |
| /** |
| * Guesses the type of a datum of the given name. This method attempts to guess only if the given name |
| * contains at least one letter. If the type can not be determined, returns {@code null}. |
| * |
| * @param name name of the datum for which to guess a type, or {@code null}. |
| * @return a datum type, or {@code null} if none can be guessed. |
| */ |
| private static VerticalDatumType guess(final String name) { |
| if (name != null) { |
| if (CharSequences.equalsFiltered("Mean Sea Level", name, Characters.Filter.LETTERS_AND_DIGITS, true)) { |
| return VerticalDatumType.GEOIDAL; |
| } |
| for (int i=0; i<name.length();) { |
| final int c = name.codePointAt(i); |
| if (Character.isLetter(c)) { |
| return CodeList.valueOf(VerticalDatumType.class, new VerticalDatumTypes(name), null); |
| } |
| i += Character.charCount(c); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * The name of a datum to compare against the list of datum types, |
| * in upper-case and (if possible) using US-ASCII characters. |
| */ |
| private final StringBuilder datum; |
| |
| /** |
| * Creates a new {@code CodeList.Filter} which will compare the given datum name against the list of datum types. |
| * The comparison is case-insensitive and ignores some non-ASCII characters. The exact algorithm applied here is |
| * implementation dependent and may change in any future version. |
| * |
| * @param name the datum name. |
| */ |
| private VerticalDatumTypes(final String name) { |
| final int length = name.length(); |
| datum = new StringBuilder(length); |
| for (int i=0; i<length;) { |
| final int c = name.codePointAt(i); |
| datum.appendCodePoint(Character.toUpperCase(c)); |
| i += Character.charCount(c); |
| } |
| StringBuilders.toASCII(datum); |
| } |
| |
| /** |
| * Returns {@code true} if the name of the given code is the prefix of a word in the datum name. |
| * We do not test the characters following the prefix because the word may be incomplete |
| * (e.g. {@code "geoid"} versus {@code "geoidal"}). |
| * |
| * <p>This method is public as an implementation side-effect and should be ignored.</p> |
| * |
| * @param code the code to test. |
| * @return {@code true} if the code matches the criterion. |
| */ |
| @Override |
| public boolean test(final CodeList<?> code) { |
| final int i = datum.indexOf(code.name()); |
| return (i == 0) || (i >= 0 && Character.isWhitespace(datum.codePointBefore(i))); |
| } |
| } |