/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, 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.
*/
package org.geotoolkit.referencing.cs;
import java.util.HashMap;
import java.util.Map;
import javax.measure.Unit;
import javax.measure.UnitConverter;
import javax.measure.IncommensurableException;
import org.opengis.referencing.cs.*;
import org.opengis.util.InternationalString;
import org.opengis.geometry.MismatchedDimensionException;
import org.geotoolkit.lang.Static;
import org.geotoolkit.measure.Measure;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.resources.Vocabulary;
import org.apache.sis.measure.Units;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.referencing.cs.DefaultCartesianCS;
import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
import static org.opengis.referencing.IdentifiedObject.ALIAS_KEY;
import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
/**
* Predefined coordinate systems.
* <strong>Warning:</strong> this is a temporary class which may disappear in future Geotk version,
* after we migrated functionality to Apache SIS.
*
* @author Martin Desruisseaux (IRD)
* @module
*/
public final class PredefinedCS extends Static {
/**
* A two-dimensional Cartesian CS with
* <var>{@linkplain Axes#EASTING Easting}</var>,
* <var>{@linkplain Axes#NORTHING Northing}</var>
* axis in metres.
*/
public static final DefaultCartesianCS PROJECTED = new DefaultCartesianCS(
name(Vocabulary.Keys.Projected),
Axes.EASTING,
Axes.NORTHING);
/**
* A two-dimensional Cartesian CS with
* <var>{@linkplain Axes#X x}</var>,
* <var>{@linkplain Axes#Y y}</var>
* axis in metres.
*/
public static final DefaultCartesianCS CARTESIAN_2D = new DefaultCartesianCS(
name(Vocabulary.Keys.Cartesian2d),
Axes.X,
Axes.Y);
/**
* A three-dimensional Cartesian CS with
* <var>{@linkplain Axes#X x}</var>,
* <var>{@linkplain Axes#Y y}</var>,
* <var>{@linkplain Axes#Z z}</var>
* axis in metres.
*/
public static final DefaultCartesianCS CARTESIAN_3D = new DefaultCartesianCS(
name(Vocabulary.Keys.Cartesian3d),
Axes.X,
Axes.Y,
Axes.Z);
/**
* A two-dimensional Cartesian CS with
* <var>{@linkplain Axes#COLUMN column}</var>,
* <var>{@linkplain Axes#ROW row}</var>
* axis.
*/
public static final DefaultCartesianCS GRID = new DefaultCartesianCS(
name(Vocabulary.Keys.Grid),
Axes.COLUMN,
Axes.ROW);
/**
* Creates a comparator.
*/
private PredefinedCS() {
}
/**
* Creates a name for the predefined constants in subclasses. The name is an unlocalized String
* object. However, since this method is used for creation of convenience objects only (not for
* objects created from an "official" database), the "unlocalized" name is actually chosen
* according the user's locale at class initialization time. The same name is also added in
* a localizable form as an alias. Since the {@link #nameMatches} convenience method checks
* the alias, it still possible to consider two objects are equivalent even if their names
* were formatted in different locales.
*/
private static Map<String,Object> name(final int key) {
final Map<String,Object> properties = new HashMap<>(4);
final InternationalString name = Vocabulary.formatInternational(key);
properties.put(NAME_KEY, name.toString());
properties.put(ALIAS_KEY, name);
return properties;
}
/**
* 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.
*/
private static void ensureDimensionMatch(final CoordinateSystem cs, final String name, final double[] coordinates)
throws MismatchedDimensionException
{
final int dimension = cs.getDimension();
if (coordinates.length != dimension) {
throw new MismatchedDimensionException(Errors.format(
Errors.Keys.MismatchedDimension_3,
name, coordinates.length, dimension));
}
}
private static DefaultCartesianCS createCartesian(final Map<String,?> properties, final CoordinateSystemAxis[] axis) {
switch (axis.length) {
case 2: return new DefaultCartesianCS(properties, axis[0], axis[1]);
case 3: return new DefaultCartesianCS(properties, axis[0], axis[1], axis[2]);
default: throw new IllegalArgumentException();
}
}
private static DefaultEllipsoidalCS createEllipsoidal(final Map<String,?> properties, final CoordinateSystemAxis[] axis) {
switch (axis.length) {
case 2: return new DefaultEllipsoidalCS(properties, axis[0], axis[1]);
case 3: return new DefaultEllipsoidalCS(properties, axis[0], axis[1], axis[2]);
default: throw new IllegalArgumentException();
}
}
/**
* Computes the distance between two points. This method is not available for all coordinate systems.
* For example, the {@linkplain DefaultEllipsoidalCS ellipsoidal CS} doesn't have sufficient information.
* <p>
* In the default Geotk implementation, this method is supported by the following class:
* <p>
* <ul>
* <li>All one-dimensional coordinate systems ({@link DefaultLinearCS},
* {@link DefaultVerticalCS}, {@link DefaultTimeCS}), in which case this method
* returns the absolute difference between the given ordinate values.</li>
* <li>{@link DefaultCartesianCS#distance(double[], double[])}.</li>
* </ul>
*
* @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.
*/
public static Measure distance(final CoordinateSystem cs, final double[] coord1, final double[] coord2)
throws UnsupportedOperationException, MismatchedDimensionException
{
if (cs instanceof CartesianCS) {
// Temporary patch replacing method overriding until we completed the migration to Apache SIS.
return distance((CartesianCS) cs, coord1, coord2);
}
ensureDimensionMatch(cs, "coord1", coord1);
ensureDimensionMatch(cs, "coord2", coord2);
if (cs.getDimension() == 1) {
return new Measure(Math.abs(coord1[0] - coord2[0]), cs.getAxis(0).getUnit());
}
throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnsupportedCoordinateSystem_1, cs.getClass()));
}
/**
* Computes the distance between two points.
*
* @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.
*/
private static Measure distance(final CartesianCS cs, final double[] coord1, final double[] coord2)
throws UnsupportedOperationException, MismatchedDimensionException
{
ensureDimensionMatch(cs, "coord1", coord1);
ensureDimensionMatch(cs, "coord2", coord2);
final Unit<?> unit = getDistanceUnit(cs);
final UnitConverter[] converters = new UnitConverter[cs.getDimension()];
for (int i=0; i<converters.length; i++) {
final Unit<?> axisUnit = cs.getAxis(i).getUnit();
try {
converters[i] = axisUnit.getConverterToAny(unit);
} catch (IncommensurableException e) {
throw new UnsupportedOperationException(Errors.format(
Errors.Keys.IncompatibleUnit_1, axisUnit), e);
}
}
final double[] delta = new double[converters.length];
for (int i=0; i<converters.length; i++) {
final UnitConverter c = converters[i];
delta[i] = c.convert(coord1[i]) - c.convert(coord2[i]);
}
return new Measure(MathFunctions.magnitude(delta), unit);
}
/**
* Suggests an unit for measuring distances in this coordinate system. The default
* implementation scans all {@linkplain CoordinateSystemAxis#getUnit() axis units},
* then returns the "largest" one (e.g. kilometre instead of metre).
*
* @return Suggested distance unit.
*/
private static Unit<?> getDistanceUnit(final CoordinateSystem cs) {
Unit<?> unit = null;
double maxScale = 0;
for (int i=cs.getDimension(); --i>=0;) {
final Unit<?> candidate = cs.getAxis(i).getUnit();
if (candidate != null) {
final double scale = Math.abs(Units.toStandardUnit(candidate));
if (scale > maxScale) {
unit = candidate;
maxScale = scale;
}
}
}
return unit;
}
/**
* Returns all axis in the specified unit. This method was used for implementation of
* {@code usingUnit} methods in CS subclasses.
*
* @param unit The unit for the new axes.
* @param ignore {@link Units#METRE} for ignoring linear units, {@link Units#RADIAN} for ignoring
* angular units, or {@code null} for none.
* @return New axes using the specified unit, or {@code null} if no change is needed.
* @throws IncommensurableException If the specified unit is incompatible with the expected one.
*
* @see DefaultCartesianCS#usingUnit(Unit)
* @see DefaultEllipsoidalCS#usingUnit(Unit)
*/
private static CoordinateSystemAxis[] axisUsingUnit(final CoordinateSystem cs, final Unit<?> unit, final Unit<?> ignore) throws IncommensurableException {
final int dimension = cs.getDimension();
CoordinateSystemAxis[] newAxis = null;
for (int i=0; i<dimension; i++) {
final CoordinateSystemAxis a = cs.getAxis(i);
final Unit<?> current = a.getUnit();
if (!unit.equals(current) && (ignore == null || !ignore.equals(unit.getSystemUnit()))) {
final CoordinateSystemAxis converted = Axes.usingUnit(a, unit);
if (converted != a) {
if (newAxis == null) {
newAxis = new CoordinateSystemAxis[dimension];
for (int j=0; j<i; j++) {
newAxis[j] = cs.getAxis(j);
}
}
newAxis[i] = converted;
}
}
}
return newAxis;
}
/**
* Returns a coordinate system with the same properties than the given one except for axis units.
*
* @param unit The unit for the new axis.
* @return A coordinate system with axis using the specified units.
* @throws IllegalArgumentException If the specified unit is incompatible with the expected one.
*/
public static CartesianCS usingUnit(final CartesianCS cs, final Unit<?> unit) throws IllegalArgumentException {
final CoordinateSystemAxis[] axes;
try {
axes = axisUsingUnit(cs, unit, null);
} catch (IncommensurableException e) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnit_1, unit), e);
}
if (axes == null) {
return cs;
}
return createCartesian(org.geotoolkit.referencing.IdentifiedObjects.getProperties(cs, null), axes);
}
/**
* Returns a coordinate system with the same properties than the current one except for
* axis units. If this coordinate system already uses the given unit, then this method
* returns {@code this}.
*
* @param unit The unit for the new axis.
* @return A coordinate system with axis using the specified units, or {@code this}.
* @throws IllegalArgumentException If the specified unit is incompatible with the expected one.
*/
public static EllipsoidalCS usingUnit(final EllipsoidalCS cs, final Unit<?> unit) throws IllegalArgumentException {
final CoordinateSystemAxis[] axes;
try {
axes = PredefinedCS.axisUsingUnit(cs, unit, Units.isLinear(unit) ? Units.RADIAN : Units.METRE);
} catch (IncommensurableException e) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnit_1, unit), e);
}
if (axes == null) {
return cs;
}
return createEllipsoidal(org.geotoolkit.referencing.IdentifiedObjects.getProperties(cs, null), axes);
}
}