Author: desruisseaux
Date: Fri Jul 31 21:59:39 2015
New Revision: 1693658

URL: http://svn.apache.org/r1693658
Log:
Allow NormalizedProjection constructors to know whether the second defining 
parameter of the Ellipsoid
is the semi-major axis length or the inverse flattening factor, and in the 
later case allow constructors
to get the definitive flattening factor in order to compute the excentricity 
more accuratly.

The intend is not to get more accurate map projection. Instead, we do that 
because some code sometime
compute back the original semi-minor axis length (for example) from the 
excentricity. Our goal is to
allow (if possible) such code to get a value closer to the original value 
(idealy the exact same value).

Modified:
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
    
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/TestCase.java

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -106,9 +106,7 @@ public final class Formulas extends Stat
 
     /**
      * Returns the radius of a hypothetical sphere having the same surface 
than the ellipsoid
-     * specified by the given axis length. This method does not verify if 
{@code a == b}
-     * (in which case {@code a} could be returned directly); it is up to the 
caller to perform
-     * such optimization if desired.
+     * specified by the given axis length.
      *
      * @param  a The semi-major axis length.
      * @param  b The semi-minor axis length.

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -126,7 +126,7 @@ public final class Equirectangular exten
                 .addName("Latitude of 1st standard parallel")
                 .addName(Citations.OGC,     Constants.STANDARD_PARALLEL_1)
                 .addName(Citations.ESRI,    "Standard_Parallel_1")
-                .addName(Citations.NETCDF,  "standard_parallel")
+                .addName(Citations.NETCDF,  Constants.STANDARD_PARALLEL)
                 .addName(Citations.GEOTIFF, "ProjStdParallel1")
                 .addName(Citations.PROJ4,   "lat_ts"), false);
 
@@ -144,7 +144,7 @@ public final class Equirectangular exten
                 .addName("False easting")
                 .addName(Citations.OGC,     Constants.FALSE_EASTING)
                 .addName(Citations.ESRI,    "False_Easting")
-                .addName(Citations.NETCDF,  "false_easting")
+                .addName(Citations.NETCDF,  Constants.FALSE_EASTING)
                 .addName(Citations.GEOTIFF, "FalseEasting")
                 .addName(Citations.PROJ4,   "x_0"));
 
@@ -153,7 +153,7 @@ public final class Equirectangular exten
                 .addName("False northing")
                 .addName(Citations.OGC,     Constants.FALSE_NORTHING)
                 .addName(Citations.ESRI,    "False_Northing")
-                .addName(Citations.NETCDF,  "false_northing")
+                .addName(Citations.NETCDF,  Constants.FALSE_NORTHING)
                 .addName(Citations.GEOTIFF, "FalseNorthing")
                 .addName(Citations.PROJ4,   "y_0"));
         /*

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -58,30 +58,6 @@ final class MapProjectionDescriptor exte
     private static final long serialVersionUID = -9142116135803309453L;
 
     /**
-     * The NetCDF parameter name for the Earth radius.
-     *
-     * @see Constants#SEMI_MAJOR
-     * @see Constants#SEMI_MINOR
-     */
-    static final String EARTH_RADIUS = "earth_radius";
-
-    /**
-     * The NetCDF parameter name for inverse flattening.
-     *
-     * @see Constants#SEMI_MAJOR
-     * @see Constants#SEMI_MINOR
-     */
-    static final String INVERSE_FLATTENING = "inverse_flattening";
-
-    /**
-     * The NetCDF parameter name for the standard parallels.
-     *
-     * @see Constants#STANDARD_PARALLEL_1
-     * @see Constants#STANDARD_PARALLEL_2
-     */
-    static final String STANDARD_PARALLEL = "standard_parallel";
-
-    /**
      * {@code true} if the {@link #STANDARD_PARALLEL} parameter can be added.
      */
     final boolean hasStandardParallels;
@@ -146,14 +122,17 @@ final class MapProjectionDescriptor exte
      */
     @Override
     public GeneralParameterDescriptor descriptor(final String name) throws 
