Search in sources :

Example 1 with DefinitionURI

use of org.apache.sis.internal.util.DefinitionURI in project sis by apache.

the class MultiAuthoritiesFactory method create.

/**
 * Creates an object from a code using the given proxy.
 *
 * @param  <T>    the type of the object to be returned.
 * @param  proxy  the proxy to use for creating the object.
 * @param  code   the code of the object to create.
 * @return the object from one of the authority factory specified at construction time.
 * @throws FactoryException if an error occurred while creating the object.
 */
private <T> T create(AuthorityFactoryProxy<? extends T> proxy, String code) throws FactoryException {
    ArgumentChecks.ensureNonNull("code", code);
    final String authority, version;
    final String[] parameters;
    final DefinitionURI uri = DefinitionURI.parse(code);
    if (uri != null) {
        Class<? extends T> type = proxy.type;
        proxy = proxy.specialize(uri.type);
        /*
             * If the URN or URL contains combined references for compound coordinate reference systems,
             * create the components. First we verify that all component references have been parsed
             * before to start creating any object.
             */
        if (uri.code == null) {
            final DefinitionURI[] components = uri.components;
            if (components != null) {
                for (int i = 0; i < components.length; i++) {
                    if (components[i] == null) {
                        throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.CanNotParseCombinedReference_2, i + 1, uri.isHTTP ? 1 : 0), uri.authority, null, uri.toString());
                    }
                }
                // Use the more specific type declared in the URN.
                if (proxy != null)
                    type = proxy.type;
                return combine(type, components, uri.isHTTP);
            }
        }
        /*
             * At this point we determined that the URN or URL references a single instance (not combined references).
             * Example: "urn:ogc:def:crs:EPSG:9.1:4326". Verifies that the object type is recognized and that a code
             * is present. The remainder steps are the same as if the user gave a simple code (e.g. "EPSG:4326").
             */
        if (uri.authority == null) {
            // We want this check before the 'code' value is modified below.
            throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.MissingAuthority_1, code), null, uri.code, code);
        }
        authority = uri.authority;
        version = uri.version;
        code = uri.code;
        parameters = uri.parameters;
        if (code == null || proxy == null) {
            final String s = uri.toString();
            final String message;
            if (code == null) {
                message = Errors.format(Errors.Keys.MissingComponentInElement_2, s, "code");
            } else {
                message = Resources.format(Resources.Keys.CanNotCreateObjectAsInstanceOf_2, type, DefinitionURI.PREFIX + DefinitionURI.SEPARATOR + uri.type);
            }
            throw new NoSuchAuthorityCodeException(message, authority, code, s);
        }
    } else {
        /*
             * Separate the authority from the rest of the code. The authority is mandatory; if missing,
             * an exception will be thrown. Note that the CharSequences.skipLeading/TrailingWhitespaces(…)
             * methods are robust to negative index, so the code will work even if code.indexOf(…) returned -1.
             */
        int afterAuthority = code.indexOf(DefaultNameSpace.DEFAULT_SEPARATOR);
        int end = CharSequences.skipTrailingWhitespaces(code, 0, afterAuthority);
        int start = CharSequences.skipLeadingWhitespaces(code, 0, end);
        if (start >= end) {
            throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.MissingAuthority_1, code), null, code);
        }
        authority = code.substring(start, end);
        /*
             * Separate the version from the rest of the code. The version is optional. The code may have no room
             * for version (e.g. "EPSG:4326"), or specify an empty version (e.g. "EPSG::4326"). If the version is
             * equals to an empty string or to the "0" string, it will be considered as no version. Usage of 0 as
             * a pseudo-version is a practice commonly found in other software products.
             */
        int afterVersion = code.indexOf(DefaultNameSpace.DEFAULT_SEPARATOR, ++afterAuthority);
        start = CharSequences.skipLeadingWhitespaces(code, afterAuthority, afterVersion);
        end = CharSequences.skipTrailingWhitespaces(code, start, afterVersion);
        version = (start < end && !code.regionMatches(start, DefinitionURI.NO_VERSION, 0, DefinitionURI.NO_VERSION.length())) ? code.substring(start, end) : null;
        if (version != null && !Character.isUnicodeIdentifierPart(version.codePointAt(0))) {
            throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.InvalidVersionIdentifier_1, version), authority, code);
        }
        /*
             * Separate the code from the authority and the version.
             */
        code = CharSequences.trimWhitespaces(code, Math.max(afterAuthority, afterVersion + 1), code.length()).toString();
        parameters = null;
    }
    /*
         * At this point we have the code without the authority and version parts.
         * Push back the authority part if the factory may need it. For now we do that only if the code has
         * parameters, since interpretation of the unit parameter in "AUTO(2):42001,unit,longitude,latitude"
         * depends on whether the authority is "AUTO" or "AUTO2". This works for now, but we may need a more
         * rigorous approach in a future SIS version.
         */
    if (parameters != null || code.indexOf(CommonAuthorityFactory.SEPARATOR) >= 0) {
        final StringBuilder buffer = new StringBuilder(authority.length() + code.length() + 1).append(authority).append(DefaultNameSpace.DEFAULT_SEPARATOR).append(code);
        if (parameters != null) {
            for (final String p : parameters) {
                buffer.append(CommonAuthorityFactory.SEPARATOR).append(p);
            }
        }
        code = buffer.toString();
    }
    return proxy.createFromAPI(getAuthorityFactory(AuthorityFactoryIdentifier.create(proxy.factoryType, authority, version)), code);
}
Also used : InternationalString(org.opengis.util.InternationalString) DefinitionURI(org.apache.sis.internal.util.DefinitionURI)

