sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1651201 [2/5] - in /sis/branches/JDK7: ./ application/sis-webapp/src/main/java/org/apache/sis/index/tree/ core/sis-build-helper/src/main/java/org/apache/sis/internal/doclet/ core/sis-build-helper/src/main/java/org/apache/sis/util/resources...
Date Mon, 12 Jan 2015 21:22:19 GMT
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -22,23 +22,72 @@ package org.apache.sis.geometry;
  * force installation of the Java2D module (e.g. JavaFX/SWT).
  */
 import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.cs.RangeMeaning;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.CoordinateOperationFactory;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.util.FactoryException;
 import org.apache.sis.util.Static;
+import org.apache.sis.util.Utilities;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
+import org.apache.sis.internal.referencing.DirectPositionView;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.system.DefaultFactories;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 import static org.apache.sis.util.StringBuilders.trimFractionalPart;
 
 
 /**
- * Utility methods for envelopes. This utility class is made up of static functions working
- * with arbitrary implementations of GeoAPI interfaces.
+ * Transforms envelopes to new Coordinate Reference Systems, and miscellaneous utilities.
+ *
+ * {@section Envelope transformations}
+ * All {@code transform(…)} methods in this class take in account the curvature of the transformed shape.
+ * For example the shape of a geographic envelope (figure below on the left side) is not rectangular in a
+ * conic projection (figure below on the right side). In order to get the envelope represented by the red
+ * rectangle, projecting the four corners of the geographic envelope is not sufficient since we would miss
+ * the southerner part.
+ *
+ * <center><table class="sis">
+ *   <caption>Example of curvature induced by a map projection</caption>
+ *   <tr>
+ *     <th>Envelope before map projection</th>
+ *     <th>Shape of the projected envelope</th>
+ *   </tr><tr>
+ *     <td><img src="doc-files/GeographicArea.png" alt="Envelope in a geographic CRS"></td>
+ *     <td><img src="doc-files/ConicArea.png" alt="Shape of the envelope transformed in a conic projection"></td>
+ *   </tr>
+ * </table></center>
+ *
+ * Apache SIS tries to detect the curvature by transforming intermediate points in addition to the corners.
+ * While optional, it is strongly recommended that all {@code MathTransform} implementations involved in the
+ * operation (directly or indirectly) support {@linkplain MathTransform#derivative(DirectPosition) derivative},
+ * for more accurate calculation of curve extremum. This is the case of most Apache SIS implementations.
+ *
+ * <p>The {@code transform(…)} methods in this class expect an arbitrary {@link Envelope} with <strong>one</strong>
+ * of the following arguments: {@link MathTransform}, {@link CoordinateOperation} or {@link CoordinateReferenceSystem}.
+ * The recommended method is the one expecting a {@code CoordinateOperation} object,
+ * since it contains sufficient information for handling the cases of envelopes that encompass a pole.
+ * The method expecting a {@code CoordinateReferenceSystem} object is merely a convenience method that
+ * infers the coordinate operation itself, but at the cost of performance if the same operation needs
+ * to be applied on many envelopes.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @since   0.3 (derived from geotk-2.4)
- * @version 0.3
+ * @version 0.5
  * @module
  *
  * @see org.apache.sis.metadata.iso.extent.Extents
@@ -46,22 +95,645 @@ import static org.apache.sis.util.String
  */
 public final class Envelopes extends Static {
     /**
-     * Enumeration of the 4 corners in an envelope, with repetition of the first point.
-     * The values are (x,y) pairs with {@code false} meaning "minimal value" and {@code true}
-     * meaning "maximal value". This is used by {@link #toPolygonWKT(Envelope)} only.
+     * Do not allow instantiation of this class.
      */
-    private static final boolean[] CORNERS = {
-        false, false,
-        false, true,
-        true,  true,
-        true,  false,
-        false, false
-    };
+    private Envelopes() {
+    }
 
     /**
-     * Do not allow instantiation of this class.
+     * Returns the coordinate operation factory to be used for transforming the envelope.
      */
-    private Envelopes() {
+    private static CoordinateOperationFactory getFactory() throws TransformException {
+        final CoordinateOperationFactory factory = DefaultFactories.forClass(CoordinateOperationFactory.class);
+        if (factory != null) {
+            return factory;
+        }
+        throw new TransformException(Errors.format(Errors.Keys.MissingRequiredModule_1, "geotk-referencing")); // This is temporary.
+    }
+
+    /**
+     * Returns {@code true} if the given axis is of kind "Wrap Around".
+     */
+    private static boolean isWrapAround(final CoordinateSystemAxis axis) {
+        return RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning());
+    }
+
+    /**
+     * Invoked when a recoverable exception occurred. Those exceptions must be minor enough
+     * that they can be silently ignored in most cases.
+     */
+    private static void recoverableException(final TransformException exception) {
+        Logging.recoverableException(Envelopes.class, "transform", exception);
+    }
+
+    /**
+     * A buckle method for calculating derivative and coordinate transformation in a single step,
+     * if the given {@code derivative} argument is {@code true}.
+     *
+     * @param transform The transform to use.
+     * @param srcPts    The array containing the source coordinate at offset 0.
+     * @param dstPts    the array into which the transformed coordinate is returned.
+     * @param dstOff    The offset to the location of the transformed point that is stored in the destination array.
+     * @param derivate  {@code true} for computing the derivative, or {@code false} if not needed.
+     * @return The matrix of the transform derivative at the given source position,
+     *         or {@code null} if the {@code derivate} argument is {@code false}.
+     * @throws TransformException If the point can not be transformed
+     *         or if a problem occurred while calculating the derivative.
+     */
+    private static Matrix derivativeAndTransform(final MathTransform transform, final double[] srcPts,
+            final double[] dstPts, final int dstOff, final boolean derivate) throws TransformException
+    {
+        if (transform instanceof AbstractMathTransform) {
+            return ((AbstractMathTransform) transform).transform(srcPts, 0, dstPts, dstOff, derivate);
+        }
+        // Derivative must be calculated before to transform the coordinate.
+        final Matrix derivative = derivate ? transform.derivative(new DirectPositionView(srcPts, 0, transform.getSourceDimensions())) : null;
+        transform.transform(srcPts, 0, dstPts, dstOff, 1);
+        return derivative;
+    }
+
+    /**
+     * Transforms the given envelope to the specified CRS. If any argument is null, or if the
+     * {@linkplain GeneralEnvelope#getCoordinateReferenceSystem() envelope CRS} is null or the
+     * same instance than the given target CRS, then the given envelope is returned unchanged.
+     * Otherwise a new transformed envelope is returned.
+     *
+     * {@section Performance tip}
+     * If there is many envelopes to transform with the same source and target CRS, then it is more efficient
+     * to get the {@link CoordinateOperation} or {@link MathTransform} instance once and invoke one of the
+     * others {@code transform(…)} methods.
+     *
+     * @param  envelope The envelope to transform (may be {@code null}).
+     * @param  targetCRS The target CRS (may be {@code null}).
+     * @return A new transformed envelope, or directly {@code envelope} if no change was required.
+     * @throws TransformException If a transformation was required and failed.
+     *
+     * @since 0.5
+     */
+    public static Envelope transform(Envelope envelope, final CoordinateReferenceSystem targetCRS)
+            throws TransformException
+    {
+        if (envelope != null && targetCRS != null) {
+            final CoordinateReferenceSystem sourceCRS = envelope.getCoordinateReferenceSystem();
+            if (sourceCRS != targetCRS) {
+                if (sourceCRS == null) {
+                    // Slight optimization: just copy the given Envelope.
+                    envelope = new GeneralEnvelope(envelope);
+                    ((GeneralEnvelope) envelope).setCoordinateReferenceSystem(targetCRS);
+                } else {
+                    final CoordinateOperation operation;
+                    try {
+                        operation = getFactory().createOperation(sourceCRS, targetCRS);
+                    } catch (FactoryException exception) {
+                        throw new TransformException(Errors.format(Errors.Keys.CanNotTransformEnvelope), exception);
+                    }
+                    envelope = transform(operation, envelope);
+                }
+                assert Utilities.deepEquals(targetCRS, envelope.getCoordinateReferenceSystem(), ComparisonMode.DEBUG);
+            }
+        }
+        return envelope;
+    }
+
+    /**
+     * Transforms an envelope using the given math transform.
+     * The transformation is only approximative: the returned envelope may be bigger than necessary,
+     * or smaller than required if the bounding box contains a pole.
+     *
+     * <p><b>Limitation</b><br>
+     * This method can not handle the case where the envelope contains the North or South pole,
+     * or when it crosses the ±180° longitude, because {@link MathTransform} does not carry sufficient information.
+     * For a more robust envelope transformation, use {@link #transform(CoordinateOperation, Envelope)} instead.</p>
+     *
+     * @param  transform The transform to use.
+     * @param  envelope Envelope to transform, or {@code null}. This envelope will not be modified.
+     * @return The transformed envelope, or {@code null} if {@code envelope} was null.
+     * @throws TransformException if a transform failed.
+     *
+     * @see #transform(CoordinateOperation, Envelope)
+     *
+     * @since 0.5
+     */
+    public static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope)
+            throws TransformException
+    {
+        ensureNonNull("transform", transform);
+        return (envelope != null) ? transform(transform, envelope, null) : null;
+    }
+
+    /**
+     * Implementation of {@link #transform(MathTransform, Envelope)} with the opportunity to
+     * save the projected center coordinate.
+     *
+     * @param targetPt After this method call, the center of the source envelope projected to
+     *        the target CRS. The length of this array must be the number of target dimensions.
+     *        May be {@code null} if this information is not needed.
+     */
+    @SuppressWarnings("null")
+    private static GeneralEnvelope transform(final MathTransform transform,
+                                             final Envelope      envelope,
+                                             final double[]      targetPt)
+            throws TransformException
+    {
+        if (transform.isIdentity()) {
+            /*
+             * Slight optimization: Just copy the envelope. Note that we need to set the CRS
+             * to null because we don't know what the target CRS was supposed to be. Even if
+             * an identity transform often imply that the target CRS is the same one than the
+             * source CRS, it is not always the case. The metadata may be differents, or the
+             * transform may be a datum shift without Bursa-Wolf parameters, etc.
+             */
+            final GeneralEnvelope transformed = new GeneralEnvelope(envelope);
+            transformed.setCoordinateReferenceSystem(null);
+            if (targetPt != null) {
+                for (int i=envelope.getDimension(); --i>=0;) {
+                    targetPt[i] = transformed.getMedian(i);
+                }
+            }
+            return transformed;
+        }
+        /*
+         * Checks argument validity: envelope and math transform dimensions must be consistent.
+         */
+        final int sourceDim = transform.getSourceDimensions();
+        final int targetDim = transform.getTargetDimensions();
+        if (envelope.getDimension() != sourceDim) {
+            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_2,
+                      sourceDim, envelope.getDimension()));
+        }
+        /*
+         * Allocates all needed objects. The value '3' below is because the following 'while'
+         * loop uses a 'pointIndex' to be interpreted as a number in base 3 (see the comment
+         * inside the loop).  The coordinate to transform must be initialized to the minimal
+         * ordinate values. This coordinate will be updated in the 'switch' statement inside
+         * the 'while' loop.
+         */
+        if (sourceDim >= 20) { // Maximal value supported by Formulas.pow3(int) is 19.
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1));
+        }
+        int             pointIndex            = 0;
+        boolean         isDerivativeSupported = true;
+        GeneralEnvelope transformed           = null;
+        final Matrix[]  derivatives           = new Matrix[Formulas.pow3(sourceDim)];
+        final double[]  ordinates             = new double[derivatives.length * targetDim];
+        final double[]  sourcePt              = new double[sourceDim];
+        for (int i=sourceDim; --i>=0;) {
+            sourcePt[i] = envelope.getMinimum(i);
+        }
+        // A window over a single coordinate in the 'ordinates' array.
+        final DirectPositionView ordinatesView = new DirectPositionView(ordinates, 0, targetDim);
+        /*
+         * Iterates over every minimal, maximal and median ordinate values (3 points) along each
+         * dimension. The total number of iterations is 3 ^ (number of source dimensions).
+         */
+        transformPoint: while (true) {
+            /*
+             * Compute the derivative (optional operation). If this operation fails, we will
+             * set a flag to 'false' so we don't try again for all remaining points. We try
+             * to compute the derivative and the transformed point in a single operation if
+             * we can. If we can not, we will compute those two information separately.
+             *
+             * Note that the very last point to be projected must be the envelope center.
+             * There is usually no need to calculate the derivative for that last point,
+             * but we let it does anyway for safety.
+             */
+            final int offset = pointIndex * targetDim;
+            try {
+                derivatives[pointIndex] = derivativeAndTransform(transform,
+                        sourcePt, ordinates, offset, isDerivativeSupported);
+            } catch (TransformException e) {
+                if (!isDerivativeSupported) {
+                    throw e; // Derivative were already disabled, so something went wrong.
+                }
+                isDerivativeSupported = false;
+                transform.transform(sourcePt, 0, ordinates, offset, 1);
+                recoverableException(e); // Log only if the above call was successful.
+            }
+            /*
+             * The transformed point has been saved for future reuse after the enclosing
+             * 'while' loop. Now add the transformed point to the destination envelope.
+             */
+            if (transformed == null) {
+                transformed = new GeneralEnvelope(targetDim);
+                for (int i=0; i<targetDim; i++) {
+                    final double value = ordinates[offset + i];
+                    transformed.setRange(i, value, value);
+                }
+            } else {
+                ordinatesView.offset = offset;
+                transformed.add(ordinatesView);
+            }
+            /*
+             * Get the next point coordinate. The 'coordinateIndex' variable is an index in base 3
+             * having a number of digits equals to the number of source dimensions.  For example a
+             * 4-D space have indexes ranging from "0000" to "2222" (numbers in base 3). The digits
+             * are then mapped to minimal (0), maximal (1) or central (2) ordinates. The outer loop
+             * stops when the counter roll back to "0000". Note that 'targetPt' must keep the value
+             * of the last projected point, which must be the envelope center identified by "2222"
+             * in the 4-D case.
+             */
+            int indexBase3 = ++pointIndex;
+            for (int dim=sourceDim; --dim>=0; indexBase3 /= 3) {
+                switch (indexBase3 % 3) {
+                    case 0:  sourcePt[dim] = envelope.getMinimum(dim); break; // Continue the loop.
+                    case 1:  sourcePt[dim] = envelope.getMaximum(dim); continue transformPoint;
+                    case 2:  sourcePt[dim] = envelope.getMedian (dim); continue transformPoint;
+                    default: throw new AssertionError(indexBase3); // Should never happen
+                }
+            }
+            break;
+        }
+        assert pointIndex == derivatives.length : pointIndex;
+        /*
+         * At this point we finished to build an envelope from all sampled positions. Now iterate
+         * over all points. For each point, iterate over all line segments from that point to a
+         * neighbor median point.  Use the derivate information for approximating the transform
+         * behavior in that area by a cubic curve. We can then find analytically the curve extremum.
+         *
+         * The same technic is applied in transform(MathTransform, Rectangle2D), except that in
+         * the Rectangle2D case the calculation was bundled right inside the main loop in order
+         * to avoid the need for storage.
+         */
+        DirectPosition temporary = null;
+        final DirectPositionView sourceView = new DirectPositionView(sourcePt, 0, sourceDim);
+        final CurveExtremum extremum = new CurveExtremum();
+        for (pointIndex=0; pointIndex < derivatives.length; pointIndex++) {
+            final Matrix D1 = derivatives[pointIndex];
+            if (D1 != null) {
+                int indexBase3 = pointIndex, power3 = 1;
+                for (int i=sourceDim; --i>=0; indexBase3 /= 3, power3 *= 3) {
+                    final int digitBase3 = indexBase3 % 3;
+                    if (digitBase3 != 2) { // Process only if we are not already located on the median along the dimension i.
+                        final int medianIndex = pointIndex + power3 * (2 - digitBase3);
+                        final Matrix D2 = derivatives[medianIndex];
+                        if (D2 != null) {
+                            final double xmin = envelope.getMinimum(i);
+                            final double xmax = envelope.getMaximum(i);
+                            final double x2   = envelope.getMedian (i);
+                            final double x1   = (digitBase3 == 0) ? xmin : xmax;
+                            final int offset1 = targetDim * pointIndex;
+                            final int offset2 = targetDim * medianIndex;
+                            for (int j=0; j<targetDim; j++) {
+                                extremum.resolve(x1, ordinates[offset1 + j], D1.getElement(j,i),
+                                                 x2, ordinates[offset2 + j], D2.getElement(j,i));
+                                boolean isP2 = false;
+                                do { // Executed exactly twice, one for each extremum point.
+                                    final double x = isP2 ? extremum.ex2 : extremum.ex1;
+                                    if (x > xmin && x < xmax) {
+                                        final double y = isP2 ? extremum.ey2 : extremum.ey1;
+                                        if (y < transformed.getMinimum(j) ||
+                                            y > transformed.getMaximum(j))
+                                        {
+                                            /*
+                                             * At this point, we have determined that adding the extremum point
+                                             * would expand the envelope. However we will not add that point
+                                             * directly because its position may not be quite right (since we
+                                             * used a cubic curve approximation). Instead, we project the point
+                                             * on the envelope border which is located vis-à-vis the extremum.
+                                             */
+                                            for (int ib3 = pointIndex, dim = sourceDim; --dim >= 0; ib3 /= 3) {
+                                                final double ordinate;
+                                                if (dim == i) {
+                                                    ordinate = x; // Position of the extremum.
+                                                } else switch (ib3 % 3) {
+                                                    case 0:  ordinate = envelope.getMinimum(dim); break;
+                                                    case 1:  ordinate = envelope.getMaximum(dim); break;
+                                                    case 2:  ordinate = envelope.getMedian (dim); break;
+                                                    default: throw new AssertionError(ib3); // Should never happen
+                                                }
+                                                sourcePt[dim] = ordinate;
+                                            }
+                                            temporary = transform.transform(sourceView, temporary);
+                                            transformed.add(temporary);
+                                        }
+                                    }
+                                } while ((isP2 = !isP2) == true);
+                            }
+                        }
+                    }
+                }
+                derivatives[pointIndex] = null; // Let GC do its job earlier.
+            }
+        }
+        if (targetPt != null) {
+            // Copy the coordinate of the center point.
+            System.arraycopy(ordinates, ordinates.length - targetDim, targetPt, 0, targetDim);
+        }
+        return transformed;
+    }
+
+    /**
+     * Transforms an envelope using the given coordinate operation.
+     * The transformation is only approximative: the returned envelope may be bigger than the
+     * smallest possible bounding box, but should not be smaller in most cases.
+     *
+     * <p>This method can handle the case where the envelope contains the North or South pole,
+     * or when it cross the ±180° longitude.</p>
+     *
+     * <div class="note"><b>Note:</b>
+     * If the envelope CRS is non-null, then the caller should ensure that the operation source CRS
+     * is the same than the envelope CRS. In case of mismatch, this method transforms the envelope
+     * to the operation source CRS before to apply the operation. This extra step may cause a lost
+     * of accuracy. In order to prevent this method from performing such pre-transformation (if not desired),
+     * callers can ensure that the envelope CRS is {@code null} before to call this method.</div>
+     *
+     * @param  operation The operation to use.
+     * @param  envelope Envelope to transform, or {@code null}. This envelope will not be modified.
+     * @return The transformed envelope, or {@code null} if {@code envelope} was null.
+     * @throws TransformException if a transform failed.
+     *
+     * @see #transform(MathTransform, Envelope)
+     *
+     * @since 0.5
+     */
+    @SuppressWarnings("null")
+    public static GeneralEnvelope transform(final CoordinateOperation operation, Envelope envelope)
+            throws TransformException
+    {
+        ensureNonNull("operation", operation);
+        if (envelope == null) {
+            return null;
+        }
+        final CoordinateReferenceSystem sourceCRS = operation.getSourceCRS();
+        if (sourceCRS != null) {
+            final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
+            if (crs != null && !Utilities.equalsIgnoreMetadata(crs, sourceCRS)) {
+                /*
+                 * Argument-check: the envelope CRS seems inconsistent with the given operation.
+                 * However we need to push the check a little bit further, since 3D-GeographicCRS
+                 * are considered not equal to CompoundCRS[2D-GeographicCRS + ellipsoidal height].
+                 * Checking for identity MathTransform is a more powerfull (but more costly) check.
+                 * Since we have the MathTransform, perform an opportunist envelope transform if it
+                 * happen to be required.
+                 */
+                final MathTransform mt;
+                try {
+                    mt = getFactory().createOperation(crs, sourceCRS).getMathTransform();
+                } catch (FactoryException e) {
+                    throw new TransformException(Errors.format(Errors.Keys.CanNotTransformEnvelope), e);
+                }
+                if (!mt.isIdentity()) {
+                    envelope = transform(mt, envelope);
+                }
+            }
+        }
+        MathTransform mt = operation.getMathTransform();
+        final double[] centerPt = new double[mt.getTargetDimensions()];
+        final GeneralEnvelope transformed = transform(mt, envelope, centerPt);
+        /*
+         * If the source envelope crosses the expected range of valid coordinates, also projects
+         * the range bounds as a safety. Example: if the source envelope goes from 150 to 200°E,
+         * some map projections will interpret 200° as if it was -160°, and consequently produce
+         * an envelope which do not include the 180°W extremum. We will add those extremum points
+         * explicitly as a safety. It may leads to bigger than necessary target envelope, but the
+         * contract is to include at least the source envelope, not to return the smallest one.
+         */
+        if (sourceCRS != null) {
+            final CoordinateSystem cs = sourceCRS.getCoordinateSystem();
+            if (cs != null) { // Should never be null, but check as a paranoiac safety.
+                DirectPosition sourcePt = null;
+                DirectPosition targetPt = null;
+                final int dimension = cs.getDimension();
+                for (int i=0; i<dimension; i++) {
+                    final CoordinateSystemAxis axis = cs.getAxis(i);
+                    if (axis == null) { // Should never be null, but check as a paranoiac safety.
+                        continue;
+                    }
+                    final double min = envelope.getMinimum(i);
+                    final double max = envelope.getMaximum(i);
+                    final double  v1 = axis.getMinimumValue();
+                    final double  v2 = axis.getMaximumValue();
+                    final boolean b1 = (v1 > min && v1 < max);
+                    final boolean b2 = (v2 > min && v2 < max);
+                    if (!b1 && !b2) {
+                        continue;
+                    }
+                    if (sourcePt == null) {
+                        sourcePt = new GeneralDirectPosition(dimension);
+                        for (int j=0; j<dimension; j++) {
+                            sourcePt.setOrdinate(j, envelope.getMedian(j));
+                        }
+                    }
+                    if (b1) {
+                        sourcePt.setOrdinate(i, v1);
+                        transformed.add(targetPt = mt.transform(sourcePt, targetPt));
+                    }
+                    if (b2) {
+                        sourcePt.setOrdinate(i, v2);
+                        transformed.add(targetPt = mt.transform(sourcePt, targetPt));
+                    }
+                    sourcePt.setOrdinate(i, envelope.getMedian(i));
+                }
+            }
+        }
+        /*
+         * Now takes the target CRS in account...
+         */
+        final CoordinateReferenceSystem targetCRS = operation.getTargetCRS();
+        if (targetCRS == null) {
+            return transformed;
+        }
+        transformed.setCoordinateReferenceSystem(targetCRS);
+        final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
+        if (targetCS == null) {
+            // It should be an error, but we keep this method tolerant.
+            return transformed;
+        }
+        /*
+         * Checks for singularity points. For example the south pole is a singularity point in
+         * geographic CRS because is is located at the maximal value allowed by one particular
+         * axis, namely latitude. This point is not a singularity in the stereographic projection,
+         * because axes extends toward infinity in all directions (mathematically) and because the
+         * South pole has nothing special apart being the origin (0,0).
+         *
+         * Algorithm:
+         *
+         * 1) Inspect the target axis, looking if there is any bounds. If bounds are found, get
+         *    the coordinates of singularity points and project them from target to source CRS.
+         *
+         *    Example: If the transformed envelope above is (80 … 85°S, 10 … 50°W), and if the
+         *             latitude in the target CRS is bounded at 90°S, then project (90°S, 30°W)
+         *             to the source CRS. Note that the longitude is set to the the center of
+         *             the envelope longitude range (more on this below).
+         *
+         * 2) If the singularity point computed above is inside the source envelope, add that
+         *    point to the target (transformed) envelope.
+         *
+         * 3) If step #2 added the point, iterate over all other axes. If an other bounded axis
+         *    is found and that axis is of kind "WRAPAROUND", test for inclusion the same point
+         *    than the point tested at step #1, except for the ordinate of the axis found in this
+         *    step. That ordinate is set to the minimal and maximal values of that axis.
+         *
+         *    Example: If the above steps found that the point (90°S, 30°W) need to be included,
+         *             then this step #3 will also test phe points (90°S, 180°W) and (90°S, 180°E).
+         *
+         * NOTE: we test (-180°, centerY), (180°, centerY), (centerX, -90°) and (centerX, 90°)
+         * at step #1 before to test (-180°, -90°), (180°, -90°), (-180°, 90°) and (180°, 90°)
+         * at step #3 because the later may not be supported by every projections. For example
+         * if the target envelope is located between 20°N and 40°N, then a Mercator projection
+         * may fail to transform the (-180°, 90°) coordinate while the (-180°, 30°) coordinate
+         * is a valid point.
+         */
+        TransformException warning = null;
+        AbstractEnvelope generalEnvelope = null;
+        DirectPosition sourcePt = null;
+        DirectPosition targetPt = null;
+        long includedMinValue = 0; // A bitmask for each dimension.
+        long includedMaxValue = 0;
+        long isWrapAroundAxis = 0;
+        long dimensionBitMask = 1;
+        final int dimension = targetCS.getDimension();
+        for (int i=0; i<dimension; i++, dimensionBitMask <<= 1) {
+            final CoordinateSystemAxis axis = targetCS.getAxis(i);
+            if (axis == null) { // Should never be null, but check as a paranoiac safety.
+                continue;
+            }
+            boolean testMax = false; // Tells if we are testing the minimal or maximal value.
+            do {
+                final double extremum = testMax ? axis.getMaximumValue() : axis.getMinimumValue();
+                if (Double.isInfinite(extremum) || Double.isNaN(extremum)) {
+                    /*
+                     * The axis is unbounded. It should always be the case when the target CRS is
+                     * a map projection, in which case this loop will finish soon and this method
+                     * will do nothing more (no object instantiated, no MathTransform inversed...)
+                     */
+                    continue;
+                }
+                if (targetPt == null) {
+                    try {
+                        mt = mt.inverse();
+                    } catch (NoninvertibleTransformException exception) {
+                        /*
+                         * If the transform is non invertible, this method can't do anything. This
+                         * is not a fatal error because the envelope has already be transformed by
+                         * the caller. We lost the check for singularity points performed by this
+                         * method, but it make no difference in the common case where the source
+                         * envelope didn't contains any of those points.
+                         *
+                         * Note that this exception is normal if target dimension is smaller than
+                         * source dimension, since the math transform can not reconstituate the
+                         * lost dimensions. So we don't log any warning in this case.
+                         */
+                        if (dimension >= mt.getSourceDimensions()) {
+                            recoverableException(exception);
+                        }
+                        return transformed;
+                    }
+                    targetPt = new GeneralDirectPosition(mt.getSourceDimensions());
+                    for (int j=0; j<dimension; j++) {
+                        targetPt.setOrdinate(j, centerPt[j]);
+                    }
+                    // TODO: avoid the hack below if we provide a contains(DirectPosition)
+                    //       method in the GeoAPI org.opengis.geometry.Envelope interface.
+                    generalEnvelope = AbstractEnvelope.castOrCopy(envelope);
+                }
+                targetPt.setOrdinate(i, extremum);
+                try {
+                    sourcePt = mt.transform(targetPt, sourcePt);
+                } catch (TransformException exception) {
+                    /*
+                     * This exception may be normal. For example if may occur when projecting
+                     * the latitude extremums with a cylindrical Mercator projection.  Do not
+                     * log any message (unless logging level is fine) and try the other points.
+                     */
+                    if (warning == null) {
+                        warning = exception;
+                    } else {
+                        warning.addSuppressed(exception);
+                    }
+                    continue;
+                }
+                if (generalEnvelope.contains(sourcePt)) {
+                    transformed.add(targetPt);
+                    if (testMax) includedMaxValue |= dimensionBitMask;
+                    else         includedMinValue |= dimensionBitMask;
+                }
+            } while ((testMax = !testMax) == true);
+            /*
+             * Keep trace of axes of kind WRAPAROUND, except if the two extremum values of that
+             * axis have been included in the envelope  (in which case the next step after this
+             * loop doesn't need to be executed for that axis).
+             */
+            if ((includedMinValue & includedMaxValue & dimensionBitMask) == 0 && isWrapAround(axis)) {
+                isWrapAroundAxis |= dimensionBitMask;
+            }
+            // Restore 'targetPt' to its initial state, which is equals to 'centerPt'.
+            if (targetPt != null) {
+                targetPt.setOrdinate(i, centerPt[i]);
+            }
+        }
+        /*
+         * Step #3 described in the above "Algorithm" section: iterate over all dimensions
+         * of type "WRAPAROUND" for which minimal or maximal axis values have not yet been
+         * included in the envelope. The set of axes is specified by a bitmask computed in
+         * the above loop.  We examine only the points that have not already been included
+         * in the envelope.
+         */
+        final long includedBoundsValue = (includedMinValue | includedMaxValue);
+        if (includedBoundsValue != 0) {
+            while (isWrapAroundAxis != 0) {
+                final int wrapAroundDimension = Long.numberOfTrailingZeros(isWrapAroundAxis);
+                dimensionBitMask = 1 << wrapAroundDimension;
+                isWrapAroundAxis &= ~dimensionBitMask; // Clear now the bit, for the next iteration.
+                final CoordinateSystemAxis wrapAroundAxis = targetCS.getAxis(wrapAroundDimension);
+                final double min = wrapAroundAxis.getMinimumValue();
+                final double max = wrapAroundAxis.getMaximumValue();
+                /*
+                 * Iterate over all axes for which a singularity point has been previously found,
+                 * excluding the "wrap around axis" currently under consideration.
+                 */
+                for (long am=(includedBoundsValue & ~dimensionBitMask), bm; am != 0; am &= ~bm) {
+                    bm = Long.lowestOneBit(am);
+                    final int axisIndex = Long.numberOfTrailingZeros(bm);
+                    final CoordinateSystemAxis axis = targetCS.getAxis(axisIndex);
+                    /*
+                     * switch (c) {
+                     *   case 0: targetPt = (..., singularityMin, ..., wrapAroundMin, ...)
+                     *   case 1: targetPt = (..., singularityMin, ..., wrapAroundMax, ...)
+                     *   case 2: targetPt = (..., singularityMax, ..., wrapAroundMin, ...)
+                     *   case 3: targetPt = (..., singularityMax, ..., wrapAroundMax, ...)
+                     * }
+                     */
+                    for (int c=0; c<4; c++) {
+                        /*
+                         * Set the ordinate value along the axis having the singularity point
+                         * (cases c=0 and c=2). If the envelope did not included that point,
+                         * then skip completly this case and the next one, i.e. skip c={0,1}
+                         * or skip c={2,3}.
+                         */
+                        double value = max;
+                        if ((c & 1) == 0) { // 'true' if we are testing "wrapAroundMin".
+                            if (((c == 0 ? includedMinValue : includedMaxValue) & bm) == 0) {
+                                c++; // Skip also the case for "wrapAroundMax".
+                                continue;
+                            }
+                            targetPt.setOrdinate(axisIndex, (c == 0) ? axis.getMinimumValue() : axis.getMaximumValue());
+                            value = min;
+                        }
+                        targetPt.setOrdinate(wrapAroundDimension, value);
+                        try {
+                            sourcePt = mt.transform(targetPt, sourcePt);
+                        } catch (TransformException exception) {
+                            if (warning == null) {
+                                warning = exception;
+                            } else {
+                                warning.addSuppressed(exception);
+                            }
+                            continue;
+                        }
+                        if (generalEnvelope.contains(sourcePt)) {
+                            transformed.add(targetPt);
+                        }
+                    }
+                    targetPt.setOrdinate(axisIndex, centerPt[axisIndex]);
+                }
+                targetPt.setOrdinate(wrapAroundDimension, centerPt[wrapAroundDimension]);
+            }
+        }
+        if (warning != null) {
+            recoverableException(warning);
+        }
+        return transformed;
     }
 
     /**
@@ -170,4 +842,17 @@ public final class Envelopes extends Sta
         }
         return buffer.append("))").toString();
     }
+
+    /**
+     * Enumeration of the 4 corners in an envelope, with repetition of the first point.
+     * The values are (x,y) pairs with {@code false} meaning "minimal value" and {@code true}
+     * meaning "maximal value". This is used by {@link #toPolygonWKT(Envelope)} only.
+     */
+    private static final boolean[] CORNERS = {
+        false, false,
+        false, true,
+        true,  true,
+        true,  false,
+        false, false
+    };
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -112,7 +112,7 @@ import static org.apache.sis.math.MathFu
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @since   0.3 (derived from geotk-2.4)
- * @version 0.3
+ * @version 0.5
  * @module
  *
  * @see Envelope2D
@@ -440,6 +440,32 @@ public class GeneralEnvelope extends Arr
     }
 
     /**
+     * Translates the envelope by the given vector. For every dimension <var>i</var>, the
+     * {@linkplain #getLower(int) lower} and {@linkplain #getUpper(int) upper} values are
+     * increased by {@code vector[i]}.
+     *
+     * <p>This method does not check if the translation result is inside the coordinate system domain
+     * (e.g. [-180 … +180]° of longitude). Callers can normalize the envelope when desired by call to
+     * the {@link #normalize()} method.</p>
+     *
+     * @param vector The translation vector. The length of this array shall be equal to this envelope
+     *        {@linkplain #getDimension() dimension}.
+     *
+     * @since 0.5
+     */
+    public void translate(final double... vector) {
+        ensureNonNull("vector", vector);
+        final int beginIndex = beginIndex();
+        ensureDimensionMatches("vector", endIndex() - beginIndex, vector);
+        final int upperIndex = beginIndex + (ordinates.length >>> 1);
+        for (int i=0; i<vector.length; i++) {
+            final double t = vector[i];
+            ordinates[beginIndex + i] += t;
+            ordinates[upperIndex + i] += t;
+        }
+    }
+
+    /**
      * Adds to this envelope a point of the given array.
      * This method does not check for anti-meridian spanning. It is invoked only
      * by the {@link Envelopes} transform methods, which build "normal" envelopes.
@@ -952,7 +978,7 @@ public class GeneralEnvelope extends Arr
                     changed = true;
                 } else {
                     throw new IllegalStateException(Errors.format(Errors.Keys.IllegalOrdinateRange_3,
-                            (axis != null) ? axis.getName() : i, lower, upper));
+                            lower, upper, (axis != null) ? axis.getName() : i));
                 }
             }
         }
@@ -985,6 +1011,8 @@ public class GeneralEnvelope extends Arr
      * @param  endIndex   The index after the last valid ordinate value of the corners.
      * @return The sub-envelope of dimension {@code endIndex - beginIndex}.
      * @throws IndexOutOfBoundsException If an index is out of bounds.
+     *
+     * @see org.apache.sis.referencing.CRS#getComponentAt(CoordinateReferenceSystem, int, int)
      */
     // Must be overridden in SubEnvelope
     public GeneralEnvelope subEnvelope(final int beginIndex, final int endIndex) throws IndexOutOfBoundsException {

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -22,11 +22,10 @@ import javax.xml.bind.annotation.XmlAttr
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.internal.util.DefinitionURI;
+import org.apache.sis.internal.metadata.NameMeaning;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.metadata.iso.citation.Citations;
 
-import static org.apache.sis.internal.metadata.ReferencingUtilities.toURNType;
-
 
 /**
  * The {@code gml:CodeType}, which is made of a code space and a code value.
@@ -154,7 +153,7 @@ public final class Code {
              */
             if (fallback != null) {
                 if (!isHTTP) {
-                    final String urn = DefinitionURI.format(toURNType(type), fallback);
+                    final String urn = DefinitionURI.format(NameMeaning.toObjectType(type), fallback);
                     if (urn != null) {
                         final Code code = new Code();
                         code.codeSpace = Citations.getIdentifier(fallback.getAuthority());

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/SecondDefiningParameter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/SecondDefiningParameter.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/SecondDefiningParameter.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/SecondDefiningParameter.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -23,7 +23,7 @@ import org.opengis.referencing.datum.Ell
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.gco.Measure;
-import org.apache.sis.internal.metadata.ReferencingUtilities;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.util.resources.Errors;
 
 

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -30,7 +30,7 @@ import static org.apache.sis.util.CharSe
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4 (derived from geotk-3.13)
- * @version 0.4
+ * @version 0.5
  * @module
  */
 public final class AxisDirections extends Static {
@@ -382,6 +382,34 @@ public final class AxisDirections extend
     }
 
     /**
+     * Returns the index of the first dimension in {@code cs} where axes are colinear with the {@code subCS} axes.
+     * If no such dimension is found, returns -1.
+     *
+     * @param  cs    The coordinate system which contains all axes.
+     * @param  subCS The coordinate system to search into {@code cs}.
+     * @return The first dimension of a sequence of axes colinear with {@code subCS} axes, or {@code -1} if none.
+     *
+     * @since 0.5
+     */
+    public static int indexOfColinear(final CoordinateSystem cs, final CoordinateSystem subCS) {
+        final int dim = indexOfColinear(cs, subCS.getAxis(0).getDirection());
+        if (dim >= 0) {
+            int i = subCS.getDimension();
+            if (dim + i > cs.getDimension()) {
+                return -1;
+            }
+            while (--i > 0) { // Intentionally exclude 0.
+                if (!absolute(subCS.getAxis(i).getDirection()).equals(
+                     absolute(cs.getAxis(i + dim).getDirection())))
+                {
+                    return -1;
+                }
+            }
+        }
+        return dim;
+    }
+
+    /**
      * Searches for an axis direction having the given name in the specified list of directions.
      * This method compares the given name with the name of each {@code AxisDirection} in a lenient way:
      *

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -31,7 +31,7 @@ import static org.apache.sis.internal.me
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4 (derived from geotk-3.00)
- * @version 0.4
+ * @version 0.5
  * @module
  */
 public final class Formulas extends Static {
@@ -71,6 +71,26 @@ public final class Formulas extends Stat
     }
 
     /**
+     * Returns 3ⁿ for very small (less than 10) positive values of <var>n</var>.
+     * Note that this method overflow for any value equals or greater than 20.
+     *
+     * @param n The exponent.
+     * @return 3ⁿ
+     *
+     * @see org.apache.sis.math.DecimalFunctions#pow10(int)
+     *
+     * @since 0.5
+     */
+    public static int pow3(int n) {
+        assert n >= 0 && n <= 19 : n;
+        int p = 1;
+        while (--n >= 0) {
+            p *= 3;
+        }
+        return p;
+    }
+
+    /**
      * Returns {@code true} if {@code ymin} is the south pole and {@code ymax} is the north pole.
      *
      * @param ymin The minimal latitude to test.

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -49,6 +49,240 @@ public final class ShapeUtilities extend
     }
 
     /**
+     * Returns the intersection point between two line segments. The lines do not continue to infinity;
+     * if the intersection does not occur between the ending points {@linkplain Line2D#getP1() P1} and
+     * {@linkplain Line2D#getP2() P2} of the two line segments, then this method returns {@code null}.
+     *
+     * @param  ax1 <var>x</var> value of the first point on the first  line.
+     * @param  ay1 <var>y</var> value of the first point on the first  line.
+     * @param  ax2 <var>x</var> value of the last  point on the first  line.
+     * @param  ay2 <var>y</var> value of the last  point on the first  line.
+     * @param  bx1 <var>x</var> value of the first point on the second line.
+     * @param  by1 <var>y</var> value of the first point on the second line.
+     * @param  bx2 <var>x</var> value of the last  point on the second line.
+     * @param  by2 <var>y</var> value of the last  point on the second line.
+     * @return The intersection point, or {@code null} if none.
+     *
+     * @todo This method is used by Geotk (a sandbox for code that may migrate to SIS), but not yet by SIS.
+     *       We temporarily keep this code here, but may delete or move it elsewhere in a future SIS version
+     *       depending whether we port to SIS the sandbox code.
+     */
+    public static Point2D.Double intersectionPoint(final double ax1, final double ay1, double ax2, double ay2,
+                                                   final double bx1, final double by1, double bx2, double by2)
+    {
+        ax2 -= ax1;
+        ay2 -= ay1;
+        bx2 -= bx1;
+        by2 -= by1;
+        double x = ay2 * bx2;
+        double y = ax2 * by2;
+        /*
+         * The above (x,y) coordinate is temporary. If and only if the two line are parallel, then x == y.
+         * Following code computes the real (x,y) coordinates of the intersection point.
+         */
+        x = ((by1-ay1) * (ax2*bx2) + x*ax1 - y*bx1) / (x-y);
+        y = abs(bx2) > abs(ax2) ?
+                (by2 / bx2) * (x - bx1) + by1 :
+                (ay2 / ax2) * (x - ax1) + ay1;
+        /*
+         * The '!=0' expressions below are important for avoiding rounding errors with
+         * horizontal or vertical lines. The '!' are important for handling NaN values.
+         */
+        if (ax2 != 0 && !(ax2 < 0 ? (x <= ax1 && x >= ax1 + ax2) : (x >= ax1 && x <= ax1 + ax2))) return null;
+        if (bx2 != 0 && !(bx2 < 0 ? (x <= bx1 && x >= bx1 + bx2) : (x >= bx1 && x <= bx1 + bx2))) return null;
+        if (ay2 != 0 && !(ay2 < 0 ? (y <= ay1 && y >= ay1 + ay2) : (y >= ay1 && y <= ay1 + ay2))) return null;
+        if (by2 != 0 && !(by2 < 0 ? (y <= by1 && y >= by1 + by2) : (y >= by1 && y <= by1 + by2))) return null;
+        return new Point2D.Double(x,y);
+    }
+
+    /**
+     * Returns the point on the given {@code line} segment which is closest to the given {@code point}.
+     * Let {@code result} be the returned point. This method guarantees (except for rounding errors) that:
+     *
+     * <ul>
+     *   <li>{@code result} is a point on the {@code line} segment. It is located between the
+     *       {@linkplain Line2D#getP1() P1} and {@linkplain Line2D#getP2() P2} ending points
+     *       of that line segment.</li>
+     *   <li>The distance between the {@code result} point and the given {@code point} is
+     *       the shortest distance among the set of points meeting the previous condition.
+     *       This distance can be obtained with {@code point.distance(result)}.</li>
+     * </ul>
+     *
+     * @param  x1 <var>x</var> value of the first point on the line.
+     * @param  y1 <var>y</var> value of the first point on the line.
+     * @param  x2 <var>x</var> value of the last  point on the line.
+     * @param  y2 <var>y</var> value of the last  point on the line.
+     * @param  x  <var>x</var> value of a point close to the given line.
+     * @param  y  <var>y</var> value of a point close to the given line.
+     * @return The nearest point on the given line.
+     *
+     * @see #colinearPoint(double,double , double,double , double,double , double)
+     *
+     * @todo This method is used by Geotk (a sandbox for code that may migrate to SIS), but not yet by SIS.
+     *       We temporarily keep this code here, but may delete or move it elsewhere in a future SIS version
+     *       depending whether we port to SIS the sandbox code.
+     */
+    public static Point2D.Double nearestColinearPoint(final double x1, final double y1,
+                                                      final double x2, final double y2,
+                                                            double x,        double y)
+    {
+        final double slope = (y2-y1) / (x2-x1);
+        if (!Double.isInfinite(slope)) {
+            final double y0 = (y2 - slope*x2);
+            x = ((y - y0) * slope + x) / (slope*slope + 1);
+            y = x*slope + y0;
+        } else {
+            x = x2;
+        }
+        if (x1 <= x2) {
+            if (x < x1) x = x1;
+            if (x > x2) x = x2;
+        } else {
+            if (x > x1) x = x1;
+            if (x < x2) x = x2;
+        }
+        if (y1 <= y2) {
+            if (y < y1) y = y1;
+            if (y > y2) y = y2;
+        } else {
+            if (y > y1) y = y1;
+            if (y < y2) y = y2;
+        }
+        return new Point2D.Double(x,y);
+    }
+
+    /**
+     * Returns a point on the given {@code line} segment located at the given {@code distance}
+     * from that line. Let {@code result} be the returned point. If {@code result} is not null,
+     * then this method guarantees (except for rounding error) that:
+     *
+     * <ul>
+     *   <li>{@code result} is a point on the {@code line} segment. It is located between
+     *       the {@linkplain Line2D#getP1() P1} and {@linkplain Line2D#getP2() P2} ending
+     *       points of that line segment.</li>
+     *   <li>The distance between the {@code result} and the given {@code point} is exactly
+     *       equal to {@code distance}.</li>
+     * </ul>
+     *
+     * If no result point meets those conditions, then this method returns {@code null}.
+     * If two result points meet those conditions, then this method returns the point
+     * which is the closest to {@code line.getP1()}.
+     *
+     * @param  x1 <var>x</var> value of the first point on the line.
+     * @param  y1 <var>y</var> value of the first point on the line.
+     * @param  x2 <var>x</var> value of the last  point on the line.
+     * @param  y2 <var>y</var> value of the last  point on the line.
+     * @param  x  <var>x</var> value of a point close to the given line.
+     * @param  y  <var>y</var> value of a point close to the given line.
+     * @param  distance The distance between the given point and the point to be returned.
+     * @return A point on the given line located at the given distance from the given point.
+     *
+     * @see #nearestColinearPoint(double,double , double,double , double,double)
+     *
+     * @todo This method is used by Geotk (a sandbox for code that may migrate to SIS), but not yet by SIS.
+     *       We temporarily keep this code here, but may delete or move it elsewhere in a future SIS version
+     *       depending whether we port to SIS the sandbox code.
+     */
+    public static Point2D.Double colinearPoint(double x1, double y1, double x2, double y2,
+                                               double x, double y, double distance)
+    {
+        final double ox1 = x1;
+        final double oy1 = y1;
+        final double ox2 = x2;
+        final double oy2 = y2;
+        distance *= distance;
+        if (x1 == x2) {
+            double dy = x1 - x;
+            dy = sqrt(distance - dy*dy);
+            y1 = y - dy;
+            y2 = y + dy;
+        } else if (y1 == y2) {
+            double dx = y1 - y;
+            dx = sqrt(distance - dx*dx);
+            x1 = x - dx;
+            x2 = x + dx;
+        } else {
+            final double m  = (y1-y2) / (x2-x1);
+            final double y0 = (y2-y) + m*(x2-x);
+            final double B  = m * y0;
+            final double A  = m*m + 1;
+            final double C  = sqrt(B*B + A*(distance - y0*y0));
+            x1 = (B+C) / A;
+            x2 = (B-C) / A;
+            y1 = y + y0 - m*x1;
+            y2 = y + y0 - m*x2;
+            x1 += x;
+            x2 += x;
+        }
+        boolean in1, in2;
+        if (oy1 > oy2) {
+            in1 = (y1 <= oy1 && y1 >= oy2);
+            in2 = (y2 <= oy1 && y2 >= oy2);
+        } else {
+            in1 = (y1 >= oy1 && y1 <= oy2);
+            in2 = (y2 >= oy1 && y2 <= oy2);
+        }
+        if (ox1 > ox2) {
+            in1 &= (x1 <= ox1 && x1 >= ox2);
+            in2 &= (x2 <= ox1 && x2 >= ox2);
+        } else {
+            in1 &= (x1 >= ox1 && x1 <= ox2);
+            in2 &= (x2 >= ox1 && x2 <= ox2);
+        }
+        if (!in1 && !in2) return null;
+        if (!in1) return new Point2D.Double(x2,y2);
+        if (!in2) return new Point2D.Double(x1,y1);
+        x = x1 - ox1;
+        y = y1 - oy1;
+        final double d1 = x*x + y*y;
+        x = x2 - ox1;
+        y = y2 - oy1;
+        final double d2 = x*x + y*y;
+        if (d1 > d2) return new Point2D.Double(x2,y2);
+        else         return new Point2D.Double(x1,y1);
+    }
+
+    /**
+     * Returns a quadratic curve passing by the 3 given points. There is an infinity of quadratic curves passing by
+     * 3 points. We can express the curve we are looking for as a parabolic equation of the form {@code y=ax²+bx+c}
+     * but where the <var>x</var> axis is not necessarily horizontal. The orientation of the <var>x</var> axis in
+     * the above equation is determined by the {@code horizontal} parameter:
+     *
+     * <ul>
+     *   <li>A value of {@code true} means that the <var>x</var> axis must be horizontal. The quadratic curve
+     *       will then look like an ordinary parabolic curve as we see in mathematic school book.</li>
+     *   <li>A value of {@code false} means that the <var>x</var> axis must be parallel to the
+     *       line segment joining the {@code P0} and {@code P2} ending points.</li>
+     * </ul>
+     *
+     * Note that if {@code P0.y == P2.y}, then both {@code horizontal} values produce the same result.
+     *
+     * @param  x1 <var>x</var> value of the starting point.
+     * @param  y1 <var>y</var> value of the starting point.
+     * @param  px <var>x</var> value of a passing point.
+     * @param  py <var>y</var> value of a passing point.
+     * @param  x2 <var>x</var> value of the ending point.
+     * @param  y2 <var>y</var> value of the ending point.
+     * @param  horizontal If {@code true}, the <var>x</var> axis is considered horizontal while computing the
+     *         {@code y=ax²+bx+c} equation terms. If {@code false}, it is considered parallel to the line
+     *         joining the {@code P0} and {@code P2} points.
+     * @return A quadratic curve passing by the given points. The curve starts at {@code P0} and ends at {@code P2}.
+     *         If two points are too close or if the three points are colinear, then this method returns {@code null}.
+     *
+     * @todo This method is used by Geotk (a sandbox for code that may migrate to SIS), but not yet by SIS.
+     *       We temporarily keep this code here, but may delete or move it elsewhere in a future SIS version
+     *       depending whether we port to SIS the sandbox code.
+     */
+    public static QuadCurve2D.Double fitParabol(final double x1, final double y1,
+                                                final double px, final double py,
+                                                final double x2, final double y2,
+                                                final boolean horizontal)
+    {
+        final Point2D.Double p = parabolicControlPoint(x1, y1, px, py, x2, y2, horizontal);
+        return (p != null) ? new QuadCurve2D.Double(x1, y1, p.x, p.y, x2, y2) : null;
+    }
+
+    /**
      * Returns the control point of a quadratic curve passing by the 3 given points. There is an infinity of quadratic
      * curves passing by 3 points. We can express the curve we are looking for as a parabolic equation of the form
      * {@code y = ax²+bx+c}, but the <var>x</var> axis is not necessarily horizontal. The <var>x</var> axis orientation
@@ -64,10 +298,10 @@ public final class ShapeUtilities extend
      *
      * Note that if {@code P0.y == P2.y}, then both {@code horizontal} values produce the same result.
      *
-     * @param  x0 <var>x</var> value of the starting point.
-     * @param  y0 <var>y</var> value of the starting point.
-     * @param  x1 <var>x</var> value of a passing point.
-     * @param  y1 <var>y</var> value of a passing point.
+     * @param  x1 <var>x</var> value of the starting point.
+     * @param  y1 <var>y</var> value of the starting point.
+     * @param  px <var>x</var> value of a passing point.
+     * @param  py <var>y</var> value of a passing point.
      * @param  x2 <var>x</var> value of the ending point.
      * @param  y2 <var>y</var> value of the ending point.
      * @param  horizontal If {@code true}, the <var>x</var> axis is considered horizontal while computing the
@@ -77,26 +311,26 @@ public final class ShapeUtilities extend
      *         and ends at {@code (x2,y2)}. If two points are too close or if the three points are colinear, then this
      *         method returns {@code null}.
      */
-    public static Point2D.Double parabolicControlPoint(final double x0, final double y0,
-            double x1, double y1, double x2, double y2, final boolean horizontal)
+    public static Point2D.Double parabolicControlPoint(final double x1, final double y1,
+            double px, double py, double x2, double y2, final boolean horizontal)
     {
         /*
          * Apply a translation in such a way that (x0,y0) become the coordinate system origin.
          * After this translation, we shall not use (x0,y0) until we are done.
          */
-        x1 -= x0;
-        y1 -= y0;
-        x2 -= x0;
-        y2 -= y0;
+        px -= x1;
+        py -= y1;
+        x2 -= x1;
+        y2 -= y1;
         if (horizontal) {
-            final double a = (y2 - y1*x2/x1) / (x2-x1); // Actually "a*x2"
+            final double a = (y2 - py*x2/px) / (x2-px); // Actually "a*x2"
             final double check = abs(a);
             if (!(check <= 1/EPS)) return null; // Two points have the same coordinates.
             if (!(check >=   EPS)) return null; // The three points are co-linear.
             final double b = y2/x2 - a;
-            x1 = (1 + b/(2*a))*x2 - y2/(2*a);
-            y1 = y0 + b*x1;
-            x1 += x0;
+            px = (1 + b/(2*a))*x2 - y2/(2*a);
+            py = y1 + b*px;
+            px += x1;
         } else {
             /*
              * Apply a rotation in such a way that (x2,y2)
@@ -105,15 +339,15 @@ public final class ShapeUtilities extend
             final double rx2 = x2;
             final double ry2 = y2;
             x2 = hypot(x2,y2);
-            y2 = (x1*rx2 + y1*ry2) / x2; // use 'y2' as a temporary variable for 'x1'
-            y1 = (y1*rx2 - x1*ry2) / x2;
-            x1 = y2;
-            y2 = 0;
+            y2 = (px*rx2 + py*ry2) / x2; // use 'y2' as a temporary variable for 'x1'
+            py = (py*rx2 - px*ry2) / x2;
+            px = y2;
+            y2 = 0; // set as a matter of principle (but not used).
             /*
              * Now compute the control point coordinates in our new coordinate system axis.
              */
             final double x = 0.5;                       // Actually "x/x2"
-            final double y = (y1*x*x2) / (x1*(x2-x1));  // Actually "y/y2"
+            final double y = (py*x*x2) / (px*(x2-px));  // Actually "y/y2"
             final double check = abs(y);
             if (!(check <= 1/EPS)) return null; // Two points have the same coordinates.
             if (!(check >=   EPS)) return null; // The three points are co-linear.
@@ -121,10 +355,40 @@ public final class ShapeUtilities extend
              * Applies the inverse rotation then a translation to bring
              * us back to the original coordinate system.
              */
-            x1 = (x*rx2 - y*ry2) + x0;
-            y1 = (y*rx2 + x*ry2) + y0;
+            px = (x*rx2 - y*ry2) + x1;
+            py = (y*rx2 + x*ry2) + y1;
         }
-        return new Point2D.Double(x1,y1);
+        return new Point2D.Double(px,py);
+    }
+
+    /**
+     * Returns a circle passing by the 3 given points. The distance between the returned
+     * point and any of the given points will be constant; it is the circle radius.
+     *
+     * @param  x1 <var>x</var> value of the first  point.
+     * @param  y1 <var>y</var> value of the first  point.
+     * @param  x2 <var>x</var> value of the second point.
+     * @param  y2 <var>y</var> value of the second point.
+     * @param  x3 <var>x</var> value of the third  point.
+     * @param  y3 <var>y</var> value of the third  point.
+     * @return A circle passing by the given points.
+     *
+     * @todo This method is used by Geotk (a sandbox for code that may migrate to SIS), but not yet by SIS.
+     *       We temporarily keep this code here, but may delete or move it elsewhere in a future SIS version
+     *       depending whether we port to SIS the sandbox code.
+     */
+    public static Point2D.Double circleCentre(double x1, double y1,
+                                              double x2, double y2,
+                                              double x3, double y3)
+    {
+        x2 -= x1;
+        x3 -= x1;
+        y2 -= y1;
+        y3 -= y1;
+        final double sq2 = (x2*x2 + y2*y2);
+        final double sq3 = (x3*x3 + y3*y3);
+        final double x   = (y2*sq3 - y3*sq2) / (y2*x3 - y3*x2);
+        return new Point2D.Double(x1 + 0.5*x, y1 + 0.5*(sq2 - x*x2)/y2);
     }
 
     /**
@@ -138,17 +402,23 @@ public final class ShapeUtilities extend
      * @return A simpler Java construct, or {@code path} if no better construct is proposed.
      */
     public static Shape toPrimitive(final Shape path) {
-        final double[] buffer = new double[6];
         final PathIterator it = path.getPathIterator(null);
-        if (!it.isDone() && it.currentSegment(buffer) == PathIterator.SEG_MOVETO && !it.isDone()) {
-            final double x1 = buffer[0];
-            final double y1 = buffer[1];
-            final int code = it.currentSegment(buffer);
-            if (it.isDone()) {
-                switch (code) {
-                    case PathIterator.SEG_LINETO:  return new       Line2D.Double(x1,y1, buffer[0], buffer[1]);
-                    case PathIterator.SEG_QUADTO:  return new  QuadCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3]);
-                    case PathIterator.SEG_CUBICTO: return new CubicCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
+        if (!it.isDone()) {
+            final double[] buffer = new double[6];
+            if (it.currentSegment(buffer) == PathIterator.SEG_MOVETO) {
+                it.next();
+                if (!it.isDone()) {
+                    final double x1 = buffer[0];
+                    final double y1 = buffer[1];
+                    final int code = it.currentSegment(buffer);
+                    it.next();
+                    if (it.isDone()) {
+                        switch (code) {
+                            case PathIterator.SEG_LINETO:  return new       Line2D.Double(x1,y1, buffer[0], buffer[1]);
+                            case PathIterator.SEG_QUADTO:  return new  QuadCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3]);
+                            case PathIterator.SEG_CUBICTO: return new CubicCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
+                        }
+                    }
                 }
             }
         }

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -115,7 +115,7 @@ public class DefaultParameterDescriptor<
 
     /**
      * Constructs a descriptor from the given properties. The properties map is given unchanged to the
-     * {@linkplain AbstractParameterDescriptor#AbstractParameterDescriptor(Map) super-class constructor}.
+     * {@linkplain AbstractParameterDescriptor#AbstractParameterDescriptor(Map, int, int) super-class constructor}.
      * The following table is a reminder of main (not all) properties:
      *
      * <table class="sis">
@@ -141,7 +141,7 @@ public class DefaultParameterDescriptor<
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.apache.sis.parameter.AbstractParameterDescriptor#DESCRIPTION_KEY}</td>
+     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -103,7 +103,7 @@ public class DefaultParameterDescriptorG
 
     /**
      * Constructs a parameter group from a set of properties. The properties map is given unchanged to the
-     * {@linkplain AbstractParameterDescriptor#AbstractParameterDescriptor(Map) super-class constructor}.
+     * {@linkplain AbstractParameterDescriptor#AbstractParameterDescriptor(Map, int, int) super-class constructor}.
      * The following table is a reminder of main (not all) properties:
      *
      * <table class="sis">
@@ -129,7 +129,7 @@ public class DefaultParameterDescriptorG
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.apache.sis.parameter.AbstractParameterDescriptor#DESCRIPTION_KEY}</td>
+     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -38,7 +38,7 @@ import org.opengis.metadata.citation.Cit
 import org.opengis.referencing.ObjectFactory;
 import org.opengis.referencing.AuthorityFactory;
 import org.opengis.referencing.IdentifiedObject;
-import org.apache.sis.internal.metadata.ReferencingUtilities;
+import org.apache.sis.internal.metadata.NameMeaning;
 import org.apache.sis.internal.jaxb.referencing.Code;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
@@ -61,7 +61,7 @@ import static org.apache.sis.internal.ut
 import static org.apache.sis.internal.util.CollectionsExt.nonEmpty;
 import static org.apache.sis.internal.util.CollectionsExt.immutableSet;
 import static org.apache.sis.internal.util.Utilities.appendUnicodeIdentifier;
-import static org.apache.sis.internal.metadata.ReferencingUtilities.canSetProperty;
+import static org.apache.sis.internal.referencing.ReferencingUtilities.canSetProperty;
 
 // Branch-dependent imports
 import java.util.Objects;
@@ -466,7 +466,7 @@ public class AbstractIdentifiedObject ex
         if (identifiers != null) {
             for (final Identifier identifier : identifiers) {
                 if (appendUnicodeIdentifier(id, '-', identifier.getCodeSpace(), ":", true) | // Really |, not ||
-                    appendUnicodeIdentifier(id, '-', ReferencingUtilities.toURNType(getClass()), ":", false) |
+appendUnicodeIdentifier(id, '-', NameMeaning.toObjectType(getClass()), ":", false) |
                     appendUnicodeIdentifier(id, '-', identifier.getCode(), ":", true))
                 {
                     /*

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1651201&r1=1651200&r2=1651201&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Mon Jan 12 21:22:17 2015
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collections;
+import javax.measure.unit.NonSI;
 import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
@@ -31,6 +32,7 @@ import org.opengis.referencing.crs.Singl
 import org.opengis.referencing.crs.CompoundCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
+import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.TemporalCRS;
@@ -39,13 +41,14 @@ import org.opengis.metadata.extent.Exten
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.internal.util.DefinitionURI;
 import org.apache.sis.internal.referencing.AxisDirections;
-import org.apache.sis.internal.metadata.ReferencingUtilities;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.referencing.cs.DefaultVerticalCS;
 import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
 import org.apache.sis.referencing.crs.DefaultGeographicCRS;
 import org.apache.sis.referencing.crs.DefaultVerticalCRS;
 import org.apache.sis.referencing.crs.DefaultCompoundCRS;
 import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.CharSequences;
@@ -53,7 +56,6 @@ import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Static;
 
 import static java.util.Collections.singletonMap;
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
 
 /**
@@ -137,7 +139,7 @@ public final class CRS extends Static {
     public static CoordinateReferenceSystem forCode(final String code)
             throws NoSuchAuthorityCodeException, FactoryException
     {
-        ensureNonNull("code", code);
+        ArgumentChecks.ensureNonNull("code", code);
         final String authority;
         final String value;
         final DefinitionURI uri = DefinitionURI.parse(code);
@@ -424,4 +426,81 @@ public final class CRS extends Static {
         }
         return singles;
     }
+
+    /**
+     * Returns the coordinate reference system in the given range of dimension indices.
+     * This method processes as below:
+     *
+     * <ul>
+     *   <li>If the given {@code crs} is {@code null}, then this method returns {@code null}.</li>
+     *   <li>Otherwise if {@code lower} is 0 and {@code upper} if the number of CRS dimensions,
+     *       then this method returns the given CRS unchanged.</li>
+     *   <li>Otherwise if the given CRS is an instance of {@link CompoundCRS}, then this method
+     *       searches for a {@linkplain CompoundCRS#getComponents() component} where:
+     *       <ul>
+     *         <li>The {@linkplain CoordinateSystem#getDimension() number of dimensions} is
+     *             equals to {@code upper - lower};</li>
+     *         <li>The sum of the number of dimensions of all previous CRS is equals to
+     *             {@code lower}.</li>
+     *       </ul>
+     *       If such component is found, then it is returned.</li>
+     *   <li>Otherwise (i.e. no component match), this method returns {@code null}.</li>
+     * </ul>
+     *
+     * This method does <strong>not</strong> attempt to build new CRS from the components.
+     * For example it does not attempt to create a 3D geographic CRS from a 2D one + a vertical component.
+     *
+     * @param  crs   The coordinate reference system to decompose, or {@code null}.
+     * @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 the given {@code crs} was {@code null}
+     *         or can not be decomposed for dimensions in the [{@code lower} … {@code upper}] range.
+     * @throws IndexOutOfBoundsException If the given index are out of bounds.
+     *
+     * @since 0.5
+     *
+     * @see org.apache.sis.geometry.GeneralEnvelope#subEnvelope(int, int)
+     */
+    public static CoordinateReferenceSystem getComponentAt(CoordinateReferenceSystem crs, int lower, int upper) {
+        if (crs != null) {
+            int dimension = crs.getCoordinateSystem().getDimension();
+            ArgumentChecks.ensureValidIndexRange(dimension, lower, upper);
+check:      while (lower != 0 || upper != dimension) {
+                if (crs instanceof CompoundCRS) {
+                    final List<CoordinateReferenceSystem> components = ((CompoundCRS) crs).getComponents();
+                    final int size = components.size();
+                    for (int i=0; i<size; i++) {
+                        crs = components.get(i);
+                        dimension = crs.getCoordinateSystem().getDimension();
+                        if (lower < dimension) {
+                            // The requested dimensions may intersect the dimension of this CRS.
+                            // The outer loop will perform the verification, and eventually go
+                            // down again in the tree of sub-components.
+                            continue check;
+                        }
+                        lower -= dimension;
+                        upper -= dimension;
+                    }
+                }
+                return null;
+            }
+        }
+        return crs;
+    }
+
+    /**
+     * Returns the Greenwich longitude of the prime meridian of the given CRS in degrees.
+     * If the prime meridian uses an other unit than degrees, then the value will be converted.
+     *
+     * @param  crs The coordinate reference system from which to get the prime meridian.
+     * @return The Greenwich longitude (in degrees) of the prime meridian of the given CRS.
+     *
+     * @since 0.5
+     *
+     * @see org.apache.sis.referencing.datum.DefaultPrimeMeridian#getGreenwichLongitude(Unit)
+     */
+    public static double getGreenwichLongitude(final GeodeticCRS crs) {
+        ArgumentChecks.ensureNonNull("crs", crs);
+        return ReferencingUtilities.getGreenwichLongitude(crs.getDatum().getPrimeMeridian(), NonSI.DEGREE_ANGLE);
+    }
 }



Mime
View raw message