sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1789729 [6/9] - in /sis/branches/JDK9: ./ application/sis-console/ application/sis-console/src/main/java/org/apache/sis/console/ application/sis-console/src/test/java/org/apache/sis/console/ core/ core/sis-feature/src/main/java/org/apache/...
Date Fri, 31 Mar 2017 18:49:18 GMT
Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -24,14 +24,15 @@ import java.util.Date;
 import java.io.IOException;
 import java.text.Format;
 import java.text.DateFormat;
+import java.text.NumberFormat;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
-import java.text.NumberFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import javax.measure.Unit;
 
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.geometry.DirectPosition;
 import org.apache.sis.measure.Angle;
 import org.apache.sis.measure.AngleFormat;
 import org.apache.sis.measure.Range;
@@ -40,11 +41,14 @@ import org.apache.sis.measure.UnitFormat
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.internal.util.MetadataServices;
 import org.apache.sis.internal.util.LocalizedParseException;
 
 import static org.apache.sis.internal.util.StandardDateFormat.UTC;
 
+// Branch-dependent imports
+import java.io.UncheckedIOException;
+
 
 /**
  * Base class of {@link Format} implementations which delegate part of their work to other
@@ -52,28 +56,44 @@ import static org.apache.sis.internal.ut
  * large blocks of data, for example a metadata tree or a <cite>Well Known Text</cite> (WKT).
  * Those blocks of data usually contain smaller elements like numbers and dates, whose parsing
  * and formatting can be delegated to {@link NumberFormat} and {@link DateFormat} respectively.
+ * Subclasses can obtain instances of those formats by call to {@link #getFormat(Class)} where
+ * the argument is the type of the value to parse or format.
+ * {@code CompoundFormat} supports at least the following value types, but subclasses may add more types:
  *
- * <p>Since {@code CompoundFormat} may work on larger texts than the usual {@code Format} classes,
+ * <table class="sis">
+ *   <caption>Supported value types</caption>
+ *   <tr><th>Value type</th>              <th>Format type</th>                                      <th>Remarks</th></tr>
+ *   <tr><td>{@link DirectPosition}</td>  <td>{@link org.apache.sis.geometry.CoordinateFormat}</td> <td>Requires {@code sis-referencing} module.</td></tr>
+ *   <tr><td>{@link Angle}</td>           <td>{@link AngleFormat}</td>                              <td></td></tr>
+ *   <tr><td>{@link Date}</td>            <td>{@link DateFormat}</td>                               <td>Timezone specified by {@link #getTimeZone()}.</td></tr>
+ *   <tr><td>{@link Number}</td>          <td>{@link NumberFormat}</td>                             <td></td></tr>
+ *   <tr><td>{@link Unit}</td>            <td>{@link UnitFormat}</td>                               <td></td></tr>
+ *   <tr><td>{@link Range}</td>           <td>{@link RangeFormat}</td>                              <td></td></tr>
+ *   <tr><td>{@link Class}</td>           <td>(internal)</td>                                       <td></td></tr>
+ * </table>
+ *
+ * <div class="section">Sources and destinations</div>
+ * Since {@code CompoundFormat} may work on larger texts than the usual {@code Format} classes,
  * it defines {@code parse} and {@code format} methods working with arbitrary {@link CharSequence}
  * and {@link Appendable} instances. The standard {@code Format} methods redirect to the above-cited
- * methods.</p>
- *
- * <p>The abstract methods to be defined by subclasses are:</p>
+ * methods.
  *
+ * <div class="section">Sub-classing</div>
+ * The abstract methods to be defined by subclasses are:
  * <ul>
- *   <li>{@link #getValueType()} returns the {@code <T>} class or a subclass.</li>
- *   <li>{@link #parse(CharSequence, ParsePosition)} may throws {@code ParseException}.</li>
- *   <li>{@link #format(Object, Appendable)} may throws {@code IOException}.</li>
+ *   <li>{@link #getValueType()}</li>
+ *   <li>{@link #format(Object, Appendable)}</li>
+ *   <li>{@link #parse(CharSequence, ParsePosition)}</li>
  * </ul>
  *
  * <div class="note"><b>API note:</b>
- * In the standard {@link Format} class, the {@code parse} methods either accept a {@link ParsePosition} argument
+ * in the standard {@link Format} class, the {@code parse} methods either accept a {@link ParsePosition} argument
  * and returns {@code null} on error, or does not take position argument and throws a {@link ParseException} on error.
  * In this {@code CompoundFormat} class, the {@code parse} method both takes a {@code ParsePosition} argument and
  * throws a {@code ParseException} on error. This allows both substring parsing and more accurate exception message
  * in case of error.</div>
  *
- * @param <T> The base type of objects parsed and formatted by this class.
+ * @param  <T>  the base type of objects parsed and formatted by this class.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
@@ -171,7 +191,7 @@ public abstract class CompoundFormat<T>
      * @return the timezone used for this format, or UTC for unlocalized format.
      */
     public TimeZone getTimeZone() {
-        return timezone != null ? (TimeZone) timezone.clone() : TimeZone.getTimeZone(UTC);
+        return (timezone != null) ? (TimeZone) timezone.clone() : TimeZone.getTimeZone(UTC);
     }
 
     /**
@@ -207,24 +227,30 @@ public abstract class CompoundFormat<T>
      *   <li>The {@code pos} index is left unchanged</li>
      *   <li>The {@code pos} {@linkplain ParsePosition#getErrorIndex() error index}
      *       is set to the beginning of the unparsable character sequence.</li>
-     *   <li>A {@code ParseException} is thrown with an
-     *       {@linkplain ParseException#getErrorOffset() error offset} relative to the above-cited
-     *       {@code pos} error index. Consequently the exact error location is <var>{@code pos}
-     *       error index</var> + <var>{@code ParseException} error offset</var>.</li>
+     *   <li>One of the following actions is taken (at implementation choice):
+     *     <ul>
+     *       <li>this method returns {@code null}, or</li>
+     *       <li>a {@code ParseException} is thrown with an {@linkplain ParseException#getErrorOffset() error offset}
+     *           set to the index of the first unparsable character.</li>
+     *     </ul>
+     *   </li>
      * </ul>
      *
-     * <div class="note"><b>Example:</b>
-     * If parsing of the {@code "30.0 40,0"} coordinate fails on the coma in the last number, then the {@code pos}
-     * error index will be set to 5 (the beginning of the {@code "40.0"} character sequence) while the
-     * {@link ParseException} error offset will be set to 2 (the coma position relative the beginning
-     * of the {@code "40.0"} character sequence).</div>
-     *
-     * This error offset policy is a consequence of the compound nature of {@code CompoundFormat},
-     * since the exception may have been produced by a call to {@link Format#parseObject(String)}.
+     * <div class="note"><b>Note:</b>
+     * if a {@code ParseException} is thrown, its error offset is usually the same than the {@code ParsePosition}
+     * error index, but implementations are free to adopt a slightly different policy. For example
+     * if parsing of the {@code "30.0 40,0"} coordinate fails on the coma in the last number, then the {@code pos}
+     * {@linkplain ParsePosition#getErrorIndex() error index} may be set to 5 (the beginning of the {@code "40.0"}
+     * character sequence) or to 7 (the coma position), depending on the implementation.</div>
+     *
+     * Most implementations never return {@code null}. However some implementations may choose to return {@code null}
+     * if they can determine that the given text is not a supported format and reserve {@code ParseException} for the
+     * cases where the text seems to be the expected format but contains a malformed element.
      *
      * @param  text  the character sequence for the object to parse.
      * @param  pos   the position where to start the parsing.
-     * @return the parsed object.
+     *               On return, the position where the parsing stopped or where an error occurred.
+     * @return the parsed object, or {@code null} if the text is not recognized.
      * @throws ParseException if an error occurred while parsing the object.
      */
     public abstract T parse(CharSequence text, ParsePosition pos) throws ParseException;
@@ -250,8 +276,6 @@ public abstract class CompoundFormat<T>
      * </ul>
      *
      * The default implementation delegates to {@link #parse(CharSequence, ParsePosition)}.
-     * In case of failure, the {@linkplain ParseException exception error offset} is added
-     * to the {@code pos} error index.
      *
      * @param  text  the string representation of the object to parse.
      * @param  pos   the position where to start the parsing.
@@ -262,7 +286,9 @@ public abstract class CompoundFormat<T>
         try {
             return parse(text, pos);
         } catch (ParseException e) {
-            pos.setErrorIndex(Math.max(pos.getIndex(), pos.getErrorIndex()) + e.getErrorOffset());
+            if (pos.getErrorIndex() < 0) {
+                pos.setErrorIndex(e.getErrorOffset());
+            }
             return null;
         }
     }
@@ -331,16 +357,16 @@ public abstract class CompoundFormat<T>
     @Override
     public StringBuffer format(final Object object, final StringBuffer toAppendTo, final FieldPosition pos) {
         final Class<? extends T> valueType = getValueType();
-        ArgumentChecks.ensureCanCast("tree", valueType, object);
+        ArgumentChecks.ensureCanCast("object", valueType, object);
         try {
             format(valueType.cast(object), toAppendTo);
         } catch (IOException e) {
             /*
              * Should never happen when writing into a StringBuffer, unless the user
-             * override the format(Object, Appendable) method. We do not rethrown an
+             * override the format(Object, Appendable) method.  We do not rethrow an
              * AssertionError because of this possibility.
              */
-            throw new BackingStoreException(e);
+            throw new UncheckedIOException(e);
         }
         return toAppendTo;
     }