Example 2 with DefinitionURI

use of org.apache.sis.internal.util.DefinitionURI in project sis by apache.

the class MultiAuthoritiesFactory method combine.

/**
 * Invoked when a {@code createFoo(…)} method is given a combined URI.
 * A combined URI is a URN or URL referencing other components. For example if the given URI
 * is {@code "urn:ogc:def:crs, crs:EPSG::27700, crs:EPSG::5701"}, then the components are:
 * <ol>
 *   <li>{@code "urn:ogc:def:crs:EPSG:9.1:27700"}</li>
 *   <li>{@code "urn:ogc:def:crs:EPSG:9.1:5701"}</li>
 * </ol>
 *
 * We do not require the components to be instance of CRS, since the "Definition identifier URNs in
 * OGC namespace" best practice paper allows other kinds of combination (e.g. of coordinate operations).
 *
 * @param  <T>         compile-time value of {@code type} argument.
 * @param  type        type of object to create.
 * @param  references  parsed URI of the components.
 * @param  isHTTP      whether the user URI is an URL (i.e. {@code "http://something"}) instead than a URN.
 * @return the combined object.
 * @throws FactoryException if an error occurred while creating the combined object.
 */
private <T> T combine(final Class<T> type, final DefinitionURI[] references, final boolean isHTTP) throws FactoryException {
    /*
         * Identify the type requested by the user and create all components with the assumption that they will
         * be of that type. This is the most common case. If during iteration we find an object of another kind,
         * then the array type will be downgraded to IdentifiedObject[]. The 'componentType' variable will keep
         * its non-null value only if the array stay of the expected sub-type.
         */
    final byte requestedType;
    IdentifiedObject[] components;
    Class<? extends IdentifiedObject> componentType;
    if (CoordinateReferenceSystem.class.isAssignableFrom(type)) {
        requestedType = AuthorityFactoryIdentifier.CRS;
        componentType = CoordinateReferenceSystem.class;
        // Intentional covariance.
        components = new CoordinateReferenceSystem[references.length];
    } else if (CoordinateOperation.class.isAssignableFrom(type)) {
        requestedType = AuthorityFactoryIdentifier.OPERATION;
        componentType = CoordinateOperation.class;
        // Intentional covariance.
        components = new CoordinateOperation[references.length];
    } else {
        throw new FactoryException(Resources.format(Resources.Keys.CanNotCombineUriAsType_1, type));
    }
    // Note: "compound-crs" ⟶ "crs".
    final String expected = NameMeaning.toObjectType(componentType);
    for (int i = 0; i < references.length; i++) {
        final DefinitionURI ref = references[i];
        final IdentifiedObject component = createObject(ref.toString());
        if (componentType != null && (!componentType.isInstance(component) || !expected.equalsIgnoreCase(ref.type))) {
            componentType = null;
            components = Arrays.copyOf(components, components.length, IdentifiedObject[].class);
        }
        components[i] = component;
    }
    /*
         * At this point we have successfully created all components. The way to interpret those components
         * depends mostly on the type of object requested by the user. For a given requested type, different
         * rules apply depending on the type of components. Those rules are described in OGC 07-092r1 (2007):
         * "Definition identifier URNs in OGC namespace".
         */
    IdentifiedObject combined = null;
    switch(requestedType) {
        case AuthorityFactoryIdentifier.OPERATION:
            {
                if (componentType != null) {
                    /*
                     * URN combined references for concatenated operations. We build an operation name from
                     * the operation identifiers (rather than CRS identifiers) because this is what the user
                     * gave to us, and because source/target CRS are not guaranteed to be defined. We do not
                     * yet support swapping roles of source and target CRS if an implied-reverse coordinate
                     * operation is included.
                     */
                    final CoordinateOperation[] ops = (CoordinateOperation[]) components;
                    String name = IdentifiedObjects.getIdentifierOrName(ops[0]) + " ⟶ " + IdentifiedObjects.getIdentifierOrName(ops[ops.length - 1]);
                    combined = DefaultFactories.forBuildin(CoordinateOperationFactory.class).createConcatenatedOperation(Collections.singletonMap(CoordinateOperation.NAME_KEY, name), ops);
                }
                break;
            }
        case AuthorityFactoryIdentifier.CRS:
            {
                if (componentType != null) {
                    /*
                     * URN combined references for compound coordinate reference systems.
                     * The URNs of the individual well-known CRSs are listed in the same order in which the
                     * individual coordinate tuples are combined to form the CompoundCRS coordinate tuple.
                     */
                    combined = CRS.compound((CoordinateReferenceSystem[]) components);
                } else if (!isHTTP) {
                    final CoordinateSystem cs = remove(references, components, CoordinateSystem.class);
                    if (cs != null) {
                        final Datum datum = remove(references, components, Datum.class);
                        if (datum != null) {
                            /*
                             * URN combined references for datum and coordinate system. In this case, the URN shall
                             * concatenate the URNs of one well-known datum and one well-known coordinate system.
                             */
                            if (ArraysExt.allEquals(references, null)) {
                                combined = combine((GeodeticDatum) datum, cs);
                            }
                        } else {
                            /*
                             * URN combined references for projected or derived CRSs. In this case, the URN shall
                             * concatenate the URNs of the one well-known CRS, one well-known Conversion, and one
                             * well-known CartesianCS. Similar action can be taken for derived CRS.
                             */
                            CoordinateReferenceSystem baseCRS = remove(references, components, CoordinateReferenceSystem.class);
                            CoordinateOperation op = remove(references, components, CoordinateOperation.class);
                            if (ArraysExt.allEquals(references, null) && op instanceof Conversion) {
                                combined = combine(baseCRS, (Conversion) op, cs);
                            }
                        }
                    }
                }
                break;
            }
    }
    /*
         * At this point the combined object has been created if we know how to create it.
         * Maybe the result matches the definition of an existing object in the database,
         * in which case we will use the existing definition for better metadata.
         */
    if (combined == null) {
        throw new FactoryException(Resources.format(Resources.Keys.UnexpectedComponentInURI));
    }
    final IdentifiedObject existing = newIdentifiedObjectFinder().findSingleton(combined);
    return type.cast(existing != null ? existing : combined);
}
Also used : FactoryException(org.opengis.util.FactoryException) InternationalString(org.opengis.util.InternationalString) DefinitionURI(org.apache.sis.internal.util.DefinitionURI)

Example 3 with DefinitionURI

use of org.apache.sis.internal.util.DefinitionURI in project sis by apache.

the class Code method getIdentifier.

/**
 * Returns the identifier for this value. This method is the converse of the constructor.
 * If the {@link #codeSpace} contains a semicolon, then the part after the last semicolon
 * will be taken as the authority version number. This is for consistency with what the
 * constructor does.
 *
 * @return the identifier, or {@code null} if none.
 */
public ReferenceIdentifier getIdentifier() {
    String c = code;
    if (c == null) {
        return null;
    }
    Citation authority = null;
    String version = null, cs = codeSpace;
    final DefinitionURI parsed = DefinitionURI.parse(c);
    if (parsed != null && parsed.code != null) {
        /*
             * Case where the URN has been successfully parsed. The OGC's URN contains an "authority" component,
             * which we take as the Identifier.codeSpace value (not Identifier.authority despite what the names
             * would suggest).
             *
             * The GML document may also provide a 'codeSpace' attribute separated from the URN, which we take
             * as the authority.  This is the opposite of what the names would suggest, but we can not map the
             * 'codeSpace' attribute to Identifier.codeSpace  because the 'codeSpace' attribute value found in
             * practice is often "IOGP" while the 'Identifier.description' example provided in ISO 19115-1 for
             * an EPSG code has the "EPSG" codespace. Example:
             *
             *    - XML: <gml:identifier codeSpace="IOGP">urn:ogc:def:crs:EPSG::4326</gml:identifier>
             *    - ISO: For "EPSG:4326", Identifier.codeSpace = "EPSG" and Identifier.code = "4326".
             *
             * Apache SIS attempts to organize this apparent contradiction by considering IOGP as the codespace of
             * the EPSG codespace, but this interpretation is not likely to be widely used by libraries other than
             * SIS. For now, a special handling is hard-coded below: if codeSpace = "IOGP" and authority = "EPSG",
             * then we take the authority as the Citations.EPSG constant, which has a "IOGP:EPSG" identifier.
             *
             * A symmetrical special handling for EPSG is done in the 'forIdentifiedObject(…)' method of this class.
             */
        if (org.apache.sis.internal.util.Citations.isEPSG(cs, parsed.authority)) {
            authority = Citations.EPSG;
        } else {
            // May be null.
            authority = Citations.fromName(cs);
        }
        cs = parsed.authority;
        version = parsed.version;
        c = parsed.code;
    } else if (cs != null) {
        /*
             * Case where the URN can not be parsed but a 'codeSpace' attribute exists. We take this 'codeSpace'
             * as both the code space and the authority. As a special case, if there is a semi-colon, we take all
             * text after that semi-color as the version number.
             */
        final int s = cs.lastIndexOf(DefinitionURI.SEPARATOR);
        if (s >= 0) {
            version = cs.substring(s + 1);
            cs = cs.substring(0, s);
        }
        authority = Citations.fromName(cs);
    }
    return new NamedIdentifier(authority, cs, c, version, null);
}
Also used : NamedIdentifier(org.apache.sis.referencing.NamedIdentifier) Citation(org.opengis.metadata.citation.Citation) DefinitionURI(org.apache.sis.internal.util.DefinitionURI)

Aggregations

DefinitionURI (org.apache.sis.internal.util.DefinitionURI)3 InternationalString (org.opengis.util.InternationalString)2 NamedIdentifier (org.apache.sis.referencing.NamedIdentifier)1 Citation (org.opengis.metadata.citation.Citation)1 FactoryException (org.opengis.util.FactoryException)1