| /* |
| * 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.referencing; |
| |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.List; |
| import java.util.LinkedHashSet; |
| import java.util.Collection; |
| import java.util.Locale; |
| |
| import org.opengis.util.NameSpace; |
| import org.opengis.util.GenericName; |
| import org.opengis.util.FactoryException; |
| import org.opengis.util.InternationalString; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.ReferenceIdentifier; |
| import org.opengis.referencing.crs.CompoundCRS; |
| import org.opengis.referencing.operation.CoordinateOperation; |
| import org.opengis.referencing.operation.ConcatenatedOperation; |
| |
| import org.apache.sis.util.Static; |
| import org.apache.sis.util.CharSequences; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.logging.Logging; |
| import org.apache.sis.internal.util.Strings; |
| import org.apache.sis.internal.util.Constants; |
| import org.apache.sis.internal.util.DefinitionURI; |
| import org.apache.sis.internal.system.Modules; |
| import org.apache.sis.internal.metadata.Identifiers; |
| import org.apache.sis.internal.metadata.NameMeaning; |
| import org.apache.sis.internal.metadata.NameToIdentifier; |
| import org.apache.sis.metadata.iso.citation.Citations; |
| import org.apache.sis.referencing.factory.IdentifiedObjectFinder; |
| import org.apache.sis.referencing.factory.GeodeticAuthorityFactory; |
| import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException; |
| |
| import static java.util.logging.Logger.getLogger; |
| import static org.apache.sis.internal.util.CollectionsExt.nonNull; |
| |
| |
| /** |
| * Utility methods working on arbitrary implementations of the {@link IdentifiedObject} interface. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @author Guilhem Legal (Geomatys) |
| * @version 1.1 |
| * |
| * @see CRS |
| * @see org.apache.sis.geometry.Envelopes |
| * |
| * @since 0.4 |
| * @module |
| */ |
| public final class IdentifiedObjects extends Static { |
| /** |
| * Do not allows instantiation of this class. |
| */ |
| private IdentifiedObjects() { |
| } |
| |
| /** |
| * Returns the information provided in the specified identified object as a map of properties. |
| * The returned map contains the following entries for each key not contained in the {@code excludes} list |
| * and for which the corresponding method returns a non-null and non-empty value. |
| * |
| * <table class="sis"> |
| * <caption>Provided properties</caption> |
| * <tr><th>Key</th> <th>Value</th></tr> |
| * <tr><td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td> |
| * <td>{@link IdentifiedObject#getName()}</td></tr> |
| * <tr><td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td> |
| * <td>{@link IdentifiedObject#getAlias()}</td></tr> |
| * <tr><td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td> |
| * <td>{@link IdentifiedObject#getIdentifiers()}</td></tr> |
| * <tr><td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td> |
| * <td>{@link IdentifiedObject#getRemarks()}</td></tr> |
| * <tr><td>{@value org.opengis.referencing.operation.CoordinateOperation#SCOPE_KEY}</td> |
| * <td>{@link CoordinateOperation#getScope()} (also in datum and reference systems)</td></tr> |
| * <tr><td>{@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY}</td> |
| * <td>{@link CoordinateOperation#getDomainOfValidity()} (also in datum and reference systems)</td></tr> |
| * <tr><td>{@value org.opengis.referencing.operation.CoordinateOperation#OPERATION_VERSION_KEY}</td> |
| * <td>{@link CoordinateOperation#getOperationVersion()}</td></tr> |
| * <tr><td>{@value org.opengis.referencing.operation.CoordinateOperation#COORDINATE_OPERATION_ACCURACY_KEY}</td> |
| * <td>{@link CoordinateOperation#getCoordinateOperationAccuracy()}</td></tr> |
| * <tr><td>{@value org.opengis.referencing.operation.OperationMethod#FORMULA_KEY}</td> |
| * <td>{@link org.opengis.referencing.operation.OperationMethod#getFormula()}</td></tr> |
| * <tr><td>{@value org.apache.sis.referencing.AbstractIdentifiedObject#DEPRECATED_KEY}</td> |
| * <td>{@link AbstractIdentifiedObject#isDeprecated()}</td></tr> |
| * </table> |
| * |
| * <div class="note"><b>Note:</b> |
| * the current implementation does not provide |
| * {@value org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#MINIMUM_VALUE_KEY}, |
| * {@value org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#MAXIMUM_VALUE_KEY} or |
| * {@value org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#RANGE_MEANING_KEY} entry for |
| * {@link org.opengis.referencing.cs.CoordinateSystemAxis} instances because the minimum and maximum |
| * values depend on the {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getUnit() |
| * units of measurement}.</div> |
| * |
| * @param object the identified object to view as a properties map. |
| * @param excludes the keys of properties to exclude from the map. |
| * @return a view of the identified object properties as an immutable map. |
| */ |
| public static Map<String,?> getProperties(final IdentifiedObject object, final String... excludes) { |
| ArgumentChecks.ensureNonNull("object", object); |
| ArgumentChecks.ensureNonNull("excludes", excludes); |
| return new Properties(object, excludes); |
| } |
| |
| /** |
| * Returns every object names and aliases according the given authority. This method performs |
| * the same work than {@link #getName(IdentifiedObject, Citation)}, except that it does not |
| * stop at the first match. This method is useful in the rare cases where the same authority |
| * declares more than one name, and all those names are of interest. |
| * |
| * @param object the object to get the names and aliases from, or {@code null}. |
| * @param authority the authority for the names to return, or {@code null} for any authority. |
| * @return the object's names and aliases, or an empty set if no name or alias matching the |
| * specified authority has been found. |
| */ |
| public static Set<String> getNames(final IdentifiedObject object, final Citation authority) { |
| final Set<String> names = new LinkedHashSet<>(8); |
| getName(object, authority, names); |
| return names; |
| } |
| |
| /** |
| * Returns an object name according the given authority. |
| * This method checks first the {@linkplain AbstractIdentifiedObject#getName() primary name}, |
| * then all {@linkplain AbstractIdentifiedObject#getAlias() aliases} in their iteration order. |
| * |
| * <ul class="verbose"> |
| * <li>If the name or alias implements the {@link Identifier} interface, |
| * then this method compares the {@linkplain Identifier#getAuthority() |
| * identifier authority} against the specified citation using the |
| * {@link Citations#identifierMatches(Citation, Citation)} method. |
| * If a matching is found, then this method returns the |
| * {@linkplain Identifier#getCode() identifier code} of that object.</li> |
| * |
| * <li>Otherwise, if the alias implements the {@link GenericName} interface, then this method |
| * compares the {@linkplain GenericName#scope() name scope} against the specified citation |
| * using the {@link Citations#identifierMatches(Citation, String)} method. |
| * If a matching is found, then this method returns the |
| * {@linkplain GenericName#toString() string representation} of that name.</li> |
| * </ul> |
| * |
| * Note that alias may implement both the {@link Identifier} and {@link GenericName} |
| * interfaces (for example {@link NamedIdentifier}). In such cases, the identifier view has |
| * precedence. |
| * |
| * @param object the object to get the name from, or {@code null}. |
| * @param authority the authority for the name to return, or {@code null} for any authority. |
| * @return the object's name (either an {@linkplain Identifier#getCode() identifier code} |
| * or a {@linkplain GenericName#toString() generic name}), |
| * or {@code null} if no name matching the specified authority has been found. |
| * |
| * @see AbstractIdentifiedObject#getName() |
| */ |
| public static String getName(final IdentifiedObject object, final Citation authority) { |
| return getName(object, authority, null); |
| } |
| |
| /** |
| * Returns an object name according the given authority. This method is {@code null}-safe: |
| * every properties are checked for null values, even the properties that are supposed to |
| * be mandatory (not all implementations define all mandatory values). |
| * |
| * @param object the object to get the name from, or {@code null}. |
| * @param authority the authority for the name to return, or {@code null} for any authority. |
| * @param addTo if non-null, the collection where to add all names found. |
| * @return the object's name (either an {@linkplain Identifier#getCode() identifier code} |
| * or a {@linkplain GenericName#toString() generic name}), |
| * or {@code null} if no name matching the specified authority has been found. |
| */ |
| private static String getName(final IdentifiedObject object, final Citation authority, final Collection<String> addTo) { |
| if (object != null) { |
| Identifier identifier = object.getName(); |
| if (identifier != null) { |
| if (authority == null || Citations.identifierMatches(authority, identifier.getAuthority())) { |
| final String name = identifier.getCode(); |
| if (name != null) { |
| if (addTo == null) { |
| return name; |
| } |
| addTo.add(name); |
| } |
| } |
| } |
| /* |
| * If we do not found a primary name for the specified authority, |
| * or if the user requested all names, search among aliases. |
| */ |
| for (final GenericName alias : nonNull(object.getAlias())) { |
| if (alias != null) { |
| final String name; |
| if (alias instanceof Identifier) { |
| identifier = (Identifier) alias; |
| if (authority != null && !Citations.identifierMatches(authority, identifier.getAuthority())) { |
| continue; // Authority does not match. Search another alias. |
| } |
| name = identifier.getCode(); |
| } else { |
| if (authority != null) { |
| final NameSpace ns = alias.scope(); if (ns == null) continue; |
| final GenericName scope = ns.name(); if (scope == null) continue; |
| if (!Citations.identifierMatches(authority, scope.toString())) { |
| continue; // Authority does not match. Search another alias. |
| } |
| } |
| name = alias.toString(); |
| } |
| if (name != null) { |
| if (addTo == null) { |
| return name; |
| } |
| addTo.add(name); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns an identifier for the given object according the given authority. |
| * This method checks all {@linkplain AbstractIdentifiedObject#getIdentifiers() identifiers} in their iteration |
| * order and returns the first identifier with an {@linkplain NamedIdentifier#getAuthority() authority} citation |
| * {@linkplain Citations#identifierMatches(Citation, Citation) matching} the specified authority. |
| * |
| * @param object the object to get the identifier from, or {@code null}. |
| * @param authority the authority for the identifier to return, or {@code null} for |
| * the first identifier regardless its authority. |
| * @return the object's identifier, or {@code null} if no identifier matching the specified authority |
| * has been found. |
| * |
| * @see AbstractIdentifiedObject#getIdentifier() |
| */ |
| public static Identifier getIdentifier(final IdentifiedObject object, final Citation authority) { |
| if (object != null) { |
| for (final Identifier identifier : nonNull(object.getIdentifiers())) { |
| if (identifier != null) { // Paranoiac check. |
| if (authority == null || Citations.identifierMatches(authority, identifier.getAuthority())) { |
| return identifier; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the string representation of the first identifier, or the object name if there is no identifier. |
| * This method searches for the first non-null element in |
| * <code>object.{@linkplain AbstractIdentifiedObject#getIdentifiers() getIdentifiers()}</code>. If there is none, |
| * then this method fallback on <code>object.{@linkplain AbstractIdentifiedObject#getName() getName()}</code>. |
| * The first element found is formatted by {@link #toString(Identifier)}. |
| * |
| * <h4>Recommended alternatives</h4> |
| * <ul> |
| * <li>If the code of a specific authority is wanted (typically EPSG), then consider |
| * using {@link #getIdentifier(IdentifiedObject, Citation)} instead.</li> |
| * <li>In many cases, the identifier is not specified. For an exhaustive scan of the EPSG |
| * database looking for a match, use one of the search methods defined below.</li> |
| * </ul> |
| * |
| * @param object the identified object, or {@code null}. |
| * @return a string representation of the first identifier or name, or {@code null} if none. |
| * |
| * @see #getIdentifier(IdentifiedObject, Citation) |
| * @see #lookupURN(IdentifiedObject, Citation) |
| */ |
| public static String getIdentifierOrName(final IdentifiedObject object) { |
| if (object != null) { |
| for (final Identifier id : nonNull(object.getIdentifiers())) { |
| final String code = toString(id); |
| if (code != null) { // Paranoiac check. |
| return code; |
| } |
| } |
| final String name = toString(object.getName()); |
| if (name != null) { // Paranoiac check. |
| return name; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the first name, alias or identifier which is a valid Unicode identifier. This method considers a |
| * name or identifier as valid if {@link CharSequences#isUnicodeIdentifier(CharSequence)} returns {@code true}. |
| * This method performs the search in the following order: |
| * |
| * <ul> |
| * <li><code>object.{@linkplain AbstractIdentifiedObject#getName() getName()}</code></li> |
| * <li><code>object.{@linkplain AbstractIdentifiedObject#getAlias() getAlias()}</code> in iteration order</li> |
| * <li><code>object.{@linkplain AbstractIdentifiedObject#getIdentifiers() getIdentifiers()}</code> in iteration order</li> |
| * </ul> |
| * |
| * This method is can be used for fetching a more human-friendly identifier than the numerical values |
| * typically returned by {@link IdentifiedObject#getIdentifiers()}. However the returned value is not |
| * guaranteed to be unique. |
| * |
| * @param object the identified object, or {@code null}. |
| * @return the first name, alias or identifier which is a valid Unicode identifier, or {@code null} if none. |
| * |
| * @see ImmutableIdentifier |
| * @see Citations#toCodeSpace(Citation) |
| * @see CharSequences#isUnicodeIdentifier(CharSequence) |
| * |
| * @since 1.0 |
| */ |
| public static String getSimpleNameOrIdentifier(final IdentifiedObject object) { |
| if (object != null) { |
| Identifier identifier = object.getName(); |
| if (identifier != null) { // Paranoiac check. |
| final String code = identifier.getCode(); |
| if (CharSequences.isUnicodeIdentifier(code)) { |
| return code; |
| } |
| } |
| for (GenericName alias : nonNull(object.getAlias())) { |
| if (alias != null && (alias = alias.tip()) != null) { |
| final String code = alias.toString(); |
| if (CharSequences.isUnicodeIdentifier(code)) { |
| return code; |
| } |
| } |
| } |
| for (final Identifier id : nonNull(object.getIdentifiers())) { |
| if (id != null) { // Paranoiac check. |
| final String code = id.getCode(); |
| if (CharSequences.isUnicodeIdentifier(code)) { |
| return code; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a name that can be used for display purpose. This method checks the non-blank |
| * {@linkplain AbstractIdentifiedObject#getName() name}, |
| * {@linkplain AbstractIdentifiedObject#getAlias() alias} or |
| * {@linkplain AbstractIdentifiedObject#getIdentifiers() identifier}, in that order. |
| * If the primary name seems to be the {@linkplain CharSequences#isAcronymForWords acronym} of an alias, |
| * then the alias is returned. For example if the name is <cite>"WGS 84"</cite> and an alias is |
| * <cite>"World Geodetic System 1984"</cite>, then that later alias is returned. |
| * |
| * <div class="note"><b>Note:</b> |
| * the name should never be missing, but this method nevertheless |
| * fallbacks on identifiers as a safety against incomplete implementations. |
| * If an identifier implements {@link GenericName} (as with {@link NamedIdentifier}), |
| * its {@link GenericName#toInternationalString() toInternationalString()} method will be used.</div> |
| * |
| * @param object the identified object, or {@code null}. |
| * @param locale the locale for the name to return, or {@code null} for the default. |
| * @return a name for human reading, or {@code null} if none were found. |
| * |
| * @since 1.1 |
| */ |
| public static String getDisplayName(final IdentifiedObject object, final Locale locale) { |
| if (object == null) { |
| return null; |
| } |
| String name = toString(object.getName(), locale); |
| for (final GenericName c : nonNull(object.getAlias())) { |
| final String alias = toString(c, locale); |
| if (alias != null) { |
| if (name == null || CharSequences.isAcronymForWords(name, alias)) { |
| return alias; |
| } |
| final String unlocalized = c.toString(); |
| if (!alias.equals(unlocalized) && CharSequences.isAcronymForWords(name, unlocalized)) { |
| return alias; // Select the localized version instead of `unlocalized`. |
| } |
| } |
| } |
| if (name == null) { |
| for (final Identifier id : nonNull(object.getIdentifiers())) { |
| name = toString(id, locale); |
| if (name != null) break; |
| } |
| } |
| return name; |
| } |
| |
| /** |
| * Looks up a URN, such as {@code "urn:ogc:def:crs:EPSG:9.1:4326"}, of the specified object. |
| * This method searches in all {@linkplain org.apache.sis.referencing.factory.GeodeticAuthorityFactory geodetic |
| * authority factories} known to SIS for an object {@linkplain org.apache.sis.util.ComparisonMode#APPROXIMATE |
| * approximately equals} to the specified object. Then there is a choice: |
| * |
| * <ul> |
| * <li>If a single matching object is found in the specified authority factory, then its URN is returned.</li> |
| * <li>Otherwise if the given object is a {@link CompoundCRS} or {@link ConcatenatedOperation} |
| * and all components have an URN, then this method returns a combined URN.</li> |
| * <li>Otherwise this method returns {@code null}.</li> |
| * </ul> |
| * |
| * <p><strong>Note that this method checks the identifier validity.</strong> |
| * If the given object declares explicitly an identifier, then this method will instantiate an object from the |
| * authority factory using that identifier and compare it with the given object. If the comparison fails, then |
| * this method returns {@code null}. Consequently this method may return {@code null} even if the given object |
| * declares explicitly its identifier. If the declared identifier is wanted unconditionally, |
| * one can use the following pattern instead: |
| * |
| * {@preformat java |
| * String urn = toURN(object.getClass(), getIdentifier(object, authority)); |
| * } |
| * |
| * This method can be seen as a converse of {@link CRS#forCode(String)}. |
| * |
| * @param object the object (usually a {@linkplain org.apache.sis.referencing.crs.AbstractCRS |
| * coordinate reference system}) whose identifier is to be found, or {@code null}. |
| * @param authority the authority for the identifier to return, or {@code null} for |
| * the first identifier regardless its authority. |
| * @return the identifier, or {@code null} if none was found without ambiguity or if the given object was null. |
| * @throws FactoryException if an error occurred during the search. |
| * |
| * @see #newFinder(String) |
| * @see #toURN(Class, Identifier) |
| * |
| * @since 0.7 |
| */ |
| public static String lookupURN(final IdentifiedObject object, final Citation authority) throws FactoryException { |
| if (object == null) { |
| return null; |
| } |
| IdentifiedObjectFinder finder; |
| try { |
| finder = newFinder(Citations.toCodeSpace(authority)); |
| } catch (NoSuchAuthorityFactoryException e) { |
| warning("lookupURN", e); |
| finder = newFinder(null); |
| } |
| String urn = lookupURN(object, authority, finder); |
| if (urn != null) { |
| return urn; |
| } |
| /* |
| * If we didn't found a URN but the given object is made of smaller components, build a combined URN. |
| * Example: "urn:ogc:def:crs, crs:EPSG::27700, crs:EPSG::5701" (without spaces actually). |
| */ |
| final List<? extends IdentifiedObject> components; |
| if (object instanceof CompoundCRS) { |
| components = CRS.getSingleComponents((CompoundCRS) object); |
| } else if (object instanceof ConcatenatedOperation) { |
| components = ((ConcatenatedOperation) object).getOperations(); |
| } else { |
| return null; |
| } |
| StringBuilder buffer = null; |
| for (final IdentifiedObject component : components) { |
| urn = lookupURN(component, authority, finder); |
| if (urn == null) { |
| return null; |
| } |
| assert urn.startsWith(DefinitionURI.PREFIX) : urn; |
| if (buffer == null) { |
| buffer = new StringBuilder(40).append(DefinitionURI.PREFIX).append(DefinitionURI.SEPARATOR) |
| .append(NameMeaning.toObjectType(object.getClass())); |
| } |
| buffer.append(DefinitionURI.COMPONENT_SEPARATOR) |
| .append(urn, DefinitionURI.PREFIX.length() + 1, urn.length()); |
| } |
| return (buffer != null) ? buffer.toString() : null; |
| } |
| |
| /** |
| * Implementation of {@link #lookupURN(IdentifiedObject, Citation)}, possibly invoked many times |
| * if the identified object is a {@link CompoundCRS} or {@link ConcatenatedOperation}. |
| */ |
| private static String lookupURN(final IdentifiedObject object, final Citation authority, |
| final IdentifiedObjectFinder finder) throws FactoryException |
| { |
| String urn = null; |
| if (object != null) { |
| for (final IdentifiedObject candidate : finder.find(object)) { |
| String c = toURN(candidate.getClass(), getIdentifier(candidate, authority)); |
| if (c == null && authority == null) { |
| /* |
| * If `authority` was null, then getIdentifier(candidate, authority) returned the identifier |
| * for the first authority. But not all authorities can be formatted as a URN. So try other |
| * authorities. |
| */ |
| for (final Identifier id : candidate.getIdentifiers()) { |
| c = toURN(candidate.getClass(), id); |
| if (c != null) break; |
| } |
| } |
| /* |
| * We should find at most one URN. But if we find many, verify that all of them are consistent. |
| */ |
| if (c != null) { |
| if (urn != null && !urn.equals(c)) { |
| return null; |
| } |
| urn = c; |
| } |
| } |
| } |
| return urn; |
| } |
| |
| /** |
| * Looks up an EPSG code, such as {@code 4326}, of the specified object. This method searches in EPSG factories |
| * known to SIS for an object {@linkplain org.apache.sis.util.ComparisonMode#APPROXIMATE approximately equals} |
| * to the specified object. If such an object is found, then its EPSG identifier is returned. |
| * Otherwise or if there is ambiguity, this method returns {@code null}. |
| * |
| * <p><strong>Note that this method checks the identifier validity.</strong> |
| * If the given object declares explicitly an identifier, then this method will instantiate an object from the |
| * EPSG factory using that identifier and compare it with the given object. If the comparison fails, then this |
| * method returns {@code null}. Consequently this method may return {@code null} even if the given object |
| * declares explicitly its identifier. If the declared identifier is wanted unconditionally, |
| * one can use the following pattern instead: |
| * |
| * {@preformat java |
| * String code = toString(getIdentifier(object, Citations.EPSG)); |
| * } |
| * |
| * This method can be seen as a converse of {@link CRS#forCode(String)}. |
| * |
| * @param object the object (usually a {@linkplain org.apache.sis.referencing.crs.AbstractCRS |
| * coordinate reference system}) whose EPSG code is to be found, or {@code null}. |
| * @return the EPSG code, or {@code null} if none was found without ambiguity or if the given object was null. |
| * @throws FactoryException if an error occurred during the search. |
| * |
| * @see #newFinder(String) |
| * |
| * @since 0.7 |
| */ |
| public static Integer lookupEPSG(final IdentifiedObject object) throws FactoryException { |
| Integer code = null; |
| if (object != null) { |
| for (final IdentifiedObject candidate : newFinder(Constants.EPSG).find(object)) { |
| final Identifier id = getIdentifier(candidate, Citations.EPSG); |
| if (id != null) try { |
| Integer previous = code; |
| code = Integer.valueOf(id.getCode()); |
| if (previous != null && !previous.equals(code)) { |
| return null; |
| } |
| } catch (NumberFormatException e) { |
| warning("lookupEPSG", e); |
| } |
| } |
| } |
| return code; |
| } |
| |
| /** |
| * Logs a warning for a non-critical error. The callers should have a fallback. |
| */ |
| private static void warning(final String method, final Exception e) { |
| Logging.recoverableException(getLogger(Modules.REFERENCING), IdentifiedObjects.class, method, e); |
| } |
| |
| /** |
| * Creates a finder which can be used for looking up unidentified objects. |
| * This method is an alternative to {@code lookup(…)} methods when more control are desired. |
| * |
| * <div class="note"><b>Example 1: be lenient regarding axis order</b><br> |
| * By default, {@code lookup(…)} methods require that objects in the dataset have their axes in the |
| * same order than the given object. For relaxing this condition, one can use the following Java code. |
| * This example assumes that at most one object from the dataset will match the given object. |
| * If more than one object may match, then the call to {@code findSingleton(…)} should be replaced |
| * by {@code find(…)}. |
| * |
| * {@preformat java |
| * IdentifiedObjectFinder finder = IdentifiedObjects.newFinder(null); |
| * finder.setIgnoringAxes(true); |
| * IdentifiedObject found = finder.findSingleton(object); |
| * }</div> |
| * |
| * <div class="note"><b>Example 2: extend the search to deprecated definitions</b><br> |
| * By default, {@code lookup(…)} methods exclude deprecated objects from the search. |
| * To search also among deprecated objects, one can use the following Java code: |
| * This example does not use the {@code findSingleton(…)} convenience method on the assumption |
| * that the search may find both deprecated and non-deprecated objects. |
| * |
| * {@preformat java |
| * IdentifiedObjectFinder finder = IdentifiedObjects.newFinder(null); |
| * finder.setSearchDomain(IdentifiedObjectFinder.Domain.ALL_DATASET); |
| * Set<IdentifiedObject> found = finder.find(object); |
| * }</div> |
| * |
| * @param authority the authority of the objects to search (typically {@code "EPSG"} or {@code "OGC"}), |
| * or {@code null} for searching among the objects created by all authorities. |
| * @return a finder to use for looking up unidentified objects. |
| * @throws NoSuchAuthorityFactoryException if the given authority is not found. |
| * @throws FactoryException if the finder can not be created for another reason. |
| * |
| * @see #lookupEPSG(IdentifiedObject) |
| * @see #lookupURN(IdentifiedObject, Citation) |
| * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#newIdentifiedObjectFinder() |
| * @see IdentifiedObjectFinder#find(IdentifiedObject) |
| */ |
| public static IdentifiedObjectFinder newFinder(final String authority) |
| throws NoSuchAuthorityFactoryException, FactoryException |
| { |
| final GeodeticAuthorityFactory factory; |
| if (authority == null) { |
| factory = AuthorityFactories.ALL; |
| } else { |
| factory = AuthorityFactories.ALL.getAuthorityFactory(GeodeticAuthorityFactory.class, authority, null); |
| } |
| return factory.newIdentifiedObjectFinder(); |
| } |
| |
| /** |
| * Returns {@code true} if either the {@linkplain AbstractIdentifiedObject#getName() primary name} or at least |
| * one {@linkplain AbstractIdentifiedObject#getAlias() alias} matches the given string according heuristic rules. |
| * If the given object is an instance of {@link AbstractIdentifiedObject}, then this method delegates to its |
| * {@link AbstractIdentifiedObject#isHeuristicMatchForName(String) isHeuristicMatchForName(String)} method |
| * in order to leverage the additional rules implemented by sub-classes. |
| * Otherwise the fallback implementation returns {@code true} if the given {@code name} is equal, |
| * ignoring aspects documented below, to one of the following names: |
| * |
| * <ul> |
| * <li>The {@linkplain AbstractIdentifiedObject#getName() primary name}'s {@linkplain NamedIdentifier#getCode() code} |
| * (without {@linkplain NamedIdentifier#getCodeSpace() codespace}).</li> |
| * <li>Any {@linkplain AbstractIdentifiedObject#getAlias() alias}'s {@linkplain NamedIdentifier#tip() tip} |
| * (without {@linkplain NamedIdentifier#scope() scope} and namespace).</li> |
| * </ul> |
| * |
| * The comparison ignores the following aspects: |
| * <ul> |
| * <li>Lower/upper cases.</li> |
| * <li>Some Latin diacritical signs (e.g. {@code "Réunion"} and {@code "Reunion"} are considered equal).</li> |
| * <li>All characters that are not {@linkplain Character#isLetterOrDigit(int) letters or digits} |
| * (e.g. {@code "Mercator (1SP)"} and {@code "Mercator_1SP"} are considered equal).</li> |
| * <li>Namespaces or scopes, because this method is typically invoked with either the value of an other |
| * <code>IdentifiedObject.getName().getCode()</code> or with the <cite>Well Known Text</cite> (WKT) |
| * projection or parameter name.</li> |
| * </ul> |
| * |
| * If the {@code object} argument is {@code null}, then this method returns {@code false}. |
| * |
| * @param object the object for which to check the name or alias, or {@code null}. |
| * @param name the name to compare with the object name or aliases. |
| * @return {@code true} if the primary name or at least one alias matches the specified {@code name}. |
| * |
| * @see AbstractIdentifiedObject#isHeuristicMatchForName(String) |
| */ |
| public static boolean isHeuristicMatchForName(final IdentifiedObject object, final String name) { |
| ArgumentChecks.ensureNonNull("name", name); |
| if (object == null) { |
| return false; |
| } |
| if (object instanceof AbstractIdentifiedObject) { |
| /* |
| * DefaultCoordinateSystemAxis overrides this method. |
| * We really need to delegate to the overridden method. |
| */ |
| return ((AbstractIdentifiedObject) object).isHeuristicMatchForName(name); |
| } else { |
| return NameToIdentifier.isHeuristicMatchForName(object.getName(), object.getAlias(), name, |
| NameToIdentifier.Simplifier.DEFAULT); |
| } |
| } |
| |
| /** |
| * Returns the URN of the given identifier, or {@code null} if no valid URN can be formed. |
| * This method builds a URN from the {@linkplain NamedIdentifier#getCodeSpace() codespace}, |
| * {@linkplain NamedIdentifier#getVersion() version} and {@linkplain NamedIdentifier#getCode() code} |
| * of the given identifier, completed by the given {@link Class} argument. |
| * |
| * <p>First, this method starts the URN with {@code "urn:"} followed by a namespace determined |
| * from the identifier {@linkplain NamedIdentifier#getCodeSpace() codespace} (which is usually |
| * an abbreviation of the identifier {@linkplain NamedIdentifier#getAuthority() authority}). |
| * The recognized namespaces are listed in the following table |
| * (note that the list of authorities that can be used in the {@code "urn:ogc:def"} namespace |
| * is specified by the <a href="https://www.ogc.org/ogcna">OGC Naming Authority</a>). |
| * If this method can not determine a namespace for the given identifier, it returns {@code null}.</p> |
| * |
| * <table class="sis"> |
| * <caption>Valid values for the authority component in URN</caption> |
| * <tr><th>Namespace</th> <th>Authority in URN</th> <th>Description</th></tr> |
| * <tr><td>{@code urn:ogc:def}</td> <td>{@code EPSG}</td> <td>EPSG dataset</td></tr> |
| * <tr><td>{@code urn:ogc:def}</td> <td>{@code OGC}</td> <td>Open Geospatial Consortium</td></tr> |
| * <tr><td>{@code urn:ogc:def}</td> <td>{@code OGC-WFS}</td> <td>OGC Web Feature Service</td></tr> |
| * <tr><td>{@code urn:ogc:def}</td> <td>{@code SI}</td> <td>Système International d'Unités</td></tr> |
| * <tr><td>{@code urn:ogc:def}</td> <td>{@code UCUM}</td> <td>Unified Code for Units of Measure</td></tr> |
| * <tr><td>{@code urn:ogc:def}</td> <td>{@code UNSD}</td> <td>United Nations Statistics Division</td></tr> |
| * <tr><td>{@code urn:ogc:def}</td> <td>{@code USNO}</td> <td>United States Naval Observatory</td></tr> |
| * </table> |
| * |
| * The namespace is followed by the authority, then by a type determined from the given {@link Class} argument. |
| * That class is usually determined simply by {@code IdentifiedObject.getClass()}. |
| * The given class shall be assignable to one of the following types, otherwise this method returns {@code null}: |
| * |
| * <table class="sis"> |
| * <caption>Valid values for the type component in URN</caption> |
| * <tr><th>Interface</th> <th>Type in URN</th> <th>Description</th></tr> |
| * <tr><td>{@link org.opengis.referencing.cs.CoordinateSystemAxis}</td> <td>{@code axis}</td> <td>Coordinate system axe definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.operation.CoordinateOperation}</td> <td>{@code coordinateOperation}</td> <td>Coordinate operation definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.crs.CoordinateReferenceSystem}</td> <td>{@code crs}</td> <td>Coordinate reference system definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.cs.CoordinateSystem}</td> <td>{@code cs}</td> <td>Coordinate system definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.datum.Datum}</td> <td>{@code datum}</td> <td>Datum definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.datum.Ellipsoid}</td> <td>{@code ellipsoid}</td> <td>Ellipsoid definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.datum.PrimeMeridian}</td> <td>{@code meridian}</td> <td>Prime meridian definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.operation.OperationMethod}</td> <td>{@code method}</td> <td>Operation method definition</td></tr> |
| * <tr><td>{@link org.opengis.parameter.ParameterDescriptor}</td> <td>{@code parameter}</td> <td>Operation parameter definition</td></tr> |
| * <tr><td>{@link org.opengis.referencing.ReferenceSystem}</td> <td>{@code referenceSystem}</td> <td>Value reference system definition</td></tr> |
| * <tr><td>{@link javax.measure.Unit}</td> <td>{@code uom}</td> <td>Unit of measure definition</td></tr> |
| * </table> |
| * |
| * The type is followed by the {@linkplain NamedIdentifier#getVersion() codespace version} if available, |
| * and finally by the {@linkplain NamedIdentifier#getCode() code} value. |
| * |
| * <p>The above tables may be expanded in any future SIS version.</p> |
| * |
| * @param type a type assignable to one of the types listed in above table. |
| * @param identifier the identifier for which to format a URN, or {@code null}. |
| * @return the URN for the given identifier, or {@code null} if the given identifier was null |
| * or can not be formatted by this method. |
| * |
| * @see #lookupURN(IdentifiedObject, Citation) |
| * |
| * @since 0.7 |
| */ |
| public static String toURN(final Class<?> type, final Identifier identifier) { |
| ArgumentChecks.ensureNonNull("type", type); |
| if (identifier == null) { |
| return null; |
| } |
| String cs = null; |
| if (identifier instanceof ReferenceIdentifier) { |
| cs = ((ReferenceIdentifier) identifier).getCodeSpace(); |
| } |
| if (cs == null || cs.isEmpty()) { |
| cs = Identifiers.getIdentifier(identifier.getAuthority(), true); |
| } |
| return NameMeaning.toURN(type, cs, |
| (identifier instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) identifier).getVersion() : null, |
| identifier.getCode()); |
| } |
| |
| /** |
| * Returns a string representation of the given identifier. |
| * This method applies the following rules: |
| * |
| * <ul> |
| * <li>If the given identifier implements the {@link GenericName} interface, |
| * then this method delegates to the {@link GenericName#toString()} method.</li> |
| * <li>Otherwise if the given identifier has a {@linkplain ReferenceIdentifier#getCodeSpace() code space}, |
| * then formats the identifier as "{@code codespace:code}".</li> |
| * <li>Otherwise if the given identifier has an {@linkplain Identifier#getAuthority() authority}, |
| * then formats the identifier as "{@code authority:code}".</li> |
| * <li>Otherwise returns the {@linkplain Identifier#getCode() identifier code}.</li> |
| * </ul> |
| * |
| * This method is provided because the {@link GenericName#toString()} behavior is specified by its javadoc, |
| * while {@link Identifier} has no such contract. For example like most ISO 19115 objects in SIS, |
| * the {@link org.apache.sis.metadata.iso.DefaultIdentifier} implementation is formatted as a tree. |
| * This static method can be used when a "name-like" representation is needed for any implementation. |
| * |
| * @param identifier the identifier, or {@code null}. |
| * @return a string representation of the given identifier, or {@code null}. |
| * |
| * @see ImmutableIdentifier#toString() |
| * @see NamedIdentifier#toString() |
| */ |
| public static String toString(final Identifier identifier) { |
| if (identifier == null) { |
| return null; |
| } |
| if (identifier instanceof GenericName) { |
| // The toString() behavior is specified by the GenericName javadoc. |
| return identifier.toString(); |
| } |
| final String code = identifier.getCode(); |
| String cs = null; |
| if (identifier instanceof ReferenceIdentifier) { |
| cs = ((ReferenceIdentifier) identifier).getCodeSpace(); |
| } |
| if (cs == null || cs.isEmpty()) { |
| cs = Citations.toCodeSpace(identifier.getAuthority()); |
| } |
| if (cs != null) { |
| return cs + Constants.DEFAULT_SEPARATOR + code; |
| } |
| return code; |
| } |
| |
| /** |
| * Returns a localized name for the given identifier if possible, or the identifier code otherwise. |
| * This method performs paranoiac checks against null or empty values. We do not provides this method |
| * in public API because those aggressive checks may be unexpected. It is okay when we merely want to |
| * provide a label for human reading. |
| * |
| * @param identifier the identifier for which to get a localized string representation. |
| * @param locale the desired locale, or {@code null} for the default. |
| * @return string representation, or {@code null} if none. |
| */ |
| private static String toString(final Identifier identifier, final Locale locale) { |
| if (identifier == null) return null; |
| if (identifier instanceof GenericName) { |
| final String name = toString(((GenericName) identifier).tip(), locale); |
| if (name != null) return name; |
| } |
| return Strings.trimOrNull(identifier.getCode()); |
| } |
| |
| /** |
| * Returns a string representation of the given name in the given locale. |
| * This method performs paranoiac checks against null or empty values. |
| * We do not provides this method in public API because those aggressive checks may be unexpected. |
| * It is okay when we merely want to provide a label for human reading. |
| * |
| * @param name the name for which to get a localized string representation. |
| * @param locale the desired locale, or {@code null} for the default. |
| * @return localized string representation, or {@code null} if none. |
| */ |
| private static String toString(final GenericName name, final Locale locale) { |
| if (name == null) { |
| return null; |
| } |
| if (locale != null) { |
| final InternationalString i18n = name.toInternationalString(); |
| if (i18n != null) { |
| final String s = Strings.trimOrNull(i18n.toString(locale)); |
| if (s != null) return s; |
| } |
| } |
| return Strings.trimOrNull(name.toString()); |
| } |
| } |