Package org.geotools.resources

Source Code of org.geotools.resources.CRSUtilities

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
*    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.
*/
package org.geotools.resources;

import java.util.Map;
import java.util.List;
import java.util.Iterator;
import java.util.Collections;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.measure.unit.Unit;

import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.GeneralDerivedCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.geometry.DirectPosition;

import org.geotools.geometry.GeneralDirectPosition;
import org.geotools.measure.AngleFormat;
import org.geotools.measure.Latitude;
import org.geotools.measure.Longitude;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.cs.DefaultEllipsoidalCS;
import org.geotools.referencing.datum.DefaultGeodeticDatum;
import org.geotools.referencing.datum.DefaultPrimeMeridian;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;


/**
* A set of static methods working on OpenGIS®
* {@linkplain CoordinateReferenceSystem coordinate reference system} objects.
* Some of those methods are useful, but not really rigorous. This is why they
* do not appear in the "official" package, but instead in this private one.
* <strong>Do not rely on this API!</strong> It may change in incompatible way
* in any future release.
*
* @since 2.0
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public final class CRSUtilities {
    /**
     * Do not allow creation of instances of this class.
     */
    private CRSUtilities() {
    }

    /**
     * Returns the dimension within the coordinate system of the first occurrence of an axis
     * colinear with the specified axis. If an axis with the same
     * {@linkplain CoordinateSystemAxis#getDirection direction} or an
     * {@linkplain AxisDirection#inverse opposite} direction than {@code axis}
     * ocurs in the coordinate system, then the dimension of the first such occurrence
     * is returned. That is, the a value <i>k</i> such that:
     *
     * <blockquote><pre>
     * cs.getAxis(<i>k</i>).getDirection().absolute() == axis.getDirection().absolute()
     * </pre></blockquote>
     *
     * is {@code true}. If no such axis occurs in this coordinate system,
     * then {@code -1} is returned.
     * <p>
     * For example, {@code dimensionColinearWith(CoordinateSystemAxis.TIME)}
     * returns the dimension number of time axis.
     *
     * @param  cs   The coordinate system to examine.
     * @param  axis The axis to look for.
     * @return The dimension number of the specified axis, or {@code -1} if none.
     */
    public static int dimensionColinearWith(final CoordinateSystem     cs,
                                            final CoordinateSystemAxis axis)
    {
        int candidate = -1;
        final int dimension = cs.getDimension();
        final AxisDirection direction = axis.getDirection().absolute();
        for (int i=0; i<dimension; i++) {
            final CoordinateSystemAxis xi = cs.getAxis(i);
            if (direction.equals(xi.getDirection().absolute())) {
                candidate = i;
                if (axis.equals(xi)) {
                    break;
                }
            }
        }
        return candidate;
    }

    /**
     * Returns the unit used for all axis in the specified coordinate system.
     * If not all axis uses the same unit, then this method returns {@code null}.
     * This convenience method is often used for Well Know Text (WKT) formatting.
     *
     * @param cs The coordinate system for which to get the unit.
     * @return The unit for all axis in the given coordinate system, or {@code null}.
     *
     * @since 2.2
     */
    public static Unit<?> getUnit(final CoordinateSystem cs) {
        Unit<?> unit = null;
        for (int i=cs.getDimension(); --i>=0;) {
            final Unit<?> candidate = cs.getAxis(i).getUnit();
            if (candidate != null) {
                if (unit == null) {
                    unit = candidate;
                } else if (!unit.equals(candidate)) {
                    return null;
                }
            }
        }
        return unit;
    }

    /**
     * Returns the components of the specified CRS, or {@code null} if none.
     */
    private static List<CoordinateReferenceSystem> getComponents(CoordinateReferenceSystem crs) {
        if (crs instanceof CompoundCRS) {
            final List<CoordinateReferenceSystem> components;
            components = ((CompoundCRS) crs).getCoordinateReferenceSystems();
            if (!components.isEmpty()) {
                return components;
            }
        }
        return null;
    }

    /**
     * Returns the dimension of the first coordinate reference system of the given type. The
     * {@code type} argument must be a subinterface of {@link CoordinateReferenceSystem}.
     * If no such dimension is found, then this method returns {@code -1}.
     *
     * @param  crs  The coordinate reference system (CRS) to examine.
     * @param  type The CRS type to look for.
     *         Must be a subclass of {@link CoordinateReferenceSystem}.
     * @return The dimension range of the specified CRS type, or {@code -1} if none.
     * @throws IllegalArgumentException if the {@code type} is not legal.
     */
    public static int getDimensionOf(final CoordinateReferenceSystem crs,
            final Class<? extends CoordinateReferenceSystem> type)
            throws IllegalArgumentException
    {
        if (type.isAssignableFrom(crs.getClass())) {
            return 0;
        }
        final List<CoordinateReferenceSystem> c = getComponents(crs);
        if (c != null) {
            int offset = 0;
            for (final CoordinateReferenceSystem ci : c) {
                final int index = getDimensionOf(ci, type);
                if (index >= 0) {
                    return index + offset;
                }
                offset += ci.getCoordinateSystem().getDimension();
            }
        }
        return -1;
    }

    /**
     * Returns a sub-coordinate reference system for the specified dimension range.
     *
     * @param  crs   The coordinate reference system to decompose.
     * @param  lower The first dimension to keep, inclusive.
     * @param  upper The last  dimension to keep, exclusive.
     * @return The sub-coordinate system, or {@code null} if {@code crs} can't
     *         be decomposed for dimensions in the range {@code [lower..upper]}.
     */
    public static CoordinateReferenceSystem getSubCRS(CoordinateReferenceSystem crs,
                                                      int lower, int upper)
    {
        int dimension = crs.getCoordinateSystem().getDimension();
        if (lower<0 || lower>upper || upper>dimension) {
            throw new IndexOutOfBoundsException(Errors.format(
                    ErrorKeys.INDEX_OUT_OF_BOUNDS_$1, lower<0 ? lower : upper));
        }
        while (lower!=0 || upper!=dimension) {
            final List<CoordinateReferenceSystem> c = getComponents(crs);
            if (c == null) {
                return null;
            }
            for (final Iterator<CoordinateReferenceSystem> it=c.iterator(); it.hasNext();) {
                crs = it.next();
                dimension = crs.getCoordinateSystem().getDimension();
                if (lower < dimension) {
                    break;
                }
                lower -= dimension;
                upper -= dimension;
            }
        }
        return crs;
    }

    /**
     * Returns a two-dimensional coordinate reference system representing the two first dimensions
     * of the specified coordinate reference system. If {@code crs} is already a two-dimensional
     * CRS, then it is returned unchanged. Otherwise, if it is a {@link CompoundCRS}, then the
     * head coordinate reference system is examined.
     *
     * @param  crs The coordinate system, or {@code null}.
     * @return A two-dimensional coordinate reference system that represents the two first
     *         dimensions of {@code crs}, or {@code null} if {@code crs} was {@code null}.
     * @throws TransformException if {@code crs} can't be reduced to a two-coordinate system.
     *         We use this exception class since this method is usually invoked in the context
     *         of a transformation process.
     */
    public static CoordinateReferenceSystem getCRS2D(CoordinateReferenceSystem crs)
            throws TransformException
    {
        if (crs != null) {
            while (crs.getCoordinateSystem().getDimension() != 2) {
                final List<CoordinateReferenceSystem> c = getComponents(crs);
                if (c == null) {
                    throw new TransformException(Errors.format(
                              ErrorKeys.CANT_REDUCE_TO_TWO_DIMENSIONS_$1, crs.getName()));
                }
                crs = c.get(0);
            }
        }
        return crs;
    }

    /**
     * Changes the dimension declared in the name. For example if {@code name} is
     * "WGS 84 (geographic 3D)", {@code search} is "3D" and {@code replace} is "2D",
     * then this method returns "WGS 84 (geographic 2D)". If the string to search is
     * not found, then it is concatenated to the name.
     *
     * @param object The identified object having the original name.
     * @param search The dimension token to search in the {@code object} name.
     * @param replace The new token to substitute to the one we were looking for.
     * @return The name with the substitution performed.
     *
     * @since 2.6
     */
    public static Map<String,?> changeDimensionInName(final IdentifiedObject object,
            final String search, final String replace)
    {
        final StringBuilder name = new StringBuilder(object.getName().getCode());
        final int last = name.length() - search.length();
        boolean append = true;
        for (int i=name.lastIndexOf(search); i>=0; i=name.lastIndexOf(search, i-1)) {
            if (i != 0 && Character.isLetterOrDigit(name.charAt(i-1))) {
                continue;
            }
            if (i != last && Character.isLetterOrDigit(i + search.length())) {
                continue;
            }
            name.replace(i, i+search.length(), replace);
            i = name.indexOf(". ", i);
            if (i >= 0) {
                /*
                 * Stops the sentence after the dimension, since it may contains details that
                 * are not applicable anymore. For example the EPSG name for 3D EllipsoidalCS is:
                 *
                 *     Ellipsoidal 3D CS. Axes: latitude, longitude, ellipsoidal height.
                 *     Orientations: north, east, up.  UoM: DMSH, DMSH, m.
                 */
                name.setLength(i+1);
            }
            append = false;
            break;
        }
        if (append) {
            if (name.indexOf(" ") >= 0) {
                name.append(" (").append(replace).append(')');
            } else {
                name.append('_').append(replace);
            }
        }
        return Collections.singletonMap(IdentifiedObject.NAME_KEY, name.toString());
    }

    /**
     * Returns the datum of the specified CRS, or {@code null} if none.
     *
     * @param  crs The coordinate reference system for which to get the datum. May be {@code null}.
     * @return The datum in the given CRS, or {@code null} if none.
     */
    public static Datum getDatum(final CoordinateReferenceSystem crs) {
        return (crs instanceof SingleCRS) ? ((SingleCRS) crs).getDatum() : null;
    }

    /**
     * Returns the ellipsoid used by the specified coordinate reference system, providing that
     * the two first dimensions use an instance of {@link GeographicCRS}. Otherwise (i.e. if the
     * two first dimensions are not geographic), returns {@code null}.
     *
     * @param  crs The coordinate reference system for which to get the ellipsoid.
     * @return The ellipsoid in the given CRS, or {@code null} if none.
     */
    public static Ellipsoid getHeadGeoEllipsoid(CoordinateReferenceSystem crs) {
        while (!(crs instanceof GeographicCRS)) {
            final List<CoordinateReferenceSystem> c = getComponents(crs);
            if (c == null) {
                return null;
            }
            crs = c.get(0);
        }
        return ((GeographicCRS) crs).getDatum().getEllipsoid();
    }

    /**
     * Derives a geographic CRS with (<var>longitude</var>, <var>latitude</var>) axis order in
     * decimal degrees, relative to Greenwich. If no such CRS can be obtained of created, returns
     * {@link DefaultGeographicCRS#WGS84}.
     *
     * @param  crs A source CRS.
     * @return A two-dimensional geographic CRS with standard axis. Never {@code null}.
     */
    public static GeographicCRS getStandardGeographicCRS2D(CoordinateReferenceSystem crs) {
        while (crs instanceof GeneralDerivedCRS) {
            crs = ((GeneralDerivedCRS) crs).getBaseCRS();
        }
        if (!(crs instanceof SingleCRS)) {
            return DefaultGeographicCRS.WGS84;
        }
        final Datum datum = ((SingleCRS) crs).getDatum();
        if (!(datum instanceof GeodeticDatum)) {
            return DefaultGeographicCRS.WGS84;
        }
        GeodeticDatum geoDatum = (GeodeticDatum) datum;
        if (geoDatum.getPrimeMeridian().getGreenwichLongitude() != 0) {
            geoDatum = new DefaultGeodeticDatum(geoDatum.getName().getCode(),
                    geoDatum.getEllipsoid(), DefaultPrimeMeridian.GREENWICH);
        } else if (crs instanceof GeographicCRS) {
            if (CRS.equalsIgnoreMetadata(DefaultEllipsoidalCS.GEODETIC_2D, crs.getCoordinateSystem())) {
                return (GeographicCRS) crs;
            }
        }
        return new DefaultGeographicCRS(crs.getName().getCode(), geoDatum, DefaultEllipsoidalCS.GEODETIC_2D);
    }

    /**
     * Transforms the relative distance vector specified by {@code source} and stores
     * the result in {@code dest}.  A relative distance vector is transformed without
     * applying the translation components.
     *
     * @param transform The transform to apply.
     * @param origin The position where to compute the delta transform in the source CS.
     * @param source The distance vector to be delta transformed
     * @return       The result of the transformation.
     * @throws TransformException if the transformation failed.
     *
     * @since 2.3
     */
    public static DirectPosition deltaTransform(final MathTransform  transform,
                                                final DirectPosition origin,
                                                final DirectPosition source)
            throws TransformException
    {
        final int sourceDim = transform.getSourceDimensions();
        final int targetDim = transform.getTargetDimensions();
        DirectPosition P1 = new GeneralDirectPosition(sourceDim);
        DirectPosition P2 = new GeneralDirectPosition(sourceDim);
        for (int i=0; i<sourceDim; i++) {
            final double c = origin.getOrdinate(i);
            final double d = source.getOrdinate(i) * 0.5;
            P1.setOrdinate(i, c-d);
            P2.setOrdinate(i, c+d);
        }
        P1 = transform.transform(P1, (sourceDim==targetDim) ? P1 : null);
        P2 = transform.transform(P2, (sourceDim==targetDim) ? P2 : null);
        for (int i=0; i<targetDim; i++) {
            P2.setOrdinate(i, P2.getOrdinate(i) - P1.getOrdinate(i));
        }
        return P2;
    }

    /**
     * Transforms the relative distance vector specified by {@code source} and stores
     * the result in {@code dest}.  A relative distance vector is transformed without
     * applying the translation components.
     *
     * @param transform The transform to apply.
     * @param origin The position where to compute the delta transform in the source CS.
     * @param source The distance vector to be delta transformed
     * @param dest   The resulting transformed distance vector, or {@code null}
     * @return       The result of the transformation.
     * @throws TransformException if the transformation failed.
     *
     * @see AffineTransform#deltaTransform(Point2D,Point2D)
     */
    public static Point2D deltaTransform(final MathTransform2D transform,
                                         final Point2D         origin,
                                         final Point2D         source,
                                               Point2D         dest)
            throws TransformException
    {
        if (transform instanceof AffineTransform) {
            return ((AffineTransform) transform).deltaTransform(source, dest);
        }
        final double ox = origin.getX();
        final double oy = origin.getY();
        final double dx = source.getX()*0.5;
        final double dy = source.getY()*0.5;
        Point2D P1 = new Point2D.Double(ox-dx, oy-dy);
        Point2D P2 = new Point2D.Double(ox+dx, oy+dy);
        P1 = transform.transform(P1, P1);
        P2 = transform.transform(P2, P2);
        if (dest == null) {
            dest = P2;
        }
        dest.setLocation(P2.getX()-P1.getX(), P2.getY()-P1.getY());
        return dest;
    }

    /**
     * Returns a character string for the specified geographic area. The string will have the
     * form "45°00.00'N-50°00.00'N 30°00.00'E-40°00.00'E". If a map projection is required in
     * order to obtain this representation, it will be automatically applied.  This string is
     * mostly used for debugging purpose.
     *
     * @param  crs The coordinate reference system of the bounding box.
     * @param  bounds The bounding box to format.
     * @return The bounding box formatted as a string.
     *
     * @todo Move this method as a static method in {@link org.geotools.referencing.CRS}.
     *       Or yet better: move formatting code in {@code GeographicBoundingBox.toString()}
     *       method, and move the transformation code into {@code GeographicBoundingBox}
     *       constructor.
     *
     * @todo Do not requires specifically WGS 84, using {@link #getStandardGeographicCRS}.
     */
    public static String toWGS84String(CoordinateReferenceSystem crs, Rectangle2D bounds) {
        Exception exception;
        final StringBuffer buffer = new StringBuffer();
        final CoordinateReferenceSystem crs2D = CRS.getHorizontalCRS(crs);
        if (crs2D == null) {
            exception = new UnsupportedOperationException(Errors.format(
                    ErrorKeys.CANT_SEPARATE_CRS_$1, crs.getName()));
        } else try {
            if (!CRS.equalsIgnoreMetadata(DefaultGeographicCRS.WGS84, crs2D)) {
                final CoordinateOperation op = ReferencingFactoryFinder.getCoordinateOperationFactory(null)
                        .createOperation(crs2D, DefaultGeographicCRS.WGS84);
                bounds = CRS.transform(op, bounds, null);
            }
            final AngleFormat fmt = new AngleFormat("DD°MM.m'");
            fmt.format(new  Latitude(bounds.getMinY()), buffer, null).append('-');
            fmt.format(new  Latitude(bounds.getMaxY()), buffer, null).append(' ');
            fmt.format(new Longitude(bounds.getMinX()), buffer, null).append('-');
            fmt.format(new Longitude(bounds.getMaxX()), buffer, null);
            return buffer.toString();
        } catch (TransformException e) {
            exception = e;
        } catch (FactoryException e) {
            exception = e;
        }
        buffer.append(Classes.getShortClassName(exception));
        final String message = exception.getLocalizedMessage();
        if (message != null) {
            buffer.append(": ").append(message);
        }
        return buffer.toString();
    }
}
TOP

Related Classes of org.geotools.resources.CRSUtilities

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.