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

org.geotoolkit.referencing.cs.AbstractCS 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) 2004-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.cs;

import java.util.Map;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.measure.unit.NonSI;
import javax.measure.converter.UnitConverter;
import javax.measure.converter.LinearConverter;
import javax.measure.converter.ConversionException;
import javax.xml.bind.annotation.XmlElement;
import net.jcip.annotations.Immutable;

import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.Matrix;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.util.InternationalString;

import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.util.converter.Classes;
import org.geotoolkit.measure.Measure;
import org.geotoolkit.measure.Units;
import org.geotoolkit.referencing.AbstractIdentifiedObject;
import org.geotoolkit.referencing.operation.matrix.GeneralMatrix;
import org.geotoolkit.internal.referencing.AxisDirections;
import org.geotoolkit.io.wkt.Formatter;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.resources.Vocabulary;

import static org.geotoolkit.util.Utilities.hash;
import static org.geotoolkit.util.Utilities.deepEquals;
import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;


/**
 * The set of coordinate system axes that spans a given coordinate space. A coordinate system (CS)
 * is derived from a set of (mathematical) rules for specifying how coordinates in a given space
 * are to be assigned to points. The coordinate values in a coordinate tuple shall be recorded in
 * the order in which the coordinate system axes are recorded, whenever those
 * coordinates use a coordinate reference system that uses this coordinate system.
 * 

* This class is conceptually abstract, even if it is technically possible to * instantiate it. Typical applications should create instances of the most specific subclass with * {@code Default} prefix instead. An exception to this rule may occurs when it is not possible to * identify the exact type. For example it is not possible to infer the exact coordinate system from * Well * Known Text is some cases (e.g. in a {@code LOCAL_CS} element). In such exceptional * situation, a plain {@code AbstractCS} object may be instantiated. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.19 * * @see DefaultCoordinateSystemAxis * @see javax.measure.unit.Unit * @see org.geotoolkit.referencing.datum.AbstractDatum * @see org.geotoolkit.referencing.crs.AbstractCRS * * @since 2.0 * @module */ @Immutable public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSystem { /** * Serial number for inter-operability with different versions. */ private static final long serialVersionUID = 6757665252533744744L; /** * Base axis to use for checking directions. This is used in order to trap * inconsistency like an axis named "Northing" with South direction. */ private static final DefaultCoordinateSystemAxis[] DIRECTION_CHECKS = { DefaultCoordinateSystemAxis.NORTHING, DefaultCoordinateSystemAxis.EASTING, DefaultCoordinateSystemAxis.SOUTHING, DefaultCoordinateSystemAxis.WESTING }; /** * The axis for this coordinate system at the specified dimension. */ @XmlElement private final CoordinateSystemAxis[] axis; /** * The unit for measuring distance in this coordinate system, or {@code null} if none. * Will be computed only when first needed. */ private transient volatile Unit distanceUnit; /** * Constructs a new object in which every attributes are set to a default value. * This is not a valid object. This constructor is strictly * reserved to JAXB, which will assign values to the fields using reflexion. */ private AbstractCS() { this(org.geotoolkit.internal.referencing.NilReferencingObject.INSTANCE); } /** * Constructs a new coordinate system with the same values than the specified one. * This copy constructor provides a way to convert an arbitrary implementation into a * Geotk one or a user-defined one (as a subclass), usually in order to leverage * some implementation-specific API. This constructor performs a shallow copy, * i.e. the properties are not cloned. * * @param cs The coordinate system to copy. * * @since 2.2 */ public AbstractCS(final CoordinateSystem cs) { super(cs); if (cs instanceof AbstractCS) { axis = ((AbstractCS) cs).axis; } else { axis = new CoordinateSystemAxis[cs.getDimension()]; for (int i=0; i properties, final CoordinateSystemAxis... axis) { super(properties); ensureNonNull("axis", axis); this.axis = axis.clone(); for (int i=0; i unit = axis[i].getUnit(); ensureNonNull("unit", unit); if (!isCompatibleUnit(direction, unit)) { throw new IllegalArgumentException(Errors.format( Errors.Keys.INCOMPATIBLE_UNIT_$1, unit)); } /* * Ensures there is no axis along the same direction * (e.g. two North axis, or an East and a West axis). */ final AxisDirection check = AxisDirections.absolute(direction); if (!check.equals(AxisDirection.OTHER)) { for (int j=i; --j>=0;) { if (check.equals(AxisDirections.absolute(axis[j].getDirection()))) { // TODO: localize name() final String nameI = axis[i].getDirection().name(); final String nameJ = axis[j].getDirection().name(); throw new IllegalArgumentException(Errors.format( Errors.Keys.COLINEAR_AXIS_$2, nameI, nameJ)); } } } /* * Checks for some inconsistency in naming and direction. For example if the axis * is named "Northing", then the direction must be North. Exceptions to this rule * are the directions along a meridian from a pole. For example a "Northing" axis * may have a "South along 180 deg" direction. */ final String name = axis[i].getName().getCode(); for (int j=0; j name(final int key) { final Map properties = new HashMap(4); final InternationalString name = Vocabulary.formatInternational(key); properties.put(NAME_KEY, name.toString()); properties.put(ALIAS_KEY, name); return properties; } /** * Returns {@code true} if the specified axis direction is allowed for this coordinate * system. This method is invoked at construction time for checking argument validity. * The default implementation returns {@code true} for all axis directions. Subclasses * will overrides this method in order to put more restrictions on allowed axis directions. * * @param direction The direction to test for compatibility. * @return {@code true} if the given direction is compatible with this coordinate system. */ protected boolean isCompatibleDirection(final AxisDirection direction) { return true; } /** * Returns {@code true} is the specified unit is legal for the specified axis direction. * This method is invoked at construction time for checking units compatibility. The default * implementation returns {@code true} in all cases. Subclasses can override this method and * check for compatibility with {@linkplain SI#METRE metre} or * {@linkplain NonSI#DEGREE_ANGLE degree} units. * * @param direction The direction of the axis having the given unit. * @param unit The unit to test for compatibility. * @return {@code true} if the given unit is compatible with this coordinate system. * * @since 2.2 */ protected boolean isCompatibleUnit(final AxisDirection direction, final Unit unit) { return true; } /** * Returns the dimension of the coordinate system. * This is the number of axis. */ @Override public int getDimension() { return axis.length; } /** * Returns the axis for this coordinate system at the specified dimension. * * @param dimension The zero based index of axis. * @return The axis at the specified dimension. * @throws IndexOutOfBoundsException if {@code dimension} is out of bounds. */ @Override public CoordinateSystemAxis getAxis(final int dimension) throws IndexOutOfBoundsException { return axis[dimension]; } /** * Returns the axis direction for the specified coordinate system. * * @param cs The coordinate system. * @return The axis directions for the specified coordinate system. */ private static AxisDirection[] getAxisDirections(final CoordinateSystem cs) { final AxisDirection[] axis = new AxisDirection[cs.getDimension()]; for (int i=0; i * Example: If coordinates in {@code sourceCS} are (x,y) pairs * in metres and coordinates in {@code targetCS} are (-y,x) pairs in * centimetres, then the transformation can be performed as below: * * {@preformat text * ┌ ┐ ┌ ┐ ┌ ┐ * │-y(cm)│ │ 0 -100 0 │ │ x(m)│ * │ x(cm)│ = │ 100 0 0 │ │ y(m)│ * │ 1 │ │ 0 0 1 │ │ 1 │ * └ ┘ └ ┘ └ ┘ * } * * @param sourceCS The source coordinate system. * @param targetCS The target coordinate system. * @return The conversion from {@code sourceCS} to {@code targetCS} as * an affine transform. Only axis direction and units are taken in account. * @throws IllegalArgumentException if axis doesn't matches, or the CS doesn't have the * same geometry. * @throws ConversionException if the units are not compatible, or the conversion is non-linear. */ public static Matrix swapAndScaleAxis(final CoordinateSystem sourceCS, final CoordinateSystem targetCS) throws IllegalArgumentException, ConversionException { if (!Classes.implementSameInterfaces(sourceCS.getClass(), targetCS.getClass(), CoordinateSystem.class)) { throw new IllegalArgumentException(Errors.format( Errors.Keys.INCOMPATIBLE_COORDINATE_SYSTEM_TYPE)); } final AxisDirection[] sourceAxis = getAxisDirections(sourceCS); final AxisDirection[] targetAxis = getAxisDirections(targetCS); final GeneralMatrix matrix = new GeneralMatrix(sourceAxis, targetAxis); assert Arrays.equals(sourceAxis, targetAxis) == matrix.isIdentity() : matrix; /* * The previous code computed a matrix for swapping axis. Usually, this * matrix contains only 0 and 1 values with only one "1" value by row. * For example, the matrix operation for swapping x and y axis is: * ┌ ┐ ┌ ┐ ┌ ┐ * │y│ │ 0 1 0 │ │x│ * │x│ = │ 1 0 0 │ │y│ * │1│ │ 0 0 1 │ │1│ * └ ┘ └ ┘ └ ┘ * Now, take in account units conversions. Each matrix's element (j,i) * is multiplied by the conversion factor from sourceCS.getUnit(i) to * targetCS.getUnit(j). This is an element-by-element multiplication, * not a matrix multiplication. The last column is processed in a special * way, since it contains the offset values. */ final int sourceDim = matrix.getNumCol()-1; final int targetDim = matrix.getNumRow()-1; assert sourceDim == sourceCS.getDimension() : sourceCS; assert targetDim == targetCS.getDimension() : targetCS; for (int j=0; j targetUnit = targetCS.getAxis(j).getUnit(); for (int i=0; i sourceUnit = sourceCS.getAxis(i).getUnit(); if (Utilities.equals(sourceUnit, targetUnit)) { // There is no units conversion to apply // between source[i] and target[j]. continue; } final UnitConverter converter = sourceUnit.getConverterToAny(targetUnit); if (!(converter instanceof LinearConverter)) { throw new ConversionException(Errors.format( Errors.Keys.NON_LINEAR_UNIT_CONVERSION_$2, sourceUnit, targetUnit)); } final double offset = converter.convert(0); final double scale = Units.derivative(converter, 0); matrix.setElement(j,i, element*scale); matrix.setElement(j,sourceDim, matrix.getElement(j,sourceDim) + element*offset); } } return matrix; } /** * Returns a coordinate system with "standard" axis order and units. * Most of the time, this method returns one of the predefined constants with axis in * (longitude,latitude) or (X,Y) order, * and units in degrees or metres. In some particular cases like * {@linkplain org.opengis.referencing.cs.CartesianCS Cartesian CS}, this method may * create a new instance on the fly. In every cases this method attempts to return a * right-handed coordinate * system, but this is not guaranteed. *

* This method is typically used together with {@link #swapAndScaleAxis swapAndScaleAxis} * for the creation of a transformation step before some * {@linkplain org.opengis.referencing.operation.MathTransform math transform}. * Example: * * {@preformat java * Matrix step1 = swapAndScaleAxis(sourceCS, standard(sourceCS)); * Matrix step2 = ... some transform operating on standard axis ... * Matrix step3 = swapAndScaleAxis(standard(targetCS), targetCS); * } * * A rational for standard axis order and units is explained in the Axis units and * direction section in the {@linkplain org.geotoolkit.referencing.operation.projection * description of map projection package}. * * @param cs The coordinate system. * @return A constant similar to the specified {@code cs} with "standard" axis. * @throws IllegalArgumentException if the specified coordinate system is unknown to this method. * * @since 2.2 */ public static CoordinateSystem standard(final CoordinateSystem cs) throws IllegalArgumentException { return PredefinedCS.standard(cs); } /** * Suggests an unit for measuring distances in this coordinate system. The default * implementation scans all {@linkplain CoordinateSystemAxis#getUnit axis units}, * ignoring angular ones (this also implies ignoring {@linkplain Unit#ONE dimensionless} ones). * If more than one non-angular unit is found, the default implementation returns the "largest" * one (e.g. kilometre instead of metre). * * @return Suggested distance unit. */ final Unit getDistanceUnit() { Unit unit = distanceUnit; // Avoid the need for synchronization. if (unit == null) { double maxScale = 0; for (int i=0; i candidate = axis[i].getUnit(); if (candidate != null && !Units.isAngular(candidate)) { // TODO: checks the unit scale type (keeps RATIO only). final double scale = Math.abs(Units.toStandardUnit(candidate)); if (unit == null || scale > maxScale) { unit = candidate; maxScale = scale; } } } distanceUnit = unit; } return unit; } /** * Convenience method for checking object dimension validity. * * @param name The name of the argument to check. * @param coordinates The coordinate array to check. * @throws MismatchedDimensionException if the coordinate doesn't have the expected dimension. */ final void ensureDimensionMatch(final String name, final double[] coordinates) throws MismatchedDimensionException { if (coordinates.length != axis.length) { throw new MismatchedDimensionException(Errors.format( Errors.Keys.MISMATCHED_DIMENSION_$3, name, coordinates.length, axis.length)); } } /** * Computes the distance between two points. This method is not available for all coordinate * systems. For example, {@linkplain DefaultEllipsoidalCS ellipsoidal CS} doesn't have * sufficient information. * * @param coord1 Coordinates of the first point. * @param coord2 Coordinates of the second point. * @return The distance between {@code coord1} and {@code coord2}. * @throws UnsupportedOperationException if this coordinate system can't compute distances. * @throws MismatchedDimensionException if a coordinate doesn't have the expected dimension. * * @todo Provides a localized message in the exception. */ public Measure distance(final double[] coord1, final double[] coord2) throws UnsupportedOperationException, MismatchedDimensionException { throw new UnsupportedOperationException(); } /** * Returns all axis in the specified unit. This method is used for implementation of * {@code usingUnit} methods in subclasses. * * @param unit The unit for the new axis. * @return New axis using the specified unit, or {@code null} if current axis fits. * @throws IllegalArgumentException If the specified unit is incompatible with the expected one. * * @see DefaultCartesianCS#usingUnit * @see DefaultEllipsoidalCS#usingUnit */ final CoordinateSystemAxis[] axisUsingUnit(final Unit unit) throws IllegalArgumentException { CoordinateSystemAxis[] newAxis = null; for (int i=0; i * If this method returns {@code true}, then there is good chances that this CS can be used * together with {@code userCS} as arguments to {@link #swapAndScaleAxis swapAndScaleAxis}. *

* This method should not be public because current implementation is not fully consistent * for every pair of CS. It tries to check the opposite direction in addition of the usual * one, but only a few pre-defined axis declare their opposite. This method should be okay * when invoked on pre-defined CS declared in this package. {@link PredefinedCS} uses this * method only that way. */ final boolean axisColinearWith(final CoordinateSystem userCS) { if (userCS.getDimension() != getDimension()) { return false; } final DefaultCoordinateSystemAxis[] axis0 = getDefaultAxis(this); final DefaultCoordinateSystemAxis[] axis1 = getDefaultAxis(userCS); next: for (int i=0; iWell * Known Text (WKT) element. Note that WKT is not yet defined for coordinate system. * Current implementation list the axis contained in this CS. * * @param formatter The formatter to use. * @return The WKT element name. Current implementation default to the class name. */ @Override public String formatWKT(final Formatter formatter) { for (int i=0; i





© 2015 - 2024 Weber Informatics LLC | Privacy Policy