All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.referencing.IdentifiedObjects Maven / Gradle / Ivy

Go to download

Implementations of Coordinate Reference Systems (CRS), conversion and transformation services derived from ISO 19111.

There is a newer version: 3.20-geoapi-3.0
Show newest version
/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2001-2011, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2011, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    This package contains documentation from OpenGIS specifications.
 *    OpenGIS consortium's work is fully acknowledged here.
 */
package org.geotoolkit.referencing;

import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Comparator;
import java.util.Collection;
import java.util.Collections;
import java.io.Serializable;
import java.io.ObjectStreamException;

import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
import org.opengis.util.GenericName;
import org.opengis.util.FactoryException;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import org.geotoolkit.lang.Static;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.naming.DefaultNameSpace;
import org.geotoolkit.metadata.iso.citation.Citations;
import org.geotoolkit.referencing.factory.IdentifiedObjectFinder;
import org.geotoolkit.referencing.factory.AbstractAuthorityFactory;

import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
import static org.opengis.referencing.IdentifiedObject.IDENTIFIERS_KEY;
import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;
import static org.geotoolkit.internal.Citations.identifierMatches;


/**
 * Utility methods working on arbitrary implementations of the {@link IdentifiedObject}
 * interface.
 *
 * {@section Note on Spatial Reference System (SRS) identifiers}
 * OGC Web Services have the concept of a Spatial Reference System identifier used to
 * communicate CRS information between systems. In Well Known Text (WKT)
 * format, this identifier is declared in the {@code AUTHORITY} element.
 * 

* Examples of Spatial Reference System (SRS) values: *

    *
  • {@code EPSG:4326} - this was understood to mean force XY axis order in * old Web Map Services (WMS). Note that latest WMS specifications require the respect * of axis order as declared in the EPSG database, which is (latitude, * longitude).
  • *
  • {@code urn:ogc:def:crs:EPSG:4326} - understood to match the EPSG database axis order * in all cases, no matter the WMS version.
  • *
  • {@code AUTO:43200} - without the parameters that are specific to AUTO codes.
  • *
* * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.18 * * @see CRS * @see org.geotoolkit.geometry.Envelopes * * @since 3.18 (derived from 1.2) * @module */ public final class IdentifiedObjects extends Static { /** * Do not allows instantiation of this class. */ private IdentifiedObjects() { } /** * An empty array of identifiers. This is useful for fetching identifiers as an array, * using the following idiom: * * {@preformat java * getIdentifiers().toArray(EMPTY_IDENTIFIER_ARRAY); * } * * @see IdentifiedObject#getIdentifiers() */ public static final ReferenceIdentifier[] EMPTY_IDENTIFIER_ARRAY = new ReferenceIdentifier[0]; /** * An empty array of alias. This is useful for fetching alias as an array, * using the following idiom: * * {@preformat java * getAlias().toArray(EMPTY_ALIAS_ARRAY); * } * * @see IdentifiedObject#getAlias() */ public static final GenericName[] EMPTY_ALIAS_ARRAY = new GenericName[0]; /** * A comparator for sorting identified objects by {@linkplain IdentifiedObject#getName() name}. */ public static final Comparator NAME_COMPARATOR = new NameComparator(); /** * A comparator for sorting identified objects by {@linkplain IdentifiedObject#getIdentifiers identifiers}. * Identifiers are compared in their iteration order. */ public static final Comparator IDENTIFIER_COMPARATOR = new IdentifierComparator(); /** * A comparator for sorting identified objects by {@linkplain IdentifiedObject#getRemarks remarks}. */ public static final Comparator REMARKS_COMPARATOR = new RemarksComparator(); /** * Compares two objects for order. Any object may be null. This method is * used for implementation of {@link #NAME_COMPARATOR} and its friends. */ static > int doCompare(final E c1, final E c2) { if (c1 == null) { return (c2 == null) ? 0 : -1; } if (c2 == null) { return +1; } return c1.compareTo(c2); } /** * Returns the informations provided in the specified identified object as a map of * properties. The returned map contains keys declared in the {@link IdentifiedObject} * interface, for example {@link IdentifiedObject#NAME_KEY NAME_KEY}. The values are * obtained by calls to the methods associated to each key, for example * {@link IdentifiedObject#getName()} for the {@code NAME_KEY}. * * @param info The identified object to view as a properties map. * @return An view of the identified object as an immutable map. */ public static Map getProperties(final IdentifiedObject info) { ensureNonNull("info", info); return new Properties(info); } /** * Returns the properties to be given to an identified object derived from the specified one. * This method returns the same properties than the supplied argument (as of * {@linkplain #getProperties(IdentifiedObject) getProperties}(info)), except for * the following: *

*

    *
  • The {@linkplain IdentifiedObject#getName() name}'s authority is replaced by the specified one.
  • *
  • All {@linkplain IdentifiedObject#getIdentifiers identifiers} are removed, because the new object * to be created is probably not endorsed by the original authority.
  • *
*

* This method returns a mutable map. Consequently, callers can add their own identifiers * directly to this map if they wish. * * @param info The identified object to view as a properties map. * @param authority The new authority for the object to be created, or {@code null} if it * is not going to have any declared authority. * @return An view of the identified object as a mutable map. */ public static Map getProperties(final IdentifiedObject info, final Citation authority) { final Map properties = new HashMap(getProperties(info)); properties.put(NAME_KEY, new NamedIdentifier(authority, info.getName().getCode())); properties.remove(IDENTIFIERS_KEY); return properties; } /** * Returns an object name according the given authority. This method checks first the * {@linkplain IdentifiedObject#getName() primary name}, then all * {@linkplain IdentifiedObject#getAlias() alias} in their iteration order. * *

    *
  • If the name or alias implements the {@link ReferenceIdentifier} interface, * then this method compares the {@linkplain ReferenceIdentifier#getAuthority * identifier authority} against the specified citation using the * {@link org.geotoolkit.metadata.iso.citation.Citations#identifierMatches(Citation,Citation) * identifierMatches} method. If a matching is found, then this method returns the * {@linkplain ReferenceIdentifier#getCode identifier code} of this object.

  • * *
  • 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 * {@linkplain org.geotoolkit.metadata.iso.citation.Citations#identifierMatches(Citation,String) * identifierMatches} method. If a matching is found, then this method returns the * {@linkplain GenericName#tip name tip} of this object.

  • *
* * Note that alias may implement both the {@link ReferenceIdentifier} and {@link GenericName} * interfaces (for example {@link NamedIdentifier}). In such cases, the identifier view has * precedence. * * @param info 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 name (either a {@linkplain ReferenceIdentifier#getCode code} or a * {@linkplain GenericName#tip name tip}), or {@code null} if no name matching the * specified authority was found. * * @see AbstractIdentifiedObject#getName(Citation) */ public static String getName(final IdentifiedObject info, final Citation authority) { if (info instanceof AbstractIdentifiedObject) { // Gives a chances to subclasses to get their overridden method invoked. return ((AbstractIdentifiedObject) info).getName(authority); } return name(info, authority); } /** * Returns an object name according the given authority. * * @param info 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 a {@linkplain ReferenceIdentifier#getCode code} or a * {@linkplain GenericName#tip name tip}), or {@code null} if no name matching the * specified authority was found. */ static String name(final IdentifiedObject info, final Citation authority) { if (info == null) { return null; } Identifier identifier = info.getName(); if (authority == null) { return identifier.getCode(); } String name = null; Citation infoAuthority = identifier.getAuthority(); if (infoAuthority != null) { if (identifierMatches(authority, infoAuthority)) { name = identifier.getCode(); } else { for (final GenericName alias : info.getAlias()) { if (alias != null) { // Paranoiac check. if (alias instanceof Identifier) { identifier = (Identifier) alias; infoAuthority = identifier.getAuthority(); if (infoAuthority != null && identifierMatches(authority, infoAuthority)) { name = identifier.getCode(); break; } } else { final GenericName scope = alias.scope().name(); if (scope != null && identifierMatches(authority, scope.toString())) { name = alias.toString(); break; } } } } } } return name; } /** * Returns an identifier for the given object according the given authority. This method checks * all {@linkplain IdentifiedObject#getIdentifiers() identifiers} in their iteration order. It * returns the first identifier with an {@linkplain ReferenceIdentifier#getAuthority authority} * citation {@linkplain org.geotoolkit.metadata.iso.citation.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 was found. * * @see AbstractIdentifiedObject#getIdentifier(Citation) */ public static ReferenceIdentifier getIdentifier(final IdentifiedObject object, final Citation authority) { if (object instanceof AbstractIdentifiedObject) { // Gives a chances to subclasses to get their overridden method invoked. return ((AbstractIdentifiedObject) object).getIdentifier(authority); } return identifier(object, authority); } /** * Returns an identifier according the given 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}. * @return The object's identifier, or {@code null} if none. */ static ReferenceIdentifier identifier(final IdentifiedObject object, final Citation authority) { if (object != null) { final Set identifiers = object.getIdentifiers(); if (identifiers != null) { for (final ReferenceIdentifier identifier : identifiers) { if (identifier != null) { // Paranoiac check. if (authority == null) { return identifier; } final Citation infoAuthority = identifier.getAuthority(); if (infoAuthority != null && identifierMatches(authority, infoAuthority)) { return identifier; } } } } } return null; } /** * Returns the declared identifier, or {@code null} if none. This method searches for the first * identifier (which is usually the main one) explicitly declared in the {@link IdentifiedObject}. * At the opposite of {@link #lookupIdentifier(IdentifiedObject, boolean) lookupIdentifier}, * this method does not verify the identifier validity. *

* More specifically, this method uses the first non-null element found in * object.{@linkplain IdentifiedObject#getIdentifiers() getIdentifiers()}. If there * is none, then it uses object.{@linkplain IdentifiedObject#getName() getName()} - * which is not guaranteed to be a valid identifier. * * {@section Recommanded alternatives} *

    *
  • If the code of a specific authority is wanted (typically EPSG), then consider * using {@link #getIdentifier(IdentifiedObject, Citation)} instead.
  • *
  • In many cases, the identifier is not specified. For an exhaustive scan of the EPSG * database looking for a match, use one of the lookup methods defined below.
  • *
* * @param object The identified object, or {@code null}. * @return Identifier represented as a string for communication between systems, or {@code null}. * * @see #getIdentifier(IdentifiedObject, Citation) * @see #lookupIdentifier(IdentifiedObject, boolean) */ public static String getIdentifier(final IdentifiedObject object) { if (object != null) { final Set identifiers = object.getIdentifiers(); if (identifiers != null) { for (final ReferenceIdentifier identifier : identifiers) { if (identifier != null) { // Paranoiac check. final String code = identifier.toString(); if (code != null) { // Paranoiac check. return code; } } } } final ReferenceIdentifier name = object.getName(); if (name != null) { return name.toString(); } } return null; } /** * Looks up an {@linkplain ReferenceIdentifier identifier}, such as {@code "EPSG:4326"}, * of the specified object. This method searches in registered factories for an object * {@linkplain ComparisonMode#APPROXIMATIVE approximatively equals} to the specified * object. If such an object is found, then its first identifier is returned. Otherwise * this method returns {@code null}. *

* Note that this method checks the identifier validity. 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 * returns {@code null} even if the given object declares explicitly its identifier. If * the declared identifier is wanted unconditionally, use * {@link #getIdentifier(IdentifiedObject)} instead. * * @param object The object (usually a {@linkplain CoordinateReferenceSystem coordinate * reference system}) whose identifier is to be found, or {@code null}. * @param fullScan If {@code true}, an exhaustive full scan against all registered objects * should be performed (may be slow). Otherwise only a fast lookup based on embedded * identifiers and names will be performed. * @return The identifier, or {@code null} if none was found or if the given object was null. * @throws FactoryException If an unexpected failure occurred during the search. * * @see AbstractAuthorityFactory#getIdentifiedObjectFinder(Class) * @see IdentifiedObjectFinder#findIdentifier(IdentifiedObject) */ public static String lookupIdentifier(final IdentifiedObject object, final boolean fullScan) throws FactoryException { if (object == null) { return null; } /* * We perform the search using the 'xyFactory' because our implementation of * IdentifiedObjectFinder should be able to inspect both the (x,y) and (y,x) * axis order using this factory. */ final AbstractAuthorityFactory xyFactory = (AbstractAuthorityFactory) CRS.getAuthorityFactory(true); final IdentifiedObjectFinder finder = xyFactory.getIdentifiedObjectFinder(object.getClass()); finder.setComparisonMode(ComparisonMode.APPROXIMATIVE); finder.setFullScanAllowed(fullScan); return finder.findIdentifier(object); } /** * Looks up an {@linkplain ReferenceIdentifier identifier} in the namespace of the given * authority, such as {@link Citations#EPSG EPSG}, of the specified CRS. Invoking this * method is equivalent to invoking * {@linkplain #lookupIdentifier(IdentifiedObject, boolean) lookupIdentifier}(object, * fullScan) except that the search is performed only among the factories of the given * authority. * * {@section Identifiers in URN and HTTP namespaces} * Note that if the given authority is {@link Citations#URN_OGC} or {@link Citations#HTTP_OGC}, * then this method behaves as if the code was searched in all authority factories and the * result formatted in a {@code "urn:ogc:def:"} or * {@value org.geotoolkit.referencing.factory.web.HTTP_AuthorityFactory#BASE_URL} namespace. * * @param authority The authority for the code to search. * @param object The object (usually a {@linkplain CoordinateReferenceSystem coordinate * reference system}) whose identifier is to be found, or {@code null}. * @param fullScan If {@code true}, an exhaustive full scan against all registered objects * should be performed (may be slow). Otherwise only a fast lookup based on embedded * identifiers and names will be performed. * @return The identifier, or {@code null} if none was found or if the given object was null. * @throws FactoryException If an unexpected failure occurred during the search. * * @category information */ public static String lookupIdentifier(final Citation authority, final IdentifiedObject object, final boolean fullScan) throws FactoryException { ensureNonNull("authority", authority); if (object == null) { return null; } ReferenceIdentifier id = IdentifiedObjects.getIdentifier(object, authority); if (id != null) { return id.getCode(); } final DefaultAuthorityFactory df = (DefaultAuthorityFactory) CRS.getAuthorityFactory(true); for (final AuthorityFactory factory : df.backingStore.getFactories()) { if (!Citations.identifierMatches(factory.getAuthority(), authority)) { continue; } if (!(factory instanceof AbstractAuthorityFactory)) { continue; } final AbstractAuthorityFactory f = (AbstractAuthorityFactory) factory; final IdentifiedObjectFinder finder = f.getIdentifiedObjectFinder(object.getClass()); finder.setComparisonMode(ComparisonMode.APPROXIMATIVE); finder.setFullScanAllowed(fullScan); final String code = finder.findIdentifier(object); if (code != null) { return code; } } return null; } /** * Looks up an EPSG code of the given {@linkplain CoordinateReferenceSystem * coordinate reference system}). This is a convenience method for {@linkplain * #lookupIdentifier(Citation, IdentifiedObject, boolean) lookupIdentifier}({@linkplain * Citations#EPSG EPSG}, crs, fullScan) with the returned code parsed as an integer. * * @param object The object (usually a {@linkplain CoordinateReferenceSystem coordinate * reference system}) whose identifier is to be found, or {@code null}. * @param fullScan If {@code true}, an exhaustive full scan against all registered objects * should be performed (may be slow). Otherwise only a fast lookup based on embedded * identifiers and names will be performed. * @return The identifier, or {@code null} if none was found or if the given object was null. * @throws FactoryException If an unexpected failure occurred during the search. * * @category information */ public static Integer lookupEpsgCode(final IdentifiedObject object, final boolean fullScan) throws FactoryException { final String identifier = lookupIdentifier(Citations.EPSG, object, fullScan); if (identifier != null) { final int split = identifier.lastIndexOf(DefaultNameSpace.DEFAULT_SEPARATOR); final String code = identifier.substring(split + 1); // The above code works even if the separator was not found, since in such case // split == -1, which implies a call to substring(0) which returns 'identifier'. try { return Integer.parseInt(code); } catch (NumberFormatException e) { throw new FactoryException(Errors.format(Errors.Keys.ILLEGAL_IDENTIFIER_$1, identifier), e); } } return null; } /** * Returns {@code true} if either the {@linkplain IdentifiedObject#getName() primary name} or * at least one {@linkplain IdentifiedObject#getAlias() alias} matches the specified string. * This method performs the search in the following order, regardless of any authority: *

*

    *
  • The {@linkplain IdentifiedObject#getName() primary name} of the object
  • *
  • The {@linkplain ScopedName fully qualified name} of an alias
  • *
  • The {@linkplain LocalName local name} of an alias
  • *
* * @param object The object to check. * @param name The name. * @return {@code true} if the primary name of at least one alias * matches the specified {@code name}. * * @see AbstractIdentifiedObject#nameMatches(String) */ public static boolean nameMatches(final IdentifiedObject object, final String name) { if (object instanceof AbstractIdentifiedObject) { return ((AbstractIdentifiedObject) object).nameMatches(name); } else { ensureNonNull("object", object); return nameMatches(object, object.getAlias(), name); } } /** * Returns {@code true} if the {@linkplain IdentifiedObject#getName() primary name} of an * object matches the primary name or one {@linkplain IdentifiedObject#getAlias() alias} * of the other object. * * @param o1 The first object to compare by name. * @param o2 The second object to compare by name. * @return {@code true} if both objects have a common name. */ public static boolean nameMatches(final IdentifiedObject o1, final IdentifiedObject o2) { ensureNonNull("o1", o1); ensureNonNull("o2", o2); return nameMatches(o1, o2.getName().getCode()) || nameMatches(o2, o1.getName().getCode()); } /** * Returns {@code true} if the {@linkplain #getName() primary name} of the given object * or one of the given alias matches the given name. * * @param object The object to check. * @param alias The list of alias in {@code object} (may be {@code null}). * This method will never modify this list. Consequently, it * may be a direct reference to an internal array. * @param name The name for which to check for equality. * @return {@code true} if the primary name or at least one alias matches the given {@code name}. */ static boolean nameMatches(final IdentifiedObject object, final Collection alias, String name) { name = name.trim(); if (name.equalsIgnoreCase(object.getName().getCode().trim())) { return true; } if (alias != null) { for (GenericName asName : alias) { if (asName != null) { // Paranoiac check. asName = asName.toFullyQualifiedName(); while (asName != null) { if (name.equalsIgnoreCase(asName.toString().trim())) { return true; } if (!(asName instanceof ScopedName)) { break; } asName = ((ScopedName) asName).tail(); } } } } return false; } } /** * {@link IdentifiedObjects#NAME_COMPARATOR} implementation as a named class (rather than anonymous) * for more predictable serialization. */ final class NameComparator implements Comparator, Serializable { /** For cross-version compatibility. */ private static final long serialVersionUID = -6605097017814062198L; /** Compares the given identified objects for order. */ @Override public int compare(final IdentifiedObject o1, final IdentifiedObject o2) { return IdentifiedObjects.doCompare(o1.getName().getCode(), o2.getName().getCode()); } /** Canonicalizes to the singleton on deserialization. */ protected Object readResolve() throws ObjectStreamException { return IdentifiedObjects.NAME_COMPARATOR; } } /** * {@link IdentifiedObjects#IDENTIFIER_COMPARATOR} implementation as a named class (rather than anonymous) * for more predictable serialization. */ final class IdentifierComparator implements Comparator, Serializable { /** For cross-version compatibility. */ private static final long serialVersionUID = -7315726806679993522L; /** Compares the given identified objects for order. */ @Override public int compare(final IdentifiedObject o1, final IdentifiedObject o2) { Collection a1 = o1.getIdentifiers(); Collection a2 = o2.getIdentifiers(); if (a1 == null) a1 = Collections.emptySet(); if (a2 == null) a2 = Collections.emptySet(); final Iterator i1 = a1.iterator(); final Iterator i2 = a2.iterator(); boolean n1, n2; while ((n1 = i1.hasNext()) & (n2 = i2.hasNext())) { // NOSONAR: Really '&', not '&&' final int c = IdentifiedObjects.doCompare(i1.next().getCode(), i2.next().getCode()); if (c != 0) { return c; } } if (n1) return +1; if (n2) return -1; return 0; } /** Canonicalizes to the singleton on deserialization. */ protected Object readResolve() throws ObjectStreamException { return IdentifiedObjects.IDENTIFIER_COMPARATOR; } } /** * {@link IdentifiedObjects#REMARKS_COMPARATOR} implementation as a named class (rather than anonymous) * for more predictable serialization. */ final class RemarksComparator implements Comparator, Serializable { /** For cross-version compatibility. */ private static final long serialVersionUID = -6675419613224162715L; /** Compares the given identified objects for order. */ @Override public int compare(final IdentifiedObject o1, final IdentifiedObject o2) { return IdentifiedObjects.doCompare(o1.getRemarks(), o2.getRemarks()); } /** Canonicalizes to the singleton on deserialization. */ protected Object readResolve() throws ObjectStreamException { return IdentifiedObjects.REMARKS_COMPARATOR; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy