Search in sources :

Example 1 with AbstractCS

use of org.apache.sis.referencing.cs.AbstractCS in project sis by apache.

the class GeodeticObjectParser method parseCoordinateSystem.

/**
 * Parses a {@code "CS"} element followed by all {@code "AXIS"} elements.
 * This element has the following pattern (simplified):
 *
 * {@preformat wkt
 *     CS["<type>", dimension],
 *     AXIS["<name>", NORTH | SOUTH | EAST | WEST | UP | DOWN | OTHER],
 *     UNIT["<name>", <conversion factor>],
 *     etc.
 * }
 *
 * This element is different from all other elements parsed by {@code GeodeticObjectParser}
 * in that its components are sibling elements rather than child elements of the CS element.
 *
 * <p>The optional {@code "UNIT[…]"} element shall be parsed by the caller. That element may appear after the
 * {@code "CS[…]"} element (not inside). The unit may be forced to some dimension (e.g. {@code "LengthUnit"})
 * or be any kind of unit, depending on the context in which this {@code parseCoordinateSystem(…)} method is
 * invoked.</p>
 *
 * <h4>Variants of Cartesian type</h4>
 * The {@link WKTKeywords#Cartesian} type may be used for projected, geocentric or other kinds of CRS.
 * However while all those variants are of the same CS type, their axis names and directions differ.
 * Current implementation uses the following rules:
 *
 * <ul>
 *   <li>If the datum is not geodetic, then the axes of the Cartesian CS are unknown.</li>
 *   <li>Otherwise if {@code dimension is 2}, then the CS is assumed to be for a projected CRS.</li>
 *   <li>Otherwise if {@code dimension is 3}, then the CS is assumed to be for a geocentric CRS.</li>
 * </ul>
 *
 * @param  parent       the parent element.
 * @param  type         the expected type (Cartesian | ellipsoidal | vertical | etc…), or null if unknown.
 * @param  dimension    the minimal number of dimensions. Can be 1 if unknown.
 * @param  isWKT1       {@code true} if the parent element is an element from the WKT 1 standard.
 * @param  defaultUnit  the contextual unit (usually {@code Units.METRE} or {@code Units.RADIAN}), or {@code null} if unknown.
 * @param  datum        the datum of the enclosing CRS, or {@code null} if unknown.
 * @return the {@code "CS"}, {@code "UNIT"} and/or {@code "AXIS"} elements as a Coordinate System, or {@code null}.
 * @throws ParseException if an element can not be parsed.
 * @throws FactoryException if the factory can not create the coordinate system.
 */
private CoordinateSystem parseCoordinateSystem(final Element parent, String type, int dimension, final boolean isWKT1, final Unit<?> defaultUnit, final Datum datum) throws ParseException, FactoryException {
    axisOrder.clear();
    final boolean is3D = (dimension >= 3);
    Map<String, Object> csProperties = null;
    /*
         * Parse the CS[<type>, <dimension>] element.  This is specific to the WKT 2 format.
         * In principle the CS element is mandatory, but the Apache SIS parser is lenient on
         * this aspect:  if the CS element is not present, we will compute the same defaults
         * than what we do for WKT 1.
         */
    if (!isWKT1) {
        final Element element = parent.pullElement(OPTIONAL, WKTKeywords.CS);
        if (element != null) {
            final String expected = type;
            type = element.pullVoidElement("type").keyword;
            dimension = element.pullInteger("dimension");
            csProperties = new HashMap<>(parseMetadataAndClose(element, "CS", null));
            if (expected != null) {
                if (!expected.equalsIgnoreCase(type)) {
                    throw new UnparsableObjectException(errorLocale, Errors.Keys.UnexpectedValueInElement_2, new String[] { WKTKeywords.CS, type }, element.offset);
                }
            }
            if (dimension <= 0 || dimension >= Numerics.MAXIMUM_MATRIX_SIZE) {
                final short key;
                final Object[] args;
                if (dimension <= 0) {
                    key = Errors.Keys.ValueNotGreaterThanZero_2;
                    args = new Object[] { "dimension", dimension };
                } else {
                    key = Errors.Keys.ExcessiveNumberOfDimensions_1;
                    args = new Object[] { dimension };
                }
                throw new UnparsableObjectException(errorLocale, key, args, element.offset);
            }
            type = type.equalsIgnoreCase(WKTKeywords.Cartesian) ? WKTKeywords.Cartesian : type.toLowerCase(symbols.getLocale());
        }
    }
    /*
         * AXIS[…] elements are optional, but if we find one we will request that there is as many axes
         * as the number of dimensions. If there is more axes than expected, we may emit an error later
         * depending on the CS type.
         *
         * AXIS[…] elements will be parsed for verifying the syntax, but otherwise ignored if the parsing
         * convention is WKT1_IGNORE_AXES. This is for compatibility with the way some other libraries
         * parse WKT 1.
         */
    CoordinateSystemAxis[] axes = null;
    CoordinateSystemAxis axis = parseAxis(type == null ? MANDATORY : OPTIONAL, parent, type, defaultUnit);
    if (axis != null) {
        final List<CoordinateSystemAxis> list = new ArrayList<>(dimension + 2);
        do {
            list.add(axis);
            axis = parseAxis(list.size() < dimension ? MANDATORY : OPTIONAL, parent, type, defaultUnit);
        } while (axis != null);
        if (!isWKT1 || !ignoreAxes) {
            axes = list.toArray(new CoordinateSystemAxis[list.size()]);
            // Take ORDER[n] elements in account.
            Arrays.sort(axes, this);
        }
    }
    /*
         * If there is no explicit AXIS[…] elements, or if the user asked to ignore them, then we need to
         * create default axes. This is possible only if we know the type of the CS to create, and only
         * for some of those CS types.
         */
    final CSFactory csFactory = factories.getCSFactory();
    if (axes == null) {
        if (type == null) {
            throw parent.missingComponent(WKTKeywords.Axis);
        }
        // Easting or Longitude axis name and abbreviation.
        String nx = null, x = null;
        // Northing or latitude axis name and abbreviation.
        String ny = null, y = null;
        // Depth, height or time axis name and abbreviation.
        String nz = null, z = null;
        AxisDirection dx = AxisDirection.EAST;
        AxisDirection dy = AxisDirection.NORTH;
        // Depth, height or time axis direction.
        AxisDirection direction = null;
        // Depth, height or time axis unit.
        Unit<?> unit = defaultUnit;
        switch(type) {
            /*
                 * Cartesian — we can create axes only for geodetic datum, in which case the axes are for
                 * two-dimensional Projected or three-dimensional Geocentric CRS.
                 */
            case WKTKeywords.Cartesian:
                {
                    if (!(datum instanceof GeodeticDatum)) {
                        throw parent.missingComponent(WKTKeywords.Axis);
                    }
                    if (defaultUnit == null) {
                        throw parent.missingComponent(WKTKeywords.LengthUnit);
                    }
                    if (is3D) {
                        // If dimension can not be 2, then CRS can not be Projected.
                        return Legacy.standard(defaultUnit.asType(Length.class));
                    }
                    nx = AxisNames.EASTING;
                    x = "E";
                    ny = AxisNames.NORTHING;
                    y = "N";
                    if (dimension >= 3) {
                        // Non-standard but SIS is tolerant to this case.
                        z = "h";
                        nz = AxisNames.ELLIPSOIDAL_HEIGHT;
                        unit = Units.METRE;
                    }
                    break;
                }
            /*
                 * Ellipsoidal — can be two- or three- dimensional, in which case the height can
                 * only be ellipsoidal height. The default axis order depends on the WKT version:
                 *
                 *   - WKT 1 said explicitly that the default order is (longitude, latitude).
                 *   - WKT 2 has no default, and allows only (latitude, longitude) order.
                 */
            case WKTKeywords.ellipsoidal:
                {
                    if (defaultUnit == null) {
                        throw parent.missingComponent(WKTKeywords.AngleUnit);
                    }
                    if (isWKT1) {
                        nx = AxisNames.GEODETIC_LONGITUDE;
                        x = "λ";
                        ny = AxisNames.GEODETIC_LATITUDE;
                        y = "φ";
                    } else {
                        nx = AxisNames.GEODETIC_LATITUDE;
                        x = "φ";
                        dx = AxisDirection.NORTH;
                        ny = AxisNames.GEODETIC_LONGITUDE;
                        y = "λ";
                        dy = AxisDirection.EAST;
                    }
                    if (dimension >= 3) {
                        direction = AxisDirection.UP;
                        z = "h";
                        nz = AxisNames.ELLIPSOIDAL_HEIGHT;
                        unit = Units.METRE;
                    }
                    break;
                }
            /*
                 * Vertical — the default name and symbol depends on whether this is depth,
                 * geoidal height, ellipsoidal height (non-standard) or other kind of heights.
                 */
            case WKTKeywords.vertical:
                {
                    if (defaultUnit == null) {
                        throw parent.missingComponent(WKTKeywords.Unit);
                    }
                    z = "h";
                    nz = "Height";
                    direction = AxisDirection.UP;
                    if (datum instanceof VerticalDatum) {
                        final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType();
                        if (vt == VerticalDatumType.GEOIDAL) {
                            nz = AxisNames.GRAVITY_RELATED_HEIGHT;
                            z = "H";
                        } else if (vt == VerticalDatumType.DEPTH) {
                            direction = AxisDirection.DOWN;
                            nz = AxisNames.DEPTH;
                            z = "D";
                        } else if (vt == VerticalDatumTypes.ELLIPSOIDAL) {
                            // Not allowed by ISO 19111 as a standalone axis, but SIS is
                            // tolerant to this case since it is sometime hard to avoid.
                            nz = AxisNames.ELLIPSOIDAL_HEIGHT;
                        }
                    }
                    break;
                }
            /*
                 * Temporal — axis name and abbreviation not yet specified by ISO 19111.
                 */
            case WKTKeywords.temporal:
                {
                    if (defaultUnit == null) {
                        throw parent.missingComponent(WKTKeywords.TimeUnit);
                    }
                    direction = AxisDirection.FUTURE;
                    nz = "Time";
                    z = "t";
                    break;
                }
            /*
                 * Parametric — axis name and abbreviation not yet specified by ISO 19111_2.
                 */
            case WKTKeywords.parametric:
                {
                    if (defaultUnit == null) {
                        throw parent.missingComponent(WKTKeywords.ParametricUnit);
                    }
                    direction = AxisDirection.OTHER;
                    nz = "Parametric";
                    z = "p";
                    break;
                }
            /*
                 * Unknown CS type — we can not guess which axes to create.
                 */
            default:
                {
                    throw parent.missingComponent(WKTKeywords.Axis);
                }
        }
        int i = 0;
        axes = new CoordinateSystemAxis[dimension];
        if (x != null && i < dimension)
            axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nx), x, dx, defaultUnit);
        if (y != null && i < dimension)
            axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, ny), y, dy, defaultUnit);
        if (z != null && i < dimension)
            axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nz), z, direction, unit);
    // Not a problem if the array does not have the expected length for the CS type. This will be verified below in this method.
    }
    /*
         * Infer a CS name will be inferred from the axes if possible.
         * Example: "Compound CS: East (km), North (km), Up (m)."
         */
    final String name;
    {
        // For keeping the 'buffer' variable local to this block.
        final StringBuilder buffer = new StringBuilder();
        if (type != null && !type.isEmpty()) {
            final int c = type.codePointAt(0);
            buffer.appendCodePoint(Character.toUpperCase(c)).append(type, Character.charCount(c), type.length()).append(' ');
        }
        name = AxisDirections.appendTo(buffer.append("CS"), axes);
    }
    if (csProperties == null) {
        csProperties = singletonMap(CoordinateSystem.NAME_KEY, name);
    } else {
        csProperties.put(CoordinateSystem.NAME_KEY, name);
    }
    if (type == null) {
        /*
             * Creates a coordinate system of unknown type. This block is executed during parsing of WKT version 1,
             * since that legacy format did not specified any information about the coordinate system in use.
             * This block should not be executed during parsing of WKT version 2.
             */
        return new AbstractCS(csProperties, axes);
    }
    /*
         * Finally, delegate to the factory method corresponding to the CS type and the number of axes.
         */
    switch(type) {
        case WKTKeywords.ellipsoidal:
            {
                switch(axes.length) {
                    case 2:
                        return csFactory.createEllipsoidalCS(csProperties, axes[0], axes[1]);
                    case 3:
                        return csFactory.createEllipsoidalCS(csProperties, axes[0], axes[1], axes[2]);
                }
                // For error message.
                dimension = (axes.length < 2) ? 2 : 3;
                break;
            }
        case WKTKeywords.Cartesian:
            {
                switch(axes.length) {
                    case 2:
                        return csFactory.createCartesianCS(csProperties, axes[0], axes[1]);
                    case 3:
                        return csFactory.createCartesianCS(csProperties, axes[0], axes[1], axes[2]);
                }
                // For error message.
                dimension = (axes.length < 2) ? 2 : 3;
                break;
            }
        case WKTKeywords.affine:
            {
                switch(axes.length) {
                    case 2:
                        return csFactory.createAffineCS(csProperties, axes[0], axes[1]);
                    case 3:
                        return csFactory.createAffineCS(csProperties, axes[0], axes[1], axes[2]);
                }
                // For error message.
                dimension = (axes.length < 2) ? 2 : 3;
                break;
            }
        case WKTKeywords.vertical:
            {
                if (axes.length != (dimension = 1))
                    break;
                return csFactory.createVerticalCS(csProperties, axes[0]);
            }
        case WKTKeywords.temporal:
            {
                if (axes.length != (dimension = 1))
                    break;
                return csFactory.createTimeCS(csProperties, axes[0]);
            }
        case WKTKeywords.linear:
            {
                if (axes.length != (dimension = 1))
                    break;
                return csFactory.createLinearCS(csProperties, axes[0]);
            }
        case WKTKeywords.polar:
            {
                if (axes.length != (dimension = 2))
                    break;
                return csFactory.createPolarCS(csProperties, axes[0], axes[1]);
            }
        case WKTKeywords.cylindrical:
            {
                if (axes.length != (dimension = 3))
                    break;
                return csFactory.createCylindricalCS(csProperties, axes[0], axes[1], axes[2]);
            }
        case WKTKeywords.spherical:
            {
                if (axes.length != (dimension = 3))
                    break;
                return csFactory.createSphericalCS(csProperties, axes[0], axes[1], axes[2]);
            }
        case WKTKeywords.parametric:
            {
                if (axes.length != (dimension = 1))
                    break;
                return ServicesForMetadata.createParametricCS(csProperties, axes[0], csFactory);
            }
        default:
            {
                warning(parent, WKTKeywords.CS, Errors.formatInternational(Errors.Keys.UnknownType_1, type), null);
                return new AbstractCS(csProperties, axes);
            }
    }
    throw new UnparsableObjectException(errorLocale, (axes.length > dimension) ? Errors.Keys.TooManyOccurrences_2 : Errors.Keys.TooFewOccurrences_2, new Object[] { dimension, WKTKeywords.Axis }, parent.offset);
}
Also used : ArrayList(java.util.ArrayList) AbstractCS(org.apache.sis.referencing.cs.AbstractCS) IdentifiedObject(org.opengis.referencing.IdentifiedObject)