@@ -393,18 +419,18 @@ public abstract class CompoundFormat<T>
      * Creates a new format to use for parsing and formatting values of the given type.
      * This method is invoked by {@link #getFormat(Class)} the first time that a format
      * is needed for the given type.
-     *
-     * <p>The default implementation creates the following formats:</p>
+     * The class given in argument can be any of the classes listed in the "Value type" column below:
      *
      * <table class="sis">
-     *   <caption>Supported formats by type</caption>
-     *   <tr><th>Value type</th>     <th>Format</th></tr>
-     *   <tr><td>{@link Angle}</td>  <td>{@link AngleFormat}</td></tr>
-     *   <tr><td>{@link Date}</td>   <td>{@link DateFormat}</td></tr>
-     *   <tr><td>{@link Number}</td> <td>{@link NumberFormat}</td></tr>
-     *   <tr><td>{@link Unit}</td>   <td>{@link UnitFormat}</td></tr>
-     *   <tr><td>{@link Range}</td>  <td>{@link RangeFormat}</td></tr>
-     *   <tr><td>{@link Class}</td>  <td>(internal)</td></tr>
+     *   <caption>Supported value types</caption>
+     *   <tr><th>Value type</th>              <th>Format type</th></tr>
+     *   <tr><td>{@link DirectPosition}</td>  <td>{@link org.apache.sis.geometry.CoordinateFormat}</td></tr>
+     *   <tr><td>{@link Angle}</td>           <td>{@link AngleFormat}</td></tr>
+     *   <tr><td>{@link Date}</td>            <td>{@link DateFormat}</td></tr>
+     *   <tr><td>{@link Number}</td>          <td>{@link NumberFormat}</td></tr>
+     *   <tr><td>{@link Unit}</td>            <td>{@link UnitFormat}</td></tr>
+     *   <tr><td>{@link Range}</td>           <td>{@link RangeFormat}</td></tr>
+     *   <tr><td>{@link Class}</td>           <td>(internal)</td></tr>
      * </table>
      *
      * Subclasses can override this method for adding more types, or for configuring the
@@ -447,6 +473,8 @@ public abstract class CompoundFormat<T>
             return new UnitFormat(locale);
         } else if (valueType == Range.class) {
             return new RangeFormat(locale);
+        } else if (valueType == DirectPosition.class) {
+            return MetadataServices.getInstance().createCoordinateFormat(locale, getTimeZone());
         } else if (valueType == Class.class) {
             return ClassFormat.INSTANCE;
         } else {

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -39,7 +39,7 @@ import org.apache.sis.internal.util.Loca
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 @SuppressWarnings("CloneableClassWithoutClone")   // Because this class does not contain field that need to be cloned.
@@ -125,15 +125,39 @@ final class DefaultFormat extends Format
      */
     @Override
     public Object parseObject(String source, final ParsePosition pos) {
-        final int length = source.length();
-        final int index = CharSequences.skipLeadingWhitespaces(source, pos.getIndex(), length);
-        source = source.substring(index, CharSequences.skipTrailingWhitespaces(source, index, length));
+        boolean exponent = false;
+        final int index = CharSequences.skipLeadingWhitespaces(source, pos.getIndex(), source.length());
+        int end;
+        for (end = index; end < source.length(); end++) {
+            final char c = source.charAt(end);
+            switch (c) {
+                default: {
+                    if (c >= '+' && c <= '9') continue;
+                    break;
+                    /*
+                     * ASCII characters in above range are +,-./0123456789
+                     * But the , and / characters are excluded by the case below.
+                     */
+                }
+                case ',': case '/': break;
+                case 'E': case 'e': {
+                    if (exponent) break;
+                    exponent = true;
+                    continue;
+                }
+            }
+            break;
+        }
+        source = source.substring(index, end);
+        final Object value;
         try {
-            return valueOf(source);
+            value = valueOf(source);
         } catch (NumberFormatException cause) {
             pos.setErrorIndex(index);
             return null;
         }
+        pos.setIndex(end);
+        return value;
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/io/TableAppender.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -42,8 +42,7 @@ import static org.apache.sis.util.Charac
  * <p>For example, the following code:</p>
  *
  * {@preformat java
- *     StringBuilder  buffer = new StringBuilder();
- *     TableAppender table  = new TableAppender(buffer);
+ *     TableAppender table = new TableAppender(System.out);
  *     table.nextLine('═');
  *     table.append("English\tFrench\tr.e.d.\n");
  *     table.nextLine('-');
@@ -70,7 +69,7 @@ import static org.apache.sis.util.Charac
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  *
  * @see org.apache.sis.util.collection.TreeTableFormat
@@ -264,6 +263,26 @@ public class TableAppender extends Appen
     }
 
     /**
+     * Creates a new table formatter writing in the given output with the specified column separator and border.
+     *
+     * @param out          the underlying stream or buffer to write to.
+     * @param leftBorder   string to write on the left side of the table.
+     * @param separator    string to write between columns.
+     * @param rightBorder  string to write on the right side of the table.
+     *
+     * @since 0.8
+     */
+    public TableAppender(final Appendable out, final String leftBorder, final String separator, final String rightBorder) {
+        super(out);
+        ArgumentChecks.ensureNonNull("leftBorder",  leftBorder);
+        ArgumentChecks.ensureNonNull("separator",   separator);
+        ArgumentChecks.ensureNonNull("rightBorder", rightBorder);
+        this.leftBorder      = leftBorder;
+        this.rightBorder     = rightBorder;
+        this.columnSeparator = separator;
+    }
+
+    /**
      * Writes a border or a corner to the underlying stream or buffer.
      *
      * @param  horizontalBorder -1 for left border, +1 for right border,  0 for center.
@@ -293,7 +312,7 @@ public class TableAppender extends Appen
         switch (horizontalBorder) {
             case -1: border = leftBorder;  break;
             case +1: border = rightBorder; break;
-            case  0: border = columnSeparator;   break;
+            case  0: border = columnSeparator; break;
             default: throw new AssertionError(horizontalBorder);
         }
         assert (verticalBorder >= -1) && (verticalBorder <= +1) : verticalBorder;
@@ -523,7 +542,9 @@ public class TableAppender extends Appen
     }
 
     /**
-     * Writes an horizontal separator.
+     * Writes an horizontal separator using the {@code '─'} character.
+     *
+     * @see #nextLine(char)
      */
     public void appendHorizontalSeparator() {
         if (currentColumn != 0 || buffer.length() != 0) {
@@ -595,6 +616,8 @@ public class TableAppender extends Appen
      *
      * @param  fill  character filling the rest of the line (default to whitespace).
      *              This character may be use as a row separator.
+     *
+     * @see #appendHorizontalSeparator()
      */
     public void nextLine(final char fill) {
         if (buffer.length() != 0) {

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/ArrayVector.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -210,7 +210,7 @@ abstract class ArrayVector<E extends Num
     /**
      * A vector backed by an array of type {@code double[]}.
      */
-    private static final class Doubles extends ArrayVector<Double> {
+    static final class Doubles extends ArrayVector<Double> {
         /** For cross-version compatibility. */
         private static final long serialVersionUID = -2900375382498345812L;
 

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/CompoundDirectPositions.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/CompoundDirectPositions.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/CompoundDirectPositions.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/CompoundDirectPositions.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -35,14 +35,14 @@ import org.apache.sis.util.resources.Err
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.8
  * @module
  */
 final class CompoundDirectPositions implements DirectPosition, Iterable<DirectPosition>, Iterator<DirectPosition> {
     /**
      * The arrays of ordinate values, for example (x[], y[], z[]).
      */
-    private final double[][] ordinates;
+    private final Vector[] ordinates;
 
     /**
      * Length of all ordinate values minus one.
@@ -57,15 +57,16 @@ final class CompoundDirectPositions impl
     /**
      * Wraps the given array of ordinate values.
      */
-    CompoundDirectPositions(final double[]... ordinates) {
+    CompoundDirectPositions(final Vector... ordinates) {
         this.ordinates = ordinates;
-        final int length = ordinates[0].length;
+        final int length = ordinates[0].size();
         for (int i=1; i<ordinates.length; i++) {
-            if (ordinates[i].length != length) {
+            if (ordinates[i].size() != length) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedArrayLengths));
             }
         }
-        last = length - 1;
+        last  = length - 1;
+        index = length;
     }
 
     /**
@@ -75,6 +76,9 @@ final class CompoundDirectPositions impl
      */
     @Override
     public Iterator<DirectPosition> iterator() {
+        if (hasNext()) {
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.CanIterateOnlyOnce));
+        }
         index = -1;
         return this;
     }
@@ -131,7 +135,7 @@ final class CompoundDirectPositions impl
      */
     @Override
     public double getOrdinate(final int dimension) {
-        return ordinates[dimension][index];
+        return ordinates[dimension].doubleValue(index);
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Line.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Line.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Line.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Line.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -22,6 +22,7 @@ import org.opengis.geometry.MismatchedDi
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
 
 import static java.lang.Double.*;
 
@@ -42,7 +43,7 @@ import static java.lang.Double.*;
  *
  * @author  Martin Desruisseaux (MPO, IRD)
  * @since   0.5
- * @version 0.5
+ * @version 0.8
  * @module
  *
  * @see Plane
@@ -194,6 +195,21 @@ public class Line implements Cloneable,
     }
 
     /**
+     * Sets this line from values of arbitrary {@code Number} type. This method is invoked by algorithms that
+     * may produce other kind of numbers (for example with different precision) than the usual {@code double}
+     * primitive type. The default implementation delegates to {@link #setEquation(double, double)}, but
+     * subclasses can override this method if they want to process other kind of numbers in a special way.
+     *
+     * @param  slope  the slope.
+     * @param  y0     the <var>y</var> value at <var>x</var> = 0.
+     *
+     * @since 0.8
+     */
+    public void setEquation(final Number slope, final Number y0) {
+        setEquation(slope.doubleValue(), y0.doubleValue());
+    }
+
+    /**
      * Sets a line through the specified points.
      * The line will continue toward infinity after the points.
      *
@@ -219,8 +235,10 @@ public class Line implements Cloneable,
     /**
      * Given a set of data points <var>x</var>[0 … <var>n</var>-1], <var>y</var>[0 … <var>n</var>-1],
      * fits them to a straight line <var>y</var> = <var>slope</var>⋅<var>x</var> + <var>y₀</var> in a
-     * least-squares senses. This method assume that the <var>x</var> values are precise and all uncertainty
-     * is in <var>y</var>.
+     * least-squares senses.
+     * This method assumes that the <var>x</var> values are precise and all uncertainty is in <var>y</var>.
+     *
+     * <p>The default implementation delegates to {@link #fit(Vector, Vector)}.</p>
      *
      * @param  x  vector of <var>x</var> values (independent variable).
      * @param  y  vector of <var>y</var> values (dependent variable).
@@ -229,30 +247,46 @@ public class Line implements Cloneable,
      * @throws IllegalArgumentException if <var>x</var> and <var>y</var> do not have the same length.
      */
     public double fit(final double[] x, final double[] y) {
-        // Do not invoke an overrideable method with our tricky iterable.
-        return doFit(new CompoundDirectPositions(x, y));
+        ArgumentChecks.ensureNonNull("x", x);
+        ArgumentChecks.ensureNonNull("y", y);
+        return fit(new ArrayVector.Doubles(x), new ArrayVector.Doubles(y));
     }
 
     /**
-     * Given a sequence of points, fits them to a straight line <var>y</var> = <var>slope</var>⋅<var>x</var> +
-     * <var>y₀</var> in a least-squares senses. This method assume that the <var>x</var> values are precise and
-     * all uncertainty is in <var>y</var>.
+     * Given a set of data points <var>x</var>[0 … <var>n</var>-1], <var>y</var>[0 … <var>n</var>-1],
+     * fits them to a straight line <var>y</var> = <var>slope</var>⋅<var>x</var> + <var>y₀</var> in a
+     * least-squares senses.
+     * This method assumes that the <var>x</var> values are precise and all uncertainty is in <var>y</var>.
      *
-     * <p>Points shall be two dimensional with ordinate values in the (<var>x</var>,<var>y</var>) order.
-     * {@link Double#NaN} ordinate values are ignored.</p>
+     * <p>The default implementation delegates to {@link #fit(Iterable)}.</p>
      *
-     * @param  points  the two-dimensional points.
+     * @param  x  vector of <var>x</var> values (independent variable).
+     * @param  y  vector of <var>y</var> values (dependent variable).
      * @return estimation of the correlation coefficient. The closer this coefficient is to +1 or -1, the better the fit.
-     * @throws MismatchedDimensionException if a point is not two-dimensional.
+     *
+     * @throws IllegalArgumentException if <var>x</var> and <var>y</var> do not have the same length.
+     *
+     * @since 0.8
      */
-    public double fit(final Iterable<? extends DirectPosition> points) {
-        return doFit(points);
+    public double fit(final Vector x, final Vector y) {
+        ArgumentChecks.ensureNonNull("x", x);
+        ArgumentChecks.ensureNonNull("y", y);
+        return fit(new CompoundDirectPositions(x, y));
     }
 
     /**
-     * Implementation of public {@code fit(…)} methods.
+     * Given a sequence of points, fits them to a straight line
+     * <var>y</var> = <var>slope</var>⋅<var>x</var> + <var>y₀</var> in a least-squares senses.
+     * Points shall be two dimensional with ordinate values in the (<var>x</var>,<var>y</var>) order.
+     * This method assumes that the <var>x</var> values are precise and all uncertainty is in <var>y</var>.
+     * {@link Double#NaN} ordinate values are ignored.
+     *
+     * @param  points  the two-dimensional points.
+     * @return estimation of the correlation coefficient. The closer this coefficient is to +1 or -1, the better the fit.
+     * @throws MismatchedDimensionException if a point is not two-dimensional.
      */
-    private double doFit(final Iterable<? extends DirectPosition> points) {
+    public double fit(final Iterable<? extends DirectPosition> points) {
+        ArgumentChecks.ensureNonNull("points", points);
         int i = 0, n = 0;
         final DoubleDouble mean_x = new DoubleDouble();
         final DoubleDouble mean_y = new DoubleDouble();
@@ -325,7 +359,7 @@ public class Line implements Cloneable,
         b.multiply(a);
         b.negate();
         b.add(mean_y);          // y₀ = mean_y - mean_x * slope
-        setEquation(a.value, b.value);
+        setEquation(a, b);
         /*
          * Compute the correlation coefficient:
          * mean_xy / sqrt(mean_x2 * (mean_y2 - mean_y * mean_y))

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/Plane.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -16,12 +16,14 @@
  */
 package org.apache.sis.math;
 
+import java.util.Iterator;
 import java.io.Serializable;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
 
 import static java.lang.Math.abs;
 import static java.lang.Math.sqrt;
@@ -44,7 +46,7 @@ import static java.lang.Math.ulp;
  * @author  Martin Desruisseaux (MPO, IRD)
  * @author  Howard Freeland (MPO, for algorithmic inspiration)
  * @since   0.5
- * @version 0.5
+ * @version 0.8
  * @module
  *
  * @see Line
@@ -211,13 +213,28 @@ public class Plane implements Cloneable,
     }
 
     /**
+     * Sets this plane from values of arbitrary {@code Number} type. This method is invoked by algorithms that
+     * may produce other kind of numbers (for example with different precision) than the usual {@code double}
+     * primitive type. The default implementation delegates to {@link #setEquation(double, double, double)},
+     * but subclasses can override this method if they want to process other kind of numbers in a special way.
+     *
+     * @param sx  the slope along the <var>x</var> values.
+     * @param sy  the slope along the <var>y</var> values.
+     * @param z0  the <var>z</var> value at (<var>x</var>,<var>y</var>) = (0,0).
+     *
+     * @since 0.8
+     */
+    public void setEquation(final Number sx, final Number sy, final Number z0) {
+        setEquation(sx.doubleValue(), sy.doubleValue(), z0.doubleValue());
+    }
+
+    /**
      * Computes the plane's coefficients from the given ordinate values.
-     * This method uses a linear regression in the least-square sense,
-     * with the assumption that the (<var>x</var>,<var>y</var>) values are precise
-     * and all uncertainty is in <var>z</var>.
+     * This method uses a linear regression in the least-square sense, with the assumption that
+     * the (<var>x</var>,<var>y</var>) values are precise and all uncertainty is in <var>z</var>.
+     * {@link Double#NaN} values are ignored. The result is undetermined if all points are colinear.
      *
-     * <p>{@link Double#NaN} values are ignored.
-     * The result is undetermined if all points are colinear.</p>
+     * <p>The default implementation delegates to {@link #fit(Vector, Vector, Vector)}.</p>
      *
      * @param  x  vector of <var>x</var> coordinates.
      * @param  y  vector of <var>y</var> coordinates.
@@ -226,154 +243,313 @@ public class Plane implements Cloneable,
      * @throws IllegalArgumentException if <var>x</var>, <var>y</var> and <var>z</var> do not have the same length.
      */
     public double fit(final double[] x, final double[] y, final double[] z) {
-        // Do not invoke an overrideable method with our tricky iterable.
-        return fit(new CompoundDirectPositions(x, y, z), true, true, true);
+        ArgumentChecks.ensureNonNull("x", x);
+        ArgumentChecks.ensureNonNull("y", y);
+        ArgumentChecks.ensureNonNull("z", z);
+        return fit(new ArrayVector.Doubles(x), new ArrayVector.Doubles(y), new ArrayVector.Doubles(z));
+    }
+
+    /**
+     * Computes the plane's coefficients from the given ordinate values.
+     * This method uses a linear regression in the least-square sense, with the assumption that
+     * the (<var>x</var>,<var>y</var>) values are precise and all uncertainty is in <var>z</var>.
+     * {@link Double#NaN} values are ignored. The result is undetermined if all points are colinear.
+     *
+     * <p>The default implementation delegates to {@link #fit(Iterable)}.</p>
+     *
+     * @param  x  vector of <var>x</var> coordinates.
+     * @param  y  vector of <var>y</var> coordinates.
+     * @param  z  vector of <var>z</var> values.
+     * @return an estimation of the Pearson correlation coefficient.
+     * @throws IllegalArgumentException if <var>x</var>, <var>y</var> and <var>z</var> do not have the same length.
+     *
+     * @since 0.8
+     */
+    public double fit(final Vector x, final Vector y, final Vector z) {
+        ArgumentChecks.ensureNonNull("x", x);
+        ArgumentChecks.ensureNonNull("y", y);
+        ArgumentChecks.ensureNonNull("z", z);
+        return fit(new CompoundDirectPositions(x, y, z));
+    }
+
+    /**
+     * Computes the plane's coefficients from values distributed on a regular grid. Invoking this method
+     * is equivalent (except for NaN handling) to invoking {@link #fit(Vector, Vector, Vector)} where all
+     * vectors have a length of {@code nx} × {@code ny} and the <var>x</var> and <var>y</var> vectors have
+     * the following content:
+     *
+     * <blockquote>
+     * <table class="compact" summary="x and y vector content">
+     *   <tr>
+     *     <th><var>x</var> vector</th>
+     *     <th><var>y</var> vector</th>
+     *   </tr><tr>
+     *     <td>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *       …<br>
+     *       0 1 2 3 4 5 … n<sub>x</sub>-1<br>
+     *     </td><td>
+     *       0 0 0 0 0 0 … 0<br>
+     *       1 1 1 1 1 1 … 1<br>
+     *       2 2 2 2 2 2 … 2<br>
+     *       …<br>
+     *       n<sub>y</sub>-1 n<sub>y</sub>-1 n<sub>y</sub>-1 … n<sub>y</sub>-1<br>
+     *     </td>
+     *   </tr>
+     * </table>
+     * </blockquote>
+     *
+     * This method uses a linear regression in the least-square sense, with the assumption that
+     * the (<var>x</var>,<var>y</var>) values are precise and all uncertainty is in <var>z</var>.
+     * The result is undetermined if all points are colinear.
+     *
+     * @param  nx  number of columns.
+     * @param  ny  number of rows.
+     * @param  z   values of a matrix of {@code nx} columns by {@code ny} rows organized in a row-major fashion.
+     * @return an estimation of the Pearson correlation coefficient.
+     * @throws IllegalArgumentException if <var>z</var> does not have the expected length or if a <var>z</var>
+     *         value is {@link Double#NaN}.
+     *
+     * @since 0.8
+     */
+    public double fit(final int nx, final int ny, final Vector z) {
+        ArgumentChecks.ensureStrictlyPositive("nx", nx);
+        ArgumentChecks.ensureStrictlyPositive("ny", ny);
+        ArgumentChecks.ensureNonNull("z", z);
+        final int length = Math.multiplyExact(nx, ny);
+        if (z.size() != length) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, length, z.size()));
+        }
+        final Fit r = new Fit(nx, ny, z);
+        r.resolve();
+        final double p = r.correlation(nx, length, z, null);
+        setEquation(r.sx, r.sy, r.z0);
+        return p;
     }
 
     /**
      * Computes the plane's coefficients from the given sequence of points.
-     * This method uses a linear regression in the least-square sense,
-     * with the assumption that the (<var>x</var>,<var>y</var>) values are precise
-     * and all uncertainty is in <var>z</var>.
-     *
-     * <p>Points shall be three dimensional with ordinate values in the (<var>x</var>,<var>y</var>,<var>z</var>) order.
-     * {@link Double#NaN} ordinate values are ignored.
-     * The result is undetermined if all points are colinear.</p>
+     * This method uses a linear regression in the least-square sense, with the assumption that
+     * the (<var>x</var>,<var>y</var>) values are precise and all uncertainty is in <var>z</var>.
+     * Points shall be three dimensional with ordinate values in the (<var>x</var>,<var>y</var>,<var>z</var>) order.
+     * {@link Double#NaN} values are ignored. The result is undetermined if all points are colinear.
      *
      * @param  points  the three-dimensional points.
      * @return an estimation of the Pearson correlation coefficient.
      * @throws MismatchedDimensionException if a point is not three-dimensional.
      */
     public double fit(final Iterable<? extends DirectPosition> points) {
-        return fit(points, true, true, true);
+        ArgumentChecks.ensureNonNull("points", points);
+        final Fit r = new Fit(points);
+        r.resolve();
+        final double p = r.correlation(0, 0, null, points.iterator());
+        /*
+         * Store the result only when we are done, so we have a "all or nothing" behavior.
+         * We invoke the setEquation(sx, sy, z₀) method in case the user override it.
+         */
+        setEquation(r.sx, r.sy, r.z0);
+        return p;
     }
 
     /**
-     * Implementation of public {@code fit(…)} methods.
-     * This method needs to iterate over the points two times:
+     * Computes the plane coefficients. This class needs to iterate over the points two times:
      * one for computing the coefficients, and one for the computing the Pearson coefficient.
      * The second pass can also opportunistically checks if some small coefficients can be replaced by zero.
      */
-    private double fit(final Iterable<? extends DirectPosition> points,
-            boolean detectZeroSx, boolean detectZeroSy, boolean detectZeroZ0)
-    {
-        int i = 0, n = 0;
-        final DoubleDouble sum_x  = new DoubleDouble();
-        final DoubleDouble sum_y  = new DoubleDouble();
-        final DoubleDouble sum_z  = new DoubleDouble();
-        final DoubleDouble sum_xx = new DoubleDouble();
-        final DoubleDouble sum_yy = new DoubleDouble();
-        final DoubleDouble sum_xy = new DoubleDouble();
-        final DoubleDouble sum_zx = new DoubleDouble();
-        final DoubleDouble sum_zy = new DoubleDouble();
-        final DoubleDouble xx     = new DoubleDouble();
-        final DoubleDouble yy     = new DoubleDouble();
-        final DoubleDouble xy     = new DoubleDouble();
-        final DoubleDouble zx     = new DoubleDouble();
-        final DoubleDouble zy     = new DoubleDouble();
-        for (final DirectPosition p : points) {
-            final int dimension = p.getDimension();
-            if (dimension != DIMENSION) {
-                throw new MismatchedDimensionException(Errors.format(
-                        Errors.Keys.MismatchedDimension_3, "points[" + i + ']', DIMENSION, dimension));
+    private static final class Fit {
+        private final DoubleDouble sum_x  = new DoubleDouble();
+        private final DoubleDouble sum_y  = new DoubleDouble();
+        private final DoubleDouble sum_z  = new DoubleDouble();
+        private final DoubleDouble sum_xx = new DoubleDouble();
+        private final DoubleDouble sum_yy = new DoubleDouble();
+        private final DoubleDouble sum_xy = new DoubleDouble();
+        private final DoubleDouble sum_zx = new DoubleDouble();
+        private final DoubleDouble sum_zy = new DoubleDouble();
+        private final DoubleDouble xx     = new DoubleDouble();
+        private final DoubleDouble yy     = new DoubleDouble();
+        private final DoubleDouble xy     = new DoubleDouble();
+        private final DoubleDouble zx     = new DoubleDouble();
+        private final DoubleDouble zy     = new DoubleDouble();
+        private final int n;
+
+        /** Solution of the plane equation. */ DoubleDouble sx, sy, z0;
+
+        /**
+         * Computes the values of all {@code sum_*} fields from randomly distributed points.
+         * Value of all other fields are undetermined..
+         */
+        Fit(final Iterable<? extends DirectPosition> points) {
+            int i = 0, n = 0;
+            for (final DirectPosition p : points) {
+                final int dimension = p.getDimension();
+                if (dimension != DIMENSION) {
+                    throw new MismatchedDimensionException(Errors.format(
+                            Errors.Keys.MismatchedDimension_3, "points[" + i + ']', DIMENSION, dimension));
+                }
+                i++;
+                final double x = p.getOrdinate(0); if (Double.isNaN(x)) continue;
+                final double y = p.getOrdinate(1); if (Double.isNaN(y)) continue;
+                final double z = p.getOrdinate(2); if (Double.isNaN(z)) continue;
+                xx.setToProduct(x, x);
+                yy.setToProduct(y, y);
+                xy.setToProduct(x, y);
+                zx.setToProduct(z, x);
+                zy.setToProduct(z, y);
+                sum_x .add(x );
+                sum_y .add(y );
+                sum_z .add(z );
+                sum_xx.add(xx);
+                sum_yy.add(yy);
+                sum_xy.add(xy);
+                sum_zx.add(zx);
+                sum_zy.add(zy);
+                n++;
             }
-            i++;
-            final double x = p.getOrdinate(0); if (Double.isNaN(x)) continue;
-            final double y = p.getOrdinate(1); if (Double.isNaN(y)) continue;
-            final double z = p.getOrdinate(2); if (Double.isNaN(z)) continue;
-            xx.setToProduct(x, x);
-            yy.setToProduct(y, y);
-            xy.setToProduct(x, y);
-            zx.setToProduct(z, x);
-            zy.setToProduct(z, y);
-            sum_x.add(x);
-            sum_y.add(y);
-            sum_z.add(z);
-            sum_xx.add(xx);
-            sum_yy.add(yy);
-            sum_xy.add(xy);
-            sum_zx.add(zx);
-            sum_zy.add(zy);
-            n++;
+            this.n = n;
         }
-        /*
-         *    ( sum_zx - sum_z*sum_x )  =  sx*(sum_xx - sum_x*sum_x) + sy*(sum_xy - sum_x*sum_y)
-         *    ( sum_zy - sum_z*sum_y )  =  sx*(sum_xy - sum_x*sum_y) + sy*(sum_yy - sum_y*sum_y)
-         */
-        zx.setFrom(sum_x); zx.divide(-n, 0); zx.multiply(sum_z); zx.add(sum_zx);    // zx = sum_zx - sum_z*sum_x/n
-        zy.setFrom(sum_y); zy.divide(-n, 0); zy.multiply(sum_z); zy.add(sum_zy);    // zy = sum_zy - sum_z*sum_y/n
-        xx.setFrom(sum_x); xx.divide(-n, 0); xx.multiply(sum_x); xx.add(sum_xx);    // xx = sum_xx - sum_x*sum_x/n
-        xy.setFrom(sum_y); xy.divide(-n, 0); xy.multiply(sum_x); xy.add(sum_xy);    // xy = sum_xy - sum_x*sum_y/n
-        yy.setFrom(sum_y); yy.divide(-n, 0); yy.multiply(sum_y); yy.add(sum_yy);    // yy = sum_yy - sum_y*sum_y/n
-        /*
-         * den = (xy*xy - xx*yy)
-         */
-        final DoubleDouble tmp = new DoubleDouble(xx); tmp.multiply(yy);
-        final DoubleDouble den = new DoubleDouble(xy); den.multiply(xy);
-        den.subtract(tmp);
-        /*
-         * sx = (zy*xy - zx*yy) / den
-         * sy = (zx*xy - zy*xx) / den
-         * z₀ = (sum_z - (sx*sum_x + sy*sum_y)) / n
-         */
-        final DoubleDouble sx = new DoubleDouble(zy); sx.multiply(xy); tmp.setFrom(zx); tmp.multiply(yy); sx.subtract(tmp); sx.divide(den);
-        final DoubleDouble sy = new DoubleDouble(zx); sy.multiply(xy); tmp.setFrom(zy); tmp.multiply(xx); sy.subtract(tmp); sy.divide(den);
-        final DoubleDouble z0 = new DoubleDouble(sy);
-        z0.multiply(sum_y);
-        tmp.setFrom(sx);
-        tmp.multiply(sum_x);
-        tmp.add(z0);
-        z0.setFrom(sum_z);
-        z0.subtract(tmp);
-        z0.divide(n, 0);
-        /*
-         * At this point, the model is computed. Now computes an estimation of the Pearson
-         * correlation coefficient. Note that both the z array and the z computed from the
-         * model have the same average, called sum_z below (the name is not true anymore).
-         *
-         * We do not use double-double arithmetic here since the Pearson coefficient is
-         * for information purpose (quality estimation).
+
+        /**
+         * Computes the values of all {@code sum_*} fields from the <var>z</var> values on a regular grid.
+         * Value of all other fields are undetermined..
          */
-        final double mean_x = sum_x.value / n;
-        final double mean_y = sum_y.value / n;
-        final double mean_z = sum_z.value / n;
-        final double offset = abs((sx.value * mean_x + sy.value * mean_y) + z0.value); // Offsetted z₀ - see comment before usage.
-        double sum_ds2 = 0, sum_dz2 = 0, sum_dsz = 0;
-        for (final DirectPosition p : points) {
-            final double x = (p.getOrdinate(0) - mean_x) * sx.value;
-            final double y = (p.getOrdinate(1) - mean_y) * sy.value;
-            final double z = (p.getOrdinate(2) - mean_z);
-            final double s = x + y;
-            if (!Double.isNaN(s) && !Double.isNaN(z)) {
-                sum_ds2 += s * s;
-                sum_dz2 += z * z;
-                sum_dsz += s * z;
-            }
+        Fit(final int nx, final int ny, final Vector vz) {
             /*
-             * Algorithm for detecting if a coefficient should be zero:
-             * If for every points given by the user, adding (sx⋅x) in (sx⋅x + sy⋅y + z₀) does not make any difference
-             * because (sx⋅x) is smaller than 1 ULP of (sy⋅y + z₀), then it is not worth adding it and  sx  can be set
-             * to zero. The same rational applies to (sy⋅y) and z₀.
-             *
-             * Since we work with differences from the means, the  z = sx⋅x + sy⋅y + z₀  equation can be rewritten as:
+             * Computes the sum of x, y and z values. Computes also the sum of x², y², x⋅y, z⋅x and z⋅y values.
+             * When possible, we will avoid to compute the sum inside the loop and use the following identities
+             * instead:
              *
-             *     Δz = sx⋅Δx + sy⋅Δy + (sx⋅mx + sy⋅my + z₀ - mz)    where the term between (…) is close to zero.
+             *           1 + 2 + 3 ... + n    =    n⋅(n+1)/2              (arithmetic series)
+             *        1² + 2² + 3² ... + n²   =    n⋅(n+0.5)⋅(n+1)/3
              *
-             * The check for (sx⋅Δx) and (sy⋅Δy) below ignore the (…) term since it is close to zero.
-             * The check for  z₀  is derived from an equation without the  -mz  term.
+             * Note that for exclusive upper bound, we need to replace n by n-1 in above formulas.
              */
-            if (detectZeroSx && abs(x) >= ulp(y * ZERO_THRESHOLD)) detectZeroSx = false;
-            if (detectZeroSy && abs(y) >= ulp(x * ZERO_THRESHOLD)) detectZeroSy = false;
-            if (detectZeroZ0 && offset >= ulp(s * ZERO_THRESHOLD)) detectZeroZ0 = false;
+            int n = 0;
+            for (int y=0; y<ny; y++) {
+                for (int x=0; x<nx; x++) {
+                    final double z = vz.doubleValue(n);
+                    if (Double.isNaN(z)) {
+                        throw new IllegalArgumentException(Errors.format(Errors.Keys.NotANumber_1, "z[" + n + ']'));
+                    }
+                    zx.setToProduct(z, x);
+                    zy.setToProduct(z, y);
+                    sum_z .add(z );
+                    sum_zx.add(zx);
+                    sum_zy.add(zy);
+                    n++;
+                }
+            }
+            sum_x .value = n/2d;  sum_x .multiply(nx-1,   0);                     // Division by 2 is exact.
+            sum_y .value = n/2d;  sum_y .multiply(ny-1,   0);
+            sum_xx.value = n;     sum_xx.multiply(nx-0.5, 0); sum_xx.multiply(nx-1, 0); sum_xx.divide(3, 0);
+            sum_yy.value = n;     sum_yy.multiply(ny-0.5, 0); sum_yy.multiply(ny-1, 0); sum_yy.divide(3, 0);
+            sum_xy.value = n/4d;  sum_xy.multiply(ny-1,   0); sum_xy.multiply(nx-1, 0);
+            this.n = n;
         }
-        /*
-         * Store the result only when we are done, so we have a "all or nothing" behavior.
-         * We invoke the setEquation(sx, sy, z₀) method in case the user override it.
+
+        /**
+         * Computes the {@link #sx}, {@link #sy} and {@link #z0} values using the sums computed by the constructor.
          */
-        setEquation(detectZeroSx ? 0 : sx.value,
-                    detectZeroSy ? 0 : sy.value,
-                    detectZeroZ0 ? 0 : z0.value);
-        return Math.min(sum_dsz / sqrt(sum_ds2 * sum_dz2), 1);
+        private void resolve() {
+            /*
+             *    ( sum_zx - sum_z⋅sum_x )  =  sx⋅(sum_xx - sum_x⋅sum_x) + sy⋅(sum_xy - sum_x⋅sum_y)
+             *    ( sum_zy - sum_z⋅sum_y )  =  sx⋅(sum_xy - sum_x⋅sum_y) + sy⋅(sum_yy - sum_y⋅sum_y)
+             */
+            zx.setFrom(sum_x); zx.divide(-n, 0); zx.multiply(sum_z); zx.add(sum_zx);    // zx = sum_zx - sum_z⋅sum_x/n
+            zy.setFrom(sum_y); zy.divide(-n, 0); zy.multiply(sum_z); zy.add(sum_zy);    // zy = sum_zy - sum_z⋅sum_y/n
+            xx.setFrom(sum_x); xx.divide(-n, 0); xx.multiply(sum_x); xx.add(sum_xx);    // xx = sum_xx - sum_x⋅sum_x/n
+            xy.setFrom(sum_y); xy.divide(-n, 0); xy.multiply(sum_x); xy.add(sum_xy);    // xy = sum_xy - sum_x⋅sum_y/n
+            yy.setFrom(sum_y); yy.divide(-n, 0); yy.multiply(sum_y); yy.add(sum_yy);    // yy = sum_yy - sum_y⋅sum_y/n
+            /*
+             * den = (xy⋅xy - xx⋅yy)
+             */
+            final DoubleDouble tmp = new DoubleDouble(xx); tmp.multiply(yy);
+            final DoubleDouble den = new DoubleDouble(xy); den.multiply(xy);
+            den.subtract(tmp);
+            /*
+             * sx = (zy⋅xy - zx⋅yy) / den
+             * sy = (zx⋅xy - zy⋅xx) / den
+             * z₀ = (sum_z - (sx⋅sum_x + sy⋅sum_y)) / n
+             */
+            sx = new DoubleDouble(zy); sx.multiply(xy); tmp.setFrom(zx); tmp.multiply(yy); sx.subtract(tmp); sx.divide(den);
+            sy = new DoubleDouble(zx); sy.multiply(xy); tmp.setFrom(zy); tmp.multiply(xx); sy.subtract(tmp); sy.divide(den);
+            z0 = new DoubleDouble(sy);
+            z0.multiply(sum_y);
+            tmp.setFrom(sx);
+            tmp.multiply(sum_x);
+            tmp.add(z0);
+            z0.setFrom(sum_z);
+            z0.subtract(tmp);
+            z0.divide(n, 0);
+        }
+
+        /**
+         * Computes an estimation of the Pearson correlation coefficient. We do not use double-double arithmetic
+         * here since the Pearson coefficient is for information purpose (quality estimation).
+         *
+         * <p>Only one of ({@code nx}, {@code length}, {@code z}) tuple or {@code points} argument should be non-null.</p>
+         */
+        double correlation(final int nx, final int length, final Vector vz,
+                           final Iterator<? extends DirectPosition> points)
+        {
+            boolean detectZeroSx = true;
+            boolean detectZeroSy = true;
+            boolean detectZeroZ0 = true;
+            final double sx     = this.sx.value;
+            final double sy     = this.sy.value;
+            final double z0     = this.z0.value;
+            final double mean_x = sum_x.value / n;
+            final double mean_y = sum_y.value / n;
+            final double mean_z = sum_z.value / n;
+            final double offset = abs((sx * mean_x + sy * mean_y) + z0);    // Offsetted z₀ - see comment before usage.
+            int index = 0;
+            double sum_ds2 = 0, sum_dz2 = 0, sum_dsz = 0;
+            for (;;) {
+                double x, y, z;
+                if (vz != null) {
+                    if (index >= length) break;
+                    x = index % nx;
+                    y = index / nx;
+                    z = vz.doubleValue(index++);
+                } else {
+                    if (!points.hasNext()) break;
+                    final DirectPosition p = points.next();
+                    x = p.getOrdinate(0);
+                    y = p.getOrdinate(1);
+                    z = p.getOrdinate(2);
+                }
+                x = (x - mean_x) * sx;
+                y = (y - mean_y) * sy;
+                z = (z - mean_z);
+                final double s = x + y;
+                if (!Double.isNaN(s) && !Double.isNaN(z)) {
+                    sum_ds2 += s * s;
+                    sum_dz2 += z * z;
+                    sum_dsz += s * z;
+                }
+                /*
+                 * Algorithm for detecting if a coefficient should be zero:
+                 * If for every points given by the user, adding (sx⋅x) in (sx⋅x + sy⋅y + z₀) does not make any difference
+                 * because (sx⋅x) is smaller than 1 ULP of (sy⋅y + z₀), then it is not worth adding it and  sx  can be set
+                 * to zero. The same rational applies to (sy⋅y) and z₀.
+                 *
+                 * Since we work with differences from the means, the  z = sx⋅x + sy⋅y + z₀  equation can be rewritten as:
+                 *
+                 *     Δz = sx⋅Δx + sy⋅Δy + (sx⋅mx + sy⋅my + z₀ - mz)    where the term between (…) is close to zero.
+                 *
+                 * The check for (sx⋅Δx) and (sy⋅Δy) below ignore the (…) term since it is close to zero.
+                 * The check for  z₀  is derived from an equation without the  -mz  term.
+                 */
+                if (detectZeroSx && abs(x) >= ulp(y * ZERO_THRESHOLD)) detectZeroSx = false;
+                if (detectZeroSy && abs(y) >= ulp(x * ZERO_THRESHOLD)) detectZeroSy = false;
+                if (detectZeroZ0 && offset >= ulp(s * ZERO_THRESHOLD)) detectZeroZ0 = false;
+            }
+            if (detectZeroSx) this.sx.clear();
+            if (detectZeroSy) this.sy.clear();
+            if (detectZeroZ0) this.z0.clear();
+            return Math.min(sum_dsz / sqrt(sum_ds2 * sum_dz2), 1);
+        }
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/math/StatisticsFormat.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -31,6 +31,7 @@ import org.opengis.util.InternationalStr
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.io.TabularFormat;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.BackingStoreException;
 
@@ -40,16 +41,31 @@ import static java.lang.Math.*;
 /**
  * Formats a {@link Statistics} object.
  * By default, newly created {@code StatisticsFormat} instances will format statistical values
- * in a tabular format using spaces as the column separator. This default configuration matches
- * the {@link Statistics#toString()} format.
+ * in a tabular format using spaces as the column separator.
  *
- * <div class="section">Limitations</div>
- * The current implementation can only format statistics - parsing is not yet implemented.
+ * <div class="note"><b>Example:</b>
+ * {@preformat text
+ *     Number of values:     8726
+ *     Minimum value:       6.853
+ *     Maximum value:       8.259
+ *     Mean value:          7.421
+ *     Root Mean Square:    7.846
+ *     Standard deviation:  6.489
+ * }
+ * </div>
+ *
+ * <p><b>Limitations:</b></p>
+ * <ul>
+ *   <li>The current implementation can only format features — parsing is not yet implemented.</li>
+ *   <li>{@code StatisticsFormat}, like most {@code java.text.Format} subclasses, is not thread-safe.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
+ *
+ * @see Statistics#toString()
  */
 public class StatisticsFormat extends TabularFormat<Statistics> {
     /**
@@ -209,14 +225,15 @@ public class StatisticsFormat extends Ta
     }
 
     /**
-     * Not yet implemented.
+     * Not yet supported.
      *
      * @return currently never return.
-     * @throws ParseException currently never thrown.
+     * @throws ParseException currently always thrown.
      */
     @Override
     public Statistics parse(CharSequence text, ParsePosition pos) throws ParseException {
-        throw new UnsupportedOperationException();
+        throw new ParseException(Errors.getResources(getLocale())
+                .getString(Errors.Keys.UnsupportedOperation_1, "parse"), pos.getIndex());
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -27,6 +27,8 @@ import javax.measure.Quantity;
 import org.apache.sis.math.Fraction;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.resources.Errors;
 
 
@@ -57,7 +59,7 @@ import org.apache.sis.util.resources.Err
  * @version 0.8
  * @module
  */
-abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, Serializable {
+abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, LenientComparable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -270,16 +272,28 @@ abstract class AbstractUnit<Q extends Qu
     /**
      * Compares this unit with the given object for equality.
      *
-     * @param  other  the other object to compares with this unit, or {@code null}.
-     * @return {@code true} if the given object is equals to this unit.
+     * @param  other  the other object to compare with this unit, or {@code null}.
+     * @return {@code true} if the given object is equal to this unit.
      */
     @Override
-    public boolean equals(final Object other) {
-        if (other != null && other.getClass() == getClass()) {
-            final AbstractUnit<?> that = (AbstractUnit<?>) other;
-            return epsg == that.epsg && scope == that.scope && Objects.equals(symbol, that.symbol);
+    public final boolean equals(final Object other) {
+        return equals(other, ComparisonMode.STRICT);
+    }
+
+    /**
+     * Compares this unit with the given object for equality,
+     * optionally ignoring metadata and rounding errors.
+     */
+    @Override
+    public boolean equals(final Object other, final ComparisonMode mode) {
+        if (other == null || other.getClass() != getClass()) {
+            return false;
+        }
+        if (mode.isIgnoringMetadata()) {
+            return true;
         }
-        return false;
+        final AbstractUnit<?> that = (AbstractUnit<?>) other;
+        return epsg == that.epsg && scope == that.scope && Objects.equals(symbol, that.symbol);
     }
 
     /**
@@ -308,8 +322,11 @@ abstract class AbstractUnit<Q extends Qu
     /**
      * Returns {@code true} if the given Unicode code point is a valid character for a unit symbol.
      * Current implementation accepts letters, subscripts and the degree sign, but the set of legal
-     * characters may be expanded in any future SIS version. The most important goal is to avoid
-     * confusion with exponents and to detect where a unit symbol ends.
+     * characters may be expanded in any future SIS version (however it should never allow spaces).
+     * The goal is to avoid confusion with exponents and to detect where a unit symbol ends.
+     *
+     * <p>Space characters must be excluded from the set of legal characters because allowing them
+     * would make harder for {@link UnitFormat} to detect correctly where a unit symbol ends.</p>
      *
      * <p>Note that some units defined in the {@link Units} class break this rule. In particular,
      * some of those units contains superscripts or division sign. But the hard-coded symbols in

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Angle.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Angle.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Angle.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Angle.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -23,7 +23,16 @@ import java.util.FormattableFlags;
 import java.text.Format;
 import java.text.ParseException;
 import java.io.Serializable;
+import javax.measure.Unit;
+import javax.measure.IncommensurableException;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.internal.util.Utilities;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.Classes;
 
 import static java.lang.Double.doubleToLongBits;
 import static org.apache.sis.math.MathFunctions.isNegative;
@@ -58,7 +67,7 @@ import static org.apache.sis.math.MathFu
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  *
  * @see Latitude
@@ -132,6 +141,47 @@ public class Angle implements Comparable
     }
 
     /**
+     * Returns the angular value of the axis having the given direction.
+     * This helper method is used for subclass constructors expecting a {@link DirectPosition} argument.
+     *
+     * @param  position  the position from which to get an angular value.
+     * @param  positive  axis direction of positive values.
+     * @param  negative  axis direction of negative values.
+     * @return angular value in degrees.
+     * @throws IllegalArgumentException if the given coordinate it not associated to a CRS,
+     *         or if no axis oriented toward the given directions is found, or if that axis
+     *         does not use {@linkplain Units#isAngular angular units}.
+     */
+    static double valueOf(final DirectPosition position, final AxisDirection positive, final AxisDirection negative) {
+        final CoordinateReferenceSystem crs = position.getCoordinateReferenceSystem();
+        if (crs == null) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.UnspecifiedCRS));
+        }
+        final CoordinateSystem cs = crs.getCoordinateSystem();
+        final int dimension = cs.getDimension();
+        IncommensurableException cause = null;
+        for (int i=0; i<dimension; i++) {
+            final CoordinateSystemAxis axis = cs.getAxis(i);
+            final AxisDirection dir = axis.getDirection();
+            final boolean isPositive = dir.equals(positive);
+            if (isPositive || dir.equals(negative)) {
+                double value = position.getOrdinate(i);
+                if (!isPositive) value = -value;
+                final Unit<?> unit = axis.getUnit();
+                if (unit != Units.DEGREE) try {
+                    value = unit.getConverterToAny(Units.DEGREE).convert(value);
+                } catch (IncommensurableException e) {
+                    cause = e;
+                    break;
+                }
+                return value;
+            }
+        }
+        throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCRSType_1,
+                Classes.getLeafInterfaces(crs.getClass(), CoordinateReferenceSystem.class)[0]), cause);
+    }
+
+    /**
      * Returns the angle value in decimal degrees.
      *
      * @return the angle value in decimal degrees.

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.measure;
 
+import java.math.RoundingMode;
 import java.util.Objects;
 import java.util.Locale;
 import java.text.Format;
@@ -32,11 +33,8 @@ import org.apache.sis.util.ArgumentCheck
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.LocalizedParseException;
 
-import static java.lang.Math.abs;
-import static java.lang.Math.rint;
 import static java.lang.Double.NaN;
 import static java.lang.Double.isNaN;
-import static java.lang.Double.isInfinite;
 import static org.apache.sis.math.MathFunctions.pow10;
 import static org.apache.sis.math.MathFunctions.truncate;
 import static org.apache.sis.math.MathFunctions.isNegative;
@@ -120,12 +118,13 @@ import static org.apache.sis.math.Decima
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3
- * @version 0.4
+ * @version 0.8
  * @module
  *
  * @see Angle
  * @see Latitude
  * @see Longitude
+ * @see org.apache.sis.geometry.CoordinateFormat
  */
 public class AngleFormat extends Format implements Localized {
     /**
@@ -298,6 +297,13 @@ public class AngleFormat extends Format
                    secondsSuffix;
 
     /**
+     * The rounding mode, or {@code null} for the default mode (which is {@link RoundingMode#HALF_EVEN}).
+     *
+     * @see #getRoundingMode()
+     */
+    private RoundingMode roundingMode;
+
+    /**
      * {@code true} if the {@link #parse(String, ParsePosition)} method is allowed to fallback
      * on the build-in default symbols if the string to parse doesn't match the pattern.
      *
@@ -721,6 +727,68 @@ public class AngleFormat extends Format
     }
 
     /**
+     * Returns the rounding mode. Default value is {@link RoundingMode#HALF_EVEN}.
+     *
+     * @return the rounding mode.
+     *
+     * @see NumberFormat#getRoundingMode()
+     *
+     * @since 0.8
+     */
+    public RoundingMode getRoundingMode() {
+        return (roundingMode != null) ? roundingMode : RoundingMode.HALF_EVEN;
+    }
+
+    /**
+     * Sets the rounding mode to the specified value. The given mode can be one of the following:
+     *
+     * <table class="sis">
+     *   <caption>Supported rounding modes</caption>
+     *   <tr><th>Rounding mode</th>                             <th>Result</th></tr>
+     *   <tr><td>{@link RoundingMode#UP        UP}</td>         <td>Round away from zero.</td></tr>
+     *   <tr><td>{@link RoundingMode#DOWN      DOWN}</td>       <td>Round towards zero.</td></tr>
+     *   <tr><td>{@link RoundingMode#CEILING   CEILING}</td>    <td>Round towards positive infinity.</td></tr>
+     *   <tr><td>{@link RoundingMode#FLOOR     FLOOR}</td>      <td>Round towards negative infinity.</td></tr>
+     *   <tr><td>{@link RoundingMode#HALF_EVEN HALF_EVEN}</td>  <td>Round towards nearest neighbor.</td></tr>
+     * </table>
+     *
+     * The {@link RoundingMode#HALF_UP} and {@link RoundingMode#HALF_DOWN HALF_DOWN} values are not supported
+     * by the current {@code AngleFormat} implementation.
+     *
+     * @param  mode  the new rounding mode.
+     *
+     * @see NumberFormat#setRoundingMode(RoundingMode)
+     *
+     * @since 0.8
+     */
+    public void setRoundingMode(final RoundingMode mode) {
+        ArgumentChecks.ensureNonNull("mode", mode);
+        if (mode == RoundingMode.HALF_UP || mode == RoundingMode.HALF_DOWN) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedArgumentValue_1, mode));
+        }
+        roundingMode = mode;
+    }
+
+    /**
+     * Rounds the given value according current {@link #roundingMode}.
+     *
+     * @param  sign   the sign of the value to round.
+     * @param  value  the positive value to round.
+     * @return the rounded positive value.
+     */
+    private double round(final double sign, final double value) {
+        if (roundingMode != null) {
+            switch (roundingMode) {
+                case UP:      return Math.ceil (value);
+                case DOWN:    return Math.floor(value);
+                case CEILING: return Math.abs(Math.ceil (Math.copySign(value, sign)));
+                case FLOOR:   return Math.abs(Math.floor(Math.copySign(value, sign)));
+            }
+        }
+        return Math.rint(value);
+    }
+
+    /**
      * Returns the minimum number of digits allowed in the fraction portion of the last field.
      * This value can be set by the repetition of {@code 'd'}, {@code 'm'} or {@code 's'} symbol
      * in the pattern.
@@ -899,7 +967,7 @@ public class AngleFormat extends Format
     public StringBuffer format(final double angle, StringBuffer toAppendTo, final FieldPosition pos) {
         final int offset = toAppendTo.length();
         final int fieldPos = getField(pos);
-        if (isNaN(angle) || isInfinite(angle)) {
+        if (!Double.isFinite(angle)) {
             toAppendTo = numberFormat().format(angle, toAppendTo, dummyFieldPosition());
             if (fieldPos >= DEGREES_FIELD && fieldPos <= SECONDS_FIELD) {
                 pos.setBeginIndex(offset);
@@ -915,8 +983,11 @@ public class AngleFormat extends Format
         double minutes = NaN;
         double seconds = NaN;
         int maximumFractionDigits = fractionFieldWidth;
-        if (minutesFieldWidth != 0 && !isNaN(angle)) {
-            minutes = abs(degrees - (degrees = truncate(degrees))) * 60;
+        if (minutesFieldWidth == 0) {
+            final double p = pow10(maximumFractionDigits);
+            degrees = round(angle, degrees * p) / p;
+        } else {
+            minutes = Math.abs(degrees - (degrees = truncate(degrees))) * 60;
             /*
              * Limit the maximal number of fraction digits to the amount of significant digits for a 'double' value.
              * The intend is to avoid non-significant garbage that are pure artifacts from the conversion from base
@@ -928,13 +999,13 @@ public class AngleFormat extends Format
             final double p = pow10(maximumFractionDigits);
             if (secondsFieldWidth != 0) {
                 seconds = (minutes - (minutes = truncate(minutes))) * 60;
-                seconds = rint(seconds * p) / p;                            // Correction for rounding errors.
+                seconds = round(angle, seconds * p) / p;                     // Correction for rounding errors.
                 if (seconds >= 60) {                    // We do not expect > 60 (only == 60), but let be safe.
                     seconds = 0;
                     minutes++;
                 }
             } else {
-                minutes = rint(minutes * p) / p;        // Correction for rounding errors.
+                minutes = round(angle, minutes * p) / p;                     // Correction for rounding errors.
             }
             if (minutes >= 60) {                        // We do not expect > 60 (only == 60), but let be safe.
                 minutes = 0;
@@ -1115,7 +1186,7 @@ public class AngleFormat extends Format
     {
         try {
             showLeadingFields = true;
-            toAppendTo = format(abs(angle), toAppendTo, pos);
+            toAppendTo = format(Math.abs(angle), toAppendTo, pos);
         } finally {
             showLeadingFields = false;
         }
@@ -1758,8 +1829,8 @@ BigBoss:    switch (skipSuffix(source, p
     @Override
     public int hashCode() {
         return Objects.hash(degreesFieldWidth, minutesFieldWidth, secondsFieldWidth, fractionFieldWidth,
-                minimumFractionDigits, useDecimalSeparator, isFallbackAllowed, optionalFields, locale,
-                prefix, degreesSuffix, minutesSuffix, secondsSuffix) ^ (int) serialVersionUID;
+                minimumFractionDigits, useDecimalSeparator, isFallbackAllowed, optionalFields, roundingMode,
+                locale, prefix, degreesSuffix, minutesSuffix, secondsSuffix) ^ (int) serialVersionUID;
     }
 
     /**
@@ -1782,6 +1853,7 @@ BigBoss:    switch (skipSuffix(source, p
                    useDecimalSeparator   == cast.useDecimalSeparator   &&
                    isFallbackAllowed     == cast.isFallbackAllowed     &&
                    optionalFields        == cast.optionalFields        &&
+                   roundingMode          == cast.roundingMode          &&
                    Objects.equals(locale,        cast.locale)          &&
                    Objects.equals(prefix,        cast.prefix)          &&
                    Objects.equals(degreesSuffix, cast.degreesSuffix)   &&

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -20,6 +20,9 @@ import java.util.List;
 import java.util.ArrayList;
 import javax.measure.UnitConverter;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
+import org.apache.sis.util.Utilities;
 
 
 /**
@@ -30,7 +33,7 @@ import org.apache.sis.util.ArgumentCheck
  * @version 0.8
  * @module
  */
-final class ConcatenatedConverter extends AbstractConverter {
+final class ConcatenatedConverter extends AbstractConverter implements LenientComparable {
     /**
      * For cross-version compatibility.
      */
@@ -161,4 +164,17 @@ final class ConcatenatedConverter extend
         }
         return false;
     }
+
+    /**
+     * Compares this converter with the given object for equality, optionally ignoring rounding errors.
+     */
+    @Override
+    public boolean equals(final Object other, final ComparisonMode mode) {
+        if (other instanceof ConcatenatedConverter) {
+            final ConcatenatedConverter o = (ConcatenatedConverter) other;
+            return Utilities.deepEquals(c1, o.c1, mode) &&
+                   Utilities.deepEquals(c2, o.c2, mode);
+        }
+        return false;
+    }
 }

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -26,6 +26,8 @@ import javax.measure.IncommensurableExce
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Characters;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.math.Fraction;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.internal.util.Numerics;
@@ -439,19 +441,21 @@ final class ConventionalUnit<Q extends Q
     }
 
     /**
-     * Compares this unit with the given object for equality.
+     * Compares this unit with the given object for equality,
+     * optionally ignoring metadata and rounding errors.
      *
-     * @param  other  the other object to compares with this unit, or {@code null}.
-     * @return {@code true} if the given object is equals to this unit.
+     * @param  other  the other object to compare with this unit, or {@code null}.
+     * @return {@code true} if the given object is equal to this unit.
      */
     @Override
-    public boolean equals(final Object other) {
+    public boolean equals(final Object other, final ComparisonMode mode) {
         if (other == this) {
             return true;
         }
-        if (super.equals(other)) {
+        if (super.equals(other, mode)) {
             final ConventionalUnit<?> that = (ConventionalUnit<?>) other;
-            return target.equals(that.target) && toTarget.equals(that.toTarget);
+            return Utilities.deepEquals(target,   that.target,   mode) &&
+                   Utilities.deepEquals(toTarget, that.toTarget, mode);
         }
         return false;
     }

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.measure;
 
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.cs.AxisDirection;
+
 
 /**
  * A latitude angle in decimal degrees.
@@ -47,11 +50,12 @@ package org.apache.sis.measure;
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3
- * @version 0.4
+ * @version 0.8
  * @module
  *
  * @see Longitude
  * @see AngleFormat
+ * @see org.apache.sis.geometry.CoordinateFormat
  */
 public final class Latitude extends Angle {
     /**
@@ -103,6 +107,29 @@ public final class Latitude extends Angl
     }
 
     /**
+     * Constructs a newly allocated object containing the latitude value of the given position.
+     * For this method, the latitude value is defined as the angular value associated to the first axis
+     * oriented toward {@linkplain AxisDirection#NORTH North} or {@linkplain AxisDirection#SOUTH South}.
+     * Note that this is not necessarily the <cite>geodetic latitudes</cite> used in
+     * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS};
+     * it may also be <cite>geocentric latitudes</cite>.
+     *
+     * <p>If the axis direction is South, then the sign of the ordinate value is inverted.
+     * If the ordinate value uses another angular units than {@linkplain Units#DEGREE degrees},
+     * then a unit conversion is applied.</p>
+     *
+     * @param  position  the coordinate from which to extract the latitude value in degrees.
+     * @throws IllegalArgumentException if the given coordinate it not associated to a CRS,
+     *         or if no axis oriented toward North or South is found, or if that axis does
+     *         not use {@linkplain Units#isAngular angular units}.
+     *
+     * @since 0.8
+     */
+    public Latitude(final DirectPosition position) throws IllegalArgumentException {
+        super(valueOf(position, AxisDirection.NORTH, AxisDirection.SOUTH));
+    }
+
+    /**
      * Returns the hemisphere character for an angle of the given sign.
      * This is used only by {@link #toString()}, not by {@link AngleFormat}.
      */

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -25,6 +25,8 @@ import javax.measure.UnitConverter;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.StringBuilders;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.LenientComparable;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.math.Fraction;
@@ -49,7 +51,7 @@ import org.apache.sis.internal.util.Nume
  * @version 0.8
  * @module
  */
-final class LinearConverter extends AbstractConverter {
+final class LinearConverter extends AbstractConverter implements LenientComparable {
     /**
      * For cross-version compatibility.
      */
@@ -105,6 +107,11 @@ final class LinearConverter extends Abst
     private transient volatile BigDecimal scale10, offset10;
 
     /**
+     * The inverse of this unit converter. Computed when first needed.
+     */
+    private transient volatile LinearConverter inverse;
+
+    /**
      * Creates a new linear converter for the given scale and offset.
      * The complete formula applied is {@code y = (x*scale + offset) / divisor}.
      */
@@ -253,7 +260,11 @@ final class LinearConverter extends Abst
      */
     @Override
     public synchronized UnitConverter inverse() {
-        return isIdentity() ? this : new LinearConverter(divisor, -offset, scale);
+        if (inverse == null) {
+            inverse = isIdentity() ? this : new LinearConverter(divisor, -offset, scale);
+            inverse.inverse = this;
+        }
+        return inverse;
     }
 
     /**
@@ -441,6 +452,18 @@ final class LinearConverter extends Abst
     }
 
     /**
+     * Compares this converter with the given object for equality, optionally ignoring rounding errors.
+     */
+    @Override
+    public boolean equals(final Object other, final ComparisonMode mode) {
+        if (mode.isApproximative()) {
+            return (other instanceof LinearConverter) && equivalent((LinearConverter) other);
+        } else {
+            return equals(other);
+        }
+    }
+
+    /**
      * Returns {@code true} if the given converter perform the same conversion than this converter,
      * except for rounding errors.
      */

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -16,6 +16,10 @@
  */
 package org.apache.sis.measure;
 
+import static org.apache.sis.measure.Angle.valueOf;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.cs.AxisDirection;
+
 
 /**
  * A longitude angle in decimal degrees.
@@ -32,11 +36,12 @@ package org.apache.sis.measure;
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3
- * @version 0.4
+ * @version 0.8
  * @module
  *
  * @see Latitude
  * @see AngleFormat
+ * @see org.apache.sis.geometry.CoordinateFormat
  */
 public final class Longitude extends Angle {
     /**
@@ -89,6 +94,29 @@ public final class Longitude extends Ang
     }
 
     /**
+     * Constructs a newly allocated object containing the longitude value of the given position.
+     * For this method, the longitude value is defined as the angular value associated to the first axis
+     * oriented toward {@linkplain AxisDirection#EAST East} or {@linkplain AxisDirection#WEST West}.
+     * Note that this is not necessarily the <cite>geodetic longitudes</cite> used in
+     * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS};
+     * it may also be <cite>geocentric longitudes</cite>.
+     *
+     * <p>If the axis direction is West, then the sign of the ordinate value is inverted.
+     * If the ordinate value uses another angular units than {@linkplain Units#DEGREE degrees},
+     * then a unit conversion is applied.</p>
+     *
+     * @param  position  the coordinate from which to extract the longitude value in degrees.
+     * @throws IllegalArgumentException if the given coordinate it not associated to a CRS,
+     *         or if no axis oriented toward East or West is found, or if that axis does
+     *         not use {@linkplain Units#isAngular angular units}.
+     *
+     * @since 0.8
+     */
+    public Longitude(final DirectPosition position) throws IllegalArgumentException {
+        super(valueOf(position, AxisDirection.EAST, AxisDirection.WEST));
+    }
+
+    /**
      * Returns the hemisphere character for an angle of the given sign.
      * This is used only by {@link #toString()}, not by {@link AngleFormat}.
      */

Modified: sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -182,7 +182,7 @@ public class Range<E extends Comparable<
      * by the {@link #create(Comparable, boolean, Comparable, boolean)} method - otherwise we may
      * get an {@link ArrayStoreException}.
      */
-    @SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation.
+    @SuppressWarnings({"unchecked","rawtypes"})                     // Generic array creation.
     Range<E>[] newArray(final int length) {
         return new Range[length];
     }



Mime
View raw message