ParameterNotFoundException {
-        if (isHeuristicMatchForName(name, EARTH_RADIUS)) {
+        if (isHeuristicMatchForName(name, Constants.EARTH_RADIUS)) {
             return MapProjectionParameters.EarthRadius.DESCRIPTOR;
         }
-        if (isHeuristicMatchForName(name, INVERSE_FLATTENING)) {
+        if (isHeuristicMatchForName(name, Constants.INVERSE_FLATTENING)) {
             return MapProjectionParameters.InverseFlattening.DESCRIPTOR;
         }
+        if (isHeuristicMatchForName(name, Constants.IS_IVF_DEFINITIVE)) {
+            return MapProjectionParameters.IsIvfDefinitive.DESCRIPTOR;
+        }
         if (hasStandardParallels) {
-            if (isHeuristicMatchForName(name, STANDARD_PARALLEL)) {
+            if (isHeuristicMatchForName(name, Constants.STANDARD_PARALLEL)) {
                 return MapProjectionParameters.StandardParallel.DESCRIPTOR;
             }
         }

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -33,6 +33,9 @@ import org.apache.sis.util.ArraysExt;
 import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
 import static org.apache.sis.metadata.iso.citation.Citations.NETCDF;
 
+// Branch-specific imports
+import java.util.Objects;
+
 
 /**
  * Map projection parameters, with special processing for alternative ways to 
express the ellipsoid axis length
@@ -60,7 +63,7 @@ final class MapProjectionParameters exte
      * The {@link InverseFlattening} parameter instance, created when first 
needed.
      * This is an "invisible" parameter, never shown in the {@link #values()} 
list.
      */
-    private transient ParameterValue<Double> inverseFlattening;
+    private transient InverseFlattening inverseFlattening;
 
     /**
      * The {@link StandardParallel} parameter instance, created when first 
needed.
@@ -69,6 +72,12 @@ final class MapProjectionParameters exte
     private transient ParameterValue<double[]> standardParallel;
 
     /**
+     * The {@link IsIvfDefinitive} parameter instance, created when first 
needed.
+     * This is an "invisible" parameter, never shown in the {@link #values()} 
list.
+     */
+    private transient ParameterValue<Boolean> isIvfDefinitive;
+
+    /**
      * Creates a new parameter value group. An instance of {@link 
MapProjectionDescriptor}
      * is mandatory, because some method in this class will need to cast the 
descriptor.
      */
@@ -97,38 +106,46 @@ final class MapProjectionParameters exte
      */
     @Override
     ParameterValue<?> parameterIfExist(final String name) throws 
ParameterNotFoundException {
-        if (MapProjectionDescriptor.isHeuristicMatchForName(name, 
MapProjectionDescriptor.EARTH_RADIUS)) {
-            ParameterValue<?> value = earthRadius;
-            if (value == null) {
-                value = earthRadius = new EarthRadius(
-                        parameter(Constants.SEMI_MAJOR),
-                        parameter(Constants.SEMI_MINOR));
+        if (MapProjectionDescriptor.isHeuristicMatchForName(name, 
Constants.EARTH_RADIUS)) {
+            if (earthRadius == null) {
+                earthRadius = new EarthRadius(parameter(Constants.SEMI_MAJOR),
+                                              parameter(Constants.SEMI_MINOR));
             }
-            return value;
+            return earthRadius;
+        }
+        if (MapProjectionDescriptor.isHeuristicMatchForName(name, 
Constants.INVERSE_FLATTENING)) {
+            return getInverseFlattening();
         }
-        if (MapProjectionDescriptor.isHeuristicMatchForName(name, 
MapProjectionDescriptor.INVERSE_FLATTENING)) {
-            ParameterValue<?> value = inverseFlattening;
-            if (value == null) {
-                value = inverseFlattening = new InverseFlattening(
-                        parameter(Constants.SEMI_MAJOR),
-                        parameter(Constants.SEMI_MINOR));
+        if (MapProjectionDescriptor.isHeuristicMatchForName(name, 
Constants.IS_IVF_DEFINITIVE)) {
+            if (isIvfDefinitive == null) {
+                isIvfDefinitive = new IsIvfDefinitive(getInverseFlattening());
             }
-            return value;
+            return isIvfDefinitive;
         }
         if (((MapProjectionDescriptor) getDescriptor()).hasStandardParallels) {
-            if (MapProjectionDescriptor.isHeuristicMatchForName(name, 
MapProjectionDescriptor.STANDARD_PARALLEL)) {
-                ParameterValue<?> value = standardParallel;
-                if (value == null) {
-                    value = standardParallel = new StandardParallel(
-                            parameter(Constants.STANDARD_PARALLEL_1),
-                            parameter(Constants.STANDARD_PARALLEL_2));
+            if (MapProjectionDescriptor.isHeuristicMatchForName(name, 
Constants.STANDARD_PARALLEL)) {
+                if (standardParallel == null) {
+                    standardParallel = new 
StandardParallel(parameter(Constants.STANDARD_PARALLEL_1),
+                                                            
parameter(Constants.STANDARD_PARALLEL_2));
                 }
-                return value;
+                return standardParallel;
             }
         }
         return super.parameterIfExist(name);
     }
 
+    /**
+     * Returns the {@link InverseFlattening} instance, creating it when first 
needed.
+     * This parameter is used also by {@link IsIvfDefinitive}.
+     */
+    private InverseFlattening getInverseFlattening() {
+        if (inverseFlattening == null) {
+            inverseFlattening = new 
InverseFlattening(parameter(Constants.SEMI_MAJOR),
+                                                      
parameter(Constants.SEMI_MINOR));
+        }
+        return inverseFlattening;
+    }
+
 
 
 
@@ -152,17 +169,10 @@ final class MapProjectionParameters exte
          * This is not a standard parameter.
          */
         static final ParameterDescriptor<Double> DESCRIPTOR = new 
DefaultParameterDescriptor<>(
-                toMap(MapProjectionDescriptor.EARTH_RADIUS), 0, 1, 
Double.class,
+                InverseFlattening.toMap(Constants.EARTH_RADIUS), 0, 1, 
Double.class,
                 MeasurementRange.createGreaterThan(0.0, SI.METRE), null, null);
 
         /**
-         * Helper method for {@link #DESCRIPTOR} constructions.
-         */
-        static Map<String,?> toMap(final String name) {
-            return Collections.singletonMap(NAME_KEY, new 
NamedIdentifier(NETCDF, name));
-        }
-
-        /**
          * The parameters for the semi-major and semi-minor axis length.
          */
         private final ParameterValue<?> semiMajor, semiMinor;
@@ -181,8 +191,8 @@ final class MapProjectionParameters exte
          */
         @Override
         protected void setValue(final Object value, final Unit<?> unit) {
-            super.setValue(value, unit);   // Perform argument check.
-            final double r = (Double) value;
+            super.setValue(value, unit);        // Perform argument check.
+            final double r = (Double) value;    // At this point, can not be 
anything else than Double.
             semiMajor.setValue(r, unit);
             semiMinor.setValue(r, unit);
         }
@@ -243,21 +253,60 @@ final class MapProjectionParameters exte
          * This is not a standard parameter.
          */
         static final ParameterDescriptor<Double> DESCRIPTOR = new 
DefaultParameterDescriptor<>(
-                EarthRadius.toMap(MapProjectionDescriptor.INVERSE_FLATTENING), 
0, 1, Double.class,
+                toMap(Constants.INVERSE_FLATTENING), 0, 1, Double.class,
                 MeasurementRange.createGreaterThan(0.0, Unit.ONE), null, null);
 
         /**
+         * Helper method for {@link #DESCRIPTOR} constructions.
+         */
+        static Map<String,?> toMap(final String name) {
+            return Collections.singletonMap(NAME_KEY, new 
NamedIdentifier(NETCDF, name));
+        }
+
+        /**
          * The parameters for the semi-major and semi-minor axis length.
          */
         private final ParameterValue<?> semiMajor, semiMinor;
 
         /**
+         * The declared inverse flattening values, together with a snapshot of 
axis lengths
+         * at the time the inverse flattening has been set.
+         */
+        private double inverseFlattening, a, b;
+
+        /**
          * Creates a new parameter.
          */
         InverseFlattening(final ParameterValue<?> semiMajor, final 
ParameterValue<?> semiMinor) {
             super(DESCRIPTOR);
             this.semiMajor = semiMajor;
             this.semiMinor = semiMinor;
+            invalidate();
+        }
+
+        /**
+         * Declares that the inverse flattening factor is not definitive.
+         * We use the fact that the {@code ==} operator gives {@code false} if 
a value is NaN.
+         */
+        void invalidate() {
+            a = b = Double.NaN;
+        }
+
+        /**
+         * Returns {@code true} if the inverse flattening factor has been 
explicitely specified
+         * and seems to still valid.
+         */
+        boolean isIvfDefinitive() {
+            if (inverseFlattening > 0) {
+                final Number ca = (Number) semiMajor.getValue();
+                if (ca != null && ca.doubleValue() == a) {
+                    final Number cb = (Number) semiMinor.getValue();
+                    if (cb != null && cb.doubleValue() == b) {
+                        return Objects.equals(semiMajor.getUnit(), 
semiMinor.getUnit());
+                    }
+                }
+            }
+            return false;
         }
 
         /**
@@ -266,27 +315,31 @@ final class MapProjectionParameters exte
          */
         @Override
         protected void setValue(final Object value, final Unit<?> unit) {
-            super.setValue(value, unit);   // Perform argument check.
-            final double ivf = (Double) value;
-            if (!Double.isNaN(ivf)) {
-                final Double a = (Double) semiMajor.getValue();
-                if (a != null) {
-                    semiMinor.setValue(Formulas.getSemiMinor(a, ivf), 
semiMajor.getUnit());
-                }
+            super.setValue(value, unit);        // Perform argument check.
+            final double ivf = (Double) value;  // At this point, can not be 
anything else than Double.
+            final Number ca = (Number) semiMajor.getValue();
+            if (ca != null) {
+                a = ca.doubleValue();
+                b = Formulas.getSemiMinor(a, ivf);
+                semiMinor.setValue(b, semiMajor.getUnit());
+            } else {
+                invalidate();
             }
+            inverseFlattening = ivf;
         }
 
         /**
-         * Invoked when the parameter value is requested.
-         * Unconditionally computes the inverse flattening factor from the 
axis lengths.
+         * Invoked when the parameter value is requested. Computes the inverse 
flattening factor
+         * from the axis lengths if the currently stored value does not seem 
to be valid anymore.
          */
         @Override
         public double doubleValue() {
-            final Double a = (Double) semiMajor.getValue();
-            if (a != null && semiMinor.getValue() != null) {
-                return Formulas.getInverseFlattening(a, 
semiMinor.doubleValue(semiMajor.getUnit()));
+            final double ca = semiMajor.doubleValue();
+            final double cb = semiMinor.doubleValue(semiMajor.getUnit());
+            if (ca == a && cb == b && inverseFlattening > 0) {
+                return inverseFlattening;
             }
-            return Double.NaN;
+            return Formulas.getInverseFlattening(ca, cb);
         }
 
         /**
@@ -302,6 +355,69 @@ final class MapProjectionParameters exte
 
 
     /**
+     * Whether the inverse flattening parameter is definitive.
+     *
+     * @see org.apache.sis.referencing.datum.DefaultEllipsoid#isIvfDefinitive()
+     */
+    static final class IsIvfDefinitive extends DefaultParameterValue<Boolean> {
+        /**
+         * For cross-version compatibility. Actually instances of this class
+         * are not expected to be serialized, but we try to be a bit safer 
here.
+         */
+        private static final long serialVersionUID = 5988883252321358629L;
+
+        /**
+         * All names known to Apache SIS for the "is IVF definitive" parameter.
+         * This is not a standard parameter.
+         */
+        static final ParameterDescriptor<Boolean> DESCRIPTOR = new 
DefaultParameterDescriptor<>(
+                InverseFlattening.toMap(Constants.IS_IVF_DEFINITIVE), 0, 1, 
Boolean.class, null, null, null);
+
+        /**
+         * The parameters for the inverse flattening factor.
+         */
+        private final InverseFlattening inverseFlattening;
+
+        /**
+         * Creates a new parameter.
+         */
+        IsIvfDefinitive(final InverseFlattening inverseFlattening) {
+            super(DESCRIPTOR);
+            this.inverseFlattening = inverseFlattening;
+        }
+
+        /**
+         * Invoked when a new parameter value is set.
+         */
+        @Override
+        protected void setValue(final Object value, final Unit<?> unit) {
+            super.setValue(value, unit);   // Perform argument check.
+            if (!(Boolean) value) {
+                inverseFlattening.invalidate();
+            }
+        }
+
+        /**
+         * Invoked when the parameter value is requested.
+         */
+        @Override
+        public boolean booleanValue() {
+            return inverseFlattening.isIvfDefinitive();
+        }
+
+        /**
+         * Getters other than the above {@code booleanValue()} delegate to 
this method.
+         */
+        @Override
+        public Boolean getValue() {
+            return booleanValue();
+        }
+    }
+
+
+
+
+    /**
      * The standard parallels parameter as an array of {@code double}. This 
parameter is computed automatically
      * from the {@code "standard_parallel_1"} and {@code 
"standard_parallel_1"} standard parameters. When this
      * non-standard parameter is explicitely set, the array elements are given 
to the above-cited standard parameters.
@@ -319,7 +435,7 @@ final class MapProjectionParameters exte
          * {@link #STANDARD_PARALLEL_2}. This is not a standard parameter.
          */
         static final ParameterDescriptor<double[]> DESCRIPTOR = new 
DefaultParameterDescriptor<>(
-                EarthRadius.toMap(MapProjectionDescriptor.STANDARD_PARALLEL),
+                InverseFlattening.toMap(Constants.STANDARD_PARALLEL),
                 0, 1, double[].class, null, null, null);
 
         /**
@@ -368,15 +484,15 @@ final class MapProjectionParameters exte
          */
         @Override
         public double[] getValue() {
-            final Double p1 = (Double) standardParallel1.getValue();
-            final Double p2 = (Double) standardParallel2.getValue();
+            final Number p1 = (Number) standardParallel1.getValue();
+            final Number p2 = (Number) standardParallel2.getValue();
             if (p2 == null) {
                 if (p1 == null) {
                     return ArraysExt.EMPTY_DOUBLE;
                 }
-                return new double[] {p1};
+                return new double[] {p1.doubleValue()};
             }
-            return new double[] {(p1 != null) ? p1 : Double.NaN, p2};
+            return new double[] {(p1 != null) ? p1.doubleValue() : Double.NaN, 
p2.doubleValue()};
         }
     }
 }

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -385,7 +385,7 @@ public class ParameterBuilder extends Bu
      *   <tr><td>{@code "semi_major"}</td>         <td>Always</td>     
<td>Standard parameter defined by WKT 1.</td></tr>
      *   <tr><td>{@code "semi_minor"}</td>         <td>Always</td>     
<td>Standard parameter defined by WKT 1.</td></tr>
      *   <tr><td>{@code "earth_radius"}</td>       <td>Hidden</td>     
<td>Mapped to {@code "semi_major"} and {@code "semi_minor"} 
parameters.</td></tr>
-     *   <tr><td>{@code "inverse_flattening"}</td> <td>Hidden</td>     
<td>Mapped to {@code "semi_major"} and {@code "semi_minor"} 
parameters.</td></tr>
+     *   <tr><td>{@code "inverse_flattening"}</td> <td>Hidden</td>     
<td>Computed from the {@code "semi_major"} and {@code "semi_minor"} 
parameters.</td></tr>
      *   <tr><td>{@code "standard_parallel"}</td>  <td>Hidden</td>
      *     <td>Array of 1 or 2 elements mapped to {@code 
"standard_parallel_1"} and {@code "standard_parallel_2"}.</td></tr>
      * </table>

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -18,8 +18,10 @@ package org.apache.sis.referencing.opera
 
 import java.util.Map;
 import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.internal.referencing.provider.MapProjection;
+import org.apache.sis.internal.util.Constants;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
@@ -67,6 +69,11 @@ final class Initializer {
      * <var>ℯ</var> is the {@linkplain #excentricity excentricity},
      * <var>a</var> is the <cite>semi-major</cite> axis length and
      * <var>b</var> is the <cite>semi-minor</cite> axis length.
+     *
+     * <p>This is stored as a double-double value because this parameter is 
sometime used for computing back
+     * the semi-minor axis length or the inverse flattening factor. In such 
case we wish to find the original
+     * {@code double} parameter value without rounding errors. This wish 
usually do not apply to other internal
+     * {@link NormalizedProjection} parameters.</p>
      */
     final DoubleDouble excentricitySquared;
 
@@ -117,13 +124,31 @@ final class Initializer {
         DoubleDouble k = new DoubleDouble(a);  // The value by which to 
multiply all results of normalized projection.
         if (a != b) {
             /*
-             * ℯ² = 1 - (b/a)²
+             * (1) Using axis lengths:  ℯ² = 1 - (b/a)²
+             * (2) Using flattening;    ℯ² = 2f - f²     where f is the (NOT 
inverse) flattening factor.
              *
-             * Double-double arithmetic here makes a difference in the 3 last 
digits for WGS84 ellipsoid.
+             * If the inverse flattening factor is the definitive factor for 
the ellipsoid, we use (2).
+             * Otherwise use (1). With double-double arithmetic, this makes a 
difference in the 3 last
+             * digits for the WGS84 ellipsoid.
              */
-            if (DoubleDouble.DISABLED) {
-                final double rs = b / a;
-                excentricitySquared.value = 1 - (rs * rs);
+            boolean isIvfDefinitive;
+            try {
+                isIvfDefinitive = 
parameters.parameter(Constants.IS_IVF_DEFINITIVE).booleanValue();
+            } catch (ParameterNotFoundException e) {
+                /*
+                 * Should never happen with Apache SIS implementation, but may 
happen if the given parameters come
+                 * from another implementation. We can safely abandon our 
attempt to get the inverse flattening value,
+                 * since it was redundant with semi-minor axis length.
+                 */
+                isIvfDefinitive = false;
+            }
+            if (isIvfDefinitive) {
+                final DoubleDouble f = new 
DoubleDouble(parameters.parameter(Constants.INVERSE_FLATTENING).doubleValue());
+                f.inverseDivide(1,0);
+                excentricitySquared.setFrom(f);
+                excentricitySquared.multiply(2,0);
+                f.multiply(f);
+                excentricitySquared.subtract(f);
             } else {
                 final DoubleDouble rs = new DoubleDouble(b);
                 rs.divide(k);    // rs = b/a

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -96,7 +96,10 @@ public class LambertConicConformal exten
     }
 
     /**
-     * Constant for the Belgium 2SP case. This is 29.2985 seconds, given here 
in radians.
+     * Constant for the Belgium 2SP case. Defined as 29.2985 seconds, given 
here in radians.
+     * Use double-double arithmetic not for map projection accuracy, but for 
consistency with
+     * the normalization matrix which use that precision for "degrees to 
radians" conversion.
+     * The goal is to have cleaner results after matrix inversions and 
multiplications.
      *
      * <div class="note"><b>Tip:</b> how to verify the value:
      * {@preformat java

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -36,6 +36,7 @@ import org.opengis.parameter.ParameterVa
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.datum.Ellipsoid;
@@ -535,12 +536,34 @@ public class DefaultMathTransformFactory
                  */
                 failure = e;
             }
+            final boolean isIvfDefinitive;
             if (mismatchedParam != null) {
                 final LogRecord record = Messages.getResources((Locale) 
null).getLogRecord(Level.WARNING,
                         Messages.Keys.MismatchedEllipsoidAxisLength_3, 
ellipsoid.getName().getCode(),
                         mismatchedParam.getDescriptor().getName().getCode(), 
mismatchedValue);
                 record.setLoggerName(Loggers.COORDINATE_OPERATION);
                 Logging.log(DefaultMathTransformFactory.class, 
"createBaseToDerived", record);
+                isIvfDefinitive = false;
+            } else {
+                isIvfDefinitive = ellipsoid.isIvfDefinitive();
+            }
+            /*
+             * Following is specific to Apache SIS. We use this non-standard 
API for allowing the
+             * NormalizedProjection class (our base class for all map 
projection implementations)
+             * to known that the ellipsoid definitive parameter is the inverse 
flattening factor
+             * instead than the semi-major axis length. It makes a small 
difference in the accuracy
+             * of the excentricity parameter.
+             */
+            if (isIvfDefinitive) try {
+                
parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening());
+            } catch (ParameterNotFoundException e) {
+                /*
+                 * Should never happen with Apache SIS implementation, but may 
happen if the given parameters come
+                 * from another implementation. We can safely abandon our 
attempt to set the inverse flattening value,
+                 * since it was redundant with semi-minor axis length.
+                 */
+                
Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
+                        DefaultMathTransformFactory.class, 
"createBaseToDerived", e);
             }
         }
         MathTransform baseToDerived;

Modified: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -28,12 +28,13 @@ import org.junit.Test;
 import static org.junit.Assert.*;
 import static org.apache.sis.internal.util.Constants.SEMI_MAJOR;
 import static org.apache.sis.internal.util.Constants.SEMI_MINOR;
+import static org.apache.sis.internal.util.Constants.EARTH_RADIUS;
+import static org.apache.sis.internal.util.Constants.INVERSE_FLATTENING;
+import static org.apache.sis.internal.util.Constants.IS_IVF_DEFINITIVE;
 import static org.apache.sis.internal.util.Constants.CENTRAL_MERIDIAN;
+import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL;
 import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL_1;
 import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL_2;
-import static org.apache.sis.parameter.MapProjectionDescriptor.EARTH_RADIUS;
-import static 
org.apache.sis.parameter.MapProjectionDescriptor.INVERSE_FLATTENING;
-import static 
org.apache.sis.parameter.MapProjectionDescriptor.STANDARD_PARALLEL;
 
 
 /**
@@ -103,17 +104,24 @@ public final strictfp class MapProjectio
         final MapProjectionDescriptor descriptor = createDescriptor(0);
         final ParameterValueGroup parameters = descriptor.createValue();
 
-        parameters.parameter(SEMI_MAJOR).setValue(6378206.4); // Clarke 1866
+        parameters.parameter(SEMI_MAJOR).setValue(6378206.4);  // Clarke 1866
         parameters.parameter(SEMI_MINOR).setValue(6356583.8);
         assertEquals(294.97870, 
parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.00001);
         assertEquals(6378206.4, parameters.parameter(SEMI_MAJOR)        
.doubleValue(), 0.5);
         assertEquals(6356583.8, parameters.parameter(SEMI_MINOR)        
.doubleValue(), 0.5);
+        assertFalse("isIvfDefinitive", 
parameters.parameter(IS_IVF_DEFINITIVE).booleanValue());
 
-        parameters.parameter(SEMI_MAJOR).setValue(6378137.000); // WGS84
+        parameters.parameter(SEMI_MAJOR).setValue(6378137.0);  // WGS84
         parameters.parameter(INVERSE_FLATTENING).setValue(298.257223563);
         assertEquals(298.257, 
parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.001);
         assertEquals(6378137, parameters.parameter(SEMI_MAJOR)        
.doubleValue(), 0.5);
         assertEquals(6356752, parameters.parameter(SEMI_MINOR)        
.doubleValue(), 0.5);
+        assertTrue("isIvfDefinitive", 
parameters.parameter(IS_IVF_DEFINITIVE).booleanValue());
+
+        parameters.parameter(SEMI_MAJOR).setValue(6378350.9);  // Clarke 1858 
(approximative)
+        parameters.parameter(SEMI_MINOR).setValue(6356675.0);
+        assertEquals(294.26, 
parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.001);
+        assertFalse("isIvfDefinitive", 
parameters.parameter(IS_IVF_DEFINITIVE).booleanValue());
     }
 
     /**

Modified: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -105,7 +105,7 @@ public final strictfp class LambertConic
         createNormalizedProjection(true, 40);
         assertWktEqualsRegex("\\Q" +
                 "PARAM_MT[“Lambert conic conformal”,\n" +
-                "  PARAMETER[“excentricity”, 0.08181919084262244],\n" +
+                "  PARAMETER[“excentricity”, 0.0818191908426215],\n" +
                 "  PARAMETER[“n”, 0.64278760968653\\E\\d*\\]\\]");  // 
0.6427876096865393 in the original test.
     }
 

Modified: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -66,6 +66,9 @@ strictfp class MapProjectionTestCase ext
         final Ellipsoid ellipsoid = (ellipse ? GeodeticDatumMock.WGS84 : 
GeodeticDatumMock.SPHERE).getEllipsoid();
         
parameters.parameter(Constants.SEMI_MAJOR).setValue(ellipsoid.getSemiMajorAxis());
         
parameters.parameter(Constants.SEMI_MINOR).setValue(ellipsoid.getSemiMinorAxis());
+        if (ellipse) {
+            
parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening());
+        }
         return parameters;
     }
 

Modified: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -75,7 +75,7 @@ public final strictfp class MercatorTest
         createNormalizedProjection(true);
         assertWktEquals(
                 "PARAM_MT[“Mercator”,\n" +
-                "  PARAMETER[“excentricity”, 0.08181919084262244]]");
+                "  PARAMETER[“excentricity”, 0.0818191908426215]]");
     }
 
     /**

Modified: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -45,7 +45,17 @@ final strictfp class NoOp extends Confor
      * @param ellipsoidal {@code true} for an ellipsoidal case, or {@code 
false} for a spherical case.
      */
     NoOp(final boolean ellipsoidal) {
-        this(parameters((ellipsoidal ? GeodeticDatumMock.WGS84 : 
GeodeticDatumMock.SPHERE).getEllipsoid()));
+        this(ellipsoidal, ellipsoidal);
+    }
+
+    /**
+     * Creates a new "no-operation".
+     *
+     * @param ellipsoidal {@code true} for an ellipsoidal case, or {@code 
false} for a spherical case.
+     * @param declareIvf  {@code true} for declaring the inverse flattening 
factor.
+     */
+    NoOp(final boolean ellipsoidal, final boolean declareIvf) {
+        this(parameters((ellipsoidal ? GeodeticDatumMock.WGS84 : 
GeodeticDatumMock.SPHERE).getEllipsoid(), declareIvf));
     }
 
     /**
@@ -71,9 +81,14 @@ final strictfp class NoOp extends Confor
      * ("Relax constraint on placement of this()/super() call in 
constructors").
      */
     @Workaround(library="JDK", version="1.7")
-    private static Parameters parameters(final Ellipsoid ellipsoid) {
-        return parameters(ellipsoid.getSemiMajorAxis(),
-                          ellipsoid.getSemiMinorAxis());
+    private static Parameters parameters(final Ellipsoid ellipsoid, final 
boolean declareIvf) {
+        final Parameters parameters = parameters(
+                ellipsoid.getSemiMajorAxis(),
+                ellipsoid.getSemiMinorAxis());
+        if (declareIvf) {
+            
parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening());
+        }
+        return parameters;
     }
 
     /**

Modified: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -65,8 +65,17 @@ public final strictfp class NormalizedPr
         NormalizedProjection projection;
         transform = projection = new NoOp(false);
         assertEquals("excentricity", 0.0, projection.excentricity, 0.0);
+        /*
+         * Tested methods. Note the similarity between (1) and (3).
+         *
+         *  (1) Using double        arithmetic and axis lengths:  
0.08181919084262157
+         *  (2) Using double-double arithmetic and axis lengths:  
0.08181919084262244
+         *  (3) Using double-double arithmetic and flattening:    
0.0818191908426215
+         */
+        transform = projection = new NoOp(true, false);
+        assertEquals("excentricity", 0.08181919084262244, 
projection.excentricity, 0.0);
 
-        transform = projection = new NoOp(true);
-        assertEquals("excentricity", 0.08181919084262157, 
projection.excentricity, TOLERANCE);
+        transform = projection = new NoOp(true, true);
+        assertEquals("excentricity", 0.0818191908426215, 
projection.excentricity, 0.0);
     }
 }

Modified: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -78,17 +78,34 @@ public final class Constants extends Sta
     public static final byte CRS84 = 84;
 
     /**
+     * The NetCDF parameter name for the Earth radius.
+     */
+    public static final String EARTH_RADIUS = "earth_radius";
+
+    /**
      * Name of the {@value} projection parameter, which is handled specially 
during WKT formatting.
      */
     public static final String SEMI_MAJOR = "semi_major",
                                SEMI_MINOR = "semi_minor";
 
     /**
+     * The NetCDF parameter name for inverse flattening, and whether that 
parameter is definitive.
+     * The later is specific to SIS.
+     */
+    public static final String INVERSE_FLATTENING = "inverse_flattening",
+                               IS_IVF_DEFINITIVE  = "is_ivf_definitive";
+
+    /**
      * The OGC parameter name for the central meridian.
      */
     public static final String CENTRAL_MERIDIAN = "central_meridian";
 
     /**
+     * The NetCDF parameter name for the standard parallels.
+     */
+    public static final String STANDARD_PARALLEL = "standard_parallel";
+
+    /**
      * The OGC parameter name for the standard parallels.
      */
     public static final String STANDARD_PARALLEL_1 = "standard_parallel_1",
@@ -125,6 +142,7 @@ public final class Constants extends Sta
      */
     public static final String FALSE_EASTING  = "false_easting",
                                FALSE_NORTHING = "false_northing";
+
     /**
      * Name of the {@value} matrix parameters.
      */

Modified: 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/TestCase.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/TestCase.java?rev=1693658&r1=1693657&r2=1693658&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/TestCase.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/TestCase.java
 [UTF-8] Fri Jul 31 21:59:39 2015
@@ -24,7 +24,6 @@ import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.OutputStreamWriter;
 import java.io.UnsupportedEncodingException;
-import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.logging.Logging;
 import org.junit.runner.RunWith;
 
@@ -182,6 +181,7 @@ public abstract strictfp class TestCase
      *
      * @param success {@code true} if this method is invoked on build success,
      */
+    @SuppressWarnings("UseOfSystemOutOrSystemErr")
     static void flushOutput() {
         System.out.flush();
         System.err.flush();


Reply via email to