Example 2 with AbstractCS

use of org.apache.sis.referencing.cs.AbstractCS in project sis by apache.

the class GridExtentCRS method build.

/**
 * Builds a coordinate reference system for the given axis types. The CRS type is always engineering.
 * We can not create temporal CRS because we do not know the temporal datum origin.
 *
 * @param  gridToCRS  matrix of the transform used for converting grid cell indices to envelope coordinates.
 *         It does not matter whether it maps pixel center or corner (translation coefficients are ignored).
 * @param  types   the value of {@link GridExtent#types} or a default value (shall not be {@code null}).
 * @param  locale  locale to use for axis names, or {@code null} for default.
 * @return CRS for the grid, or {@code null}.
 *
 * @see GridExtent#typeFromAxes(CoordinateReferenceSystem, int)
 */
static EngineeringCRS build(final Matrix gridToCRS, final DimensionNameType[] types, final Locale locale) throws FactoryException {
    final int tgtDim = gridToCRS.getNumRow() - 1;
    final int srcDim = Math.min(gridToCRS.getNumCol() - 1, types.length);
    final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[tgtDim];
    final CSFactory csFactory = DefaultFactories.forBuildin(CSFactory.class);
    boolean hasVertical = false;
    boolean hasTime = false;
    boolean hasOther = false;
    for (int i = 0; i < srcDim; i++) {
        final DimensionNameType type = types[i];
        if (type != null) {
            /*
                 * Try to locate the CRS dimension corresponding to grid dimension j.
                 * We expect a one-to-one matching; if it is not the case, return null.
                 * Current version does not accept scale factors, but we could revisit
                 * in a future version if there is a need for it.
                 */
            int target = -1;
            double scale = 0;
            for (int j = 0; j < tgtDim; j++) {
                final double m = gridToCRS.getElement(j, i);
                if (m != 0) {
                    if (target >= 0 || axes[j] != null || Math.abs(m) != 1) {
                        return null;
                    }
                    target = j;
                    scale = m;
                }
            }
            if (target < 0) {
                return null;
            }
            /*
                 * This hard-coded set of axis directions is the converse of
                 * GridExtent.AXIS_DIRECTIONS map.
                 */
            String abbreviation;
            AxisDirection direction;
            if (type == DimensionNameType.COLUMN || type == DimensionNameType.SAMPLE) {
                abbreviation = "x";
                direction = AxisDirection.COLUMN_POSITIVE;
            } else if (type == DimensionNameType.ROW || type == DimensionNameType.LINE) {
                abbreviation = "y";
                direction = AxisDirection.ROW_POSITIVE;
            } else if (type == DimensionNameType.VERTICAL) {
                abbreviation = "z";
                direction = AxisDirection.UP;
                hasVertical = true;
            } else if (type == DimensionNameType.TIME) {
                abbreviation = "t";
                direction = AxisDirection.FUTURE;
                hasTime = true;
            } else {
                abbreviation = abbreviation(target);
                direction = AxisDirection.OTHER;
                hasOther = true;
            }
            /*
                 * Verify that no other axis has the same direction and abbreviation. If duplicated
                 * values are found, keep only the first occurrence in grid axis order (may not be
                 * the CRS axis order).
                 */
            for (int k = tgtDim; --k >= 0; ) {
                final CoordinateSystemAxis previous = axes[k];
                if (previous != null) {
                    if (direction.equals(AxisDirections.absolute(previous.getDirection()))) {
                        direction = AxisDirection.OTHER;
                        hasOther = true;
                    }
                    if (abbreviation.equals(previous.getAbbreviation())) {
                        abbreviation = abbreviation(target);
                    }
                }
            }
            if (scale < 0) {
                direction = AxisDirections.opposite(direction);
            }
            final String name = Types.toString(Types.getCodeTitle(type), locale);
            axes[target] = axis(csFactory, name, abbreviation, direction);
        }
    }
    /*
         * Search for axes that have not been created in above loop.
         * It happens when some axes have no associated `DimensionNameType` code.
         */
    for (int j = 0; j < tgtDim; j++) {
        if (axes[j] == null) {
            final String name = Vocabulary.getResources(locale).getString(Vocabulary.Keys.Dimension_1, j);
            final String abbreviation = abbreviation(j);
            axes[j] = axis(csFactory, name, abbreviation, AxisDirection.OTHER);
        }
    }
    /*
         * Create a coordinate system of affine type if all axes seem spatial.
         * If no specialized type seems to fit, use an unspecified ("abstract")
         * coordinate system type in last resort.
         */
    final Map<String, ?> properties = properties("Grid extent");
    final CoordinateSystem cs;
    if (hasOther || (tgtDim > (hasTime ? 1 : 3))) {
        cs = new AbstractCS(properties, axes);
    } else
        switch(tgtDim) {
            case 1:
                {
                    final CoordinateSystemAxis axis = axes[0];
                    if (hasVertical) {
                        cs = csFactory.createVerticalCS(properties, axis);
                    } else if (hasTime) {
                        cs = csFactory.createTimeCS(properties, axis);
                    } else {
                        cs = csFactory.createLinearCS(properties, axis);
                    }
                    break;
                }
            case 2:
                cs = csFactory.createAffineCS(properties, axes[0], axes[1]);
                break;
            case 3:
                cs = csFactory.createAffineCS(properties, axes[0], axes[1], axes[2]);
                break;
            default:
                return null;
        }
    return DefaultFactories.forBuildin(CRSFactory.class).createEngineeringCRS(properties(cs.getName()), CommonCRS.Engineering.GRID.datum(), cs);
}
Also used : CoordinateSystem(org.opengis.referencing.cs.CoordinateSystem) CoordinateSystemAxis(org.opengis.referencing.cs.CoordinateSystemAxis) CRSFactory(org.opengis.referencing.crs.CRSFactory) AbstractCS(org.apache.sis.referencing.cs.AbstractCS) CSFactory(org.opengis.referencing.cs.CSFactory) AxisDirection(org.opengis.referencing.cs.AxisDirection) DimensionNameType(org.opengis.metadata.spatial.DimensionNameType)

Example 3 with AbstractCS

use of org.apache.sis.referencing.cs.AbstractCS in project sis by apache.

the class AbstractCRS method forConvention.

/**
 * Returns a coordinate reference system equivalent to this one but with axes rearranged according the given
 * convention. If this CRS is already compatible with the given convention, then this method returns {@code this}.
 *
 * @param  convention  the axes convention for which a coordinate reference system is desired.
 * @return a coordinate reference system compatible with the given convention (may be {@code this}).
 *
 * @see AbstractCS#forConvention(AxesConvention)
 */
public synchronized AbstractCRS forConvention(final AxesConvention convention) {
    ensureNonNull("convention", convention);
    AbstractCRS crs = getCached(convention);
    if (crs == null) {
        final AbstractCS cs = AbstractCS.castOrCopy(coordinateSystem);
        final AbstractCS candidate = cs.forConvention(convention);
        if (candidate == cs) {
            crs = this;
        } else {
            /*
                 * Copy properties (scope, domain of validity) except the identifier (e.g. "EPSG:4326")
                 * because the modified CRS is no longer conform to the authoritative definition.
                 * If name contains a namespace (e.g. "EPSG"), remove that namespace for the same reason.
                 * For example "EPSG:WGS 84" will become simply "WGS 84".
                 */
            Map<String, ?> properties = IdentifiedObjects.getProperties(this, IDENTIFIERS_KEY);
            ReferenceIdentifier name = getName();
            if (name.getCodeSpace() != null || name.getAuthority() != null) {
                name = new NamedIdentifier(null, name.getCode());
                final Map<String, Object> copy = new HashMap<>(properties);
                copy.put(NAME_KEY, name);
                properties = copy;
            }
            crs = createSameType(properties, candidate);
        }
        crs = setCached(convention, crs);
    }
    return crs;
}
Also used : AbstractCS(org.apache.sis.referencing.cs.AbstractCS) ReferenceIdentifier(org.opengis.referencing.ReferenceIdentifier) HashMap(java.util.HashMap) NamedIdentifier(org.apache.sis.referencing.NamedIdentifier)

Aggregations

AbstractCS (org.apache.sis.referencing.cs.AbstractCS)3 ArrayList (java.util.ArrayList)1 HashMap (java.util.HashMap)1 NamedIdentifier (org.apache.sis.referencing.NamedIdentifier)1 DimensionNameType (org.opengis.metadata.spatial.DimensionNameType)1 IdentifiedObject (org.opengis.referencing.IdentifiedObject)1 ReferenceIdentifier (org.opengis.referencing.ReferenceIdentifier)1 CRSFactory (org.opengis.referencing.crs.CRSFactory)1 AxisDirection (org.opengis.referencing.cs.AxisDirection)1 CSFactory (org.opengis.referencing.cs.CSFactory)1 CoordinateSystem (org.opengis.referencing.cs.CoordinateSystem)1 CoordinateSystemAxis (org.opengis.referencing.cs.CoordinateSystemAxis)1