diff --git a/NOTICE.txt b/NOTICE.txt
index a1b38927767a307cb9d1887711c9c3cbb25dad10..42b7018bc2d339186e67bf4779e0b331a6f1c992 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,5 +1,5 @@
 RUGGED
-Copyright 2013-2016 CS Systèmes d'Information
+Copyright 2013-2017 CS Systèmes d'Information
 
 This product includes software developed by
 CS Systèmes d'Information (http://www.c-s.fr/)
diff --git a/design/dem-loading-class-diagram.puml b/design/dem-loading-class-diagram.puml
index 1e4b93d8d8717341b8e6fbea08378c66aeb961d8..08102c7f655f48a54f58271980585fd87bff2788 100644
--- a/design/dem-loading-class-diagram.puml
+++ b/design/dem-loading-class-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/direct-location-class-diagram.puml b/design/direct-location-class-diagram.puml
index 3ec9ad29abea23a58ae8ead815febd8ca2529c64..5425c284a7d532d8a13e677e0c9b2e397fb0c74d 100644
--- a/design/direct-location-class-diagram.puml
+++ b/design/direct-location-class-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/direct-location-sequence-diagram.puml b/design/direct-location-sequence-diagram.puml
index 256d379f8d977399b0aeebb1bfd5007ec1643c2b..e2fa64b23017becfdca9da75ba84914d6966b463 100644
--- a/design/direct-location-sequence-diagram.puml
+++ b/design/direct-location-sequence-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/duvenhage-inner-recursion-activity-diagram.puml b/design/duvenhage-inner-recursion-activity-diagram.puml
index 2781a4a94af758fdaf3ed22525c72ae9cfdd8c39..b57df73c4a54df5fa675dcb58b759a820f4db419 100644
--- a/design/duvenhage-inner-recursion-activity-diagram.puml
+++ b/design/duvenhage-inner-recursion-activity-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/duvenhage-top-loop-activity-diagram.puml b/design/duvenhage-top-loop-activity-diagram.puml
index 3247946e0f36586b01687f4c97002b162a30ec7c..fd54d5bc7227b55f38c478a63b832107dd39828e 100644
--- a/design/duvenhage-top-loop-activity-diagram.puml
+++ b/design/duvenhage-top-loop-activity-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/initialization-class-diagram.puml b/design/initialization-class-diagram.puml
index 4bec76b761ac6c719c3110d68d44ee38efb1c4ec..a3d127516d453b245d8c4de1f05b4c5a84179530 100644
--- a/design/initialization-class-diagram.puml
+++ b/design/initialization-class-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/initialization-sequence-diagram.puml b/design/initialization-sequence-diagram.puml
index ab11138169050ffa86dc32739bc36de30c3dc2fa..fa8701ec508c731f0a8dfeb73ea8fefcdedd4ac1 100644
--- a/design/initialization-sequence-diagram.puml
+++ b/design/initialization-sequence-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/inverse-location-sequence-diagram.puml b/design/inverse-location-sequence-diagram.puml
index 175d2cccd969bffe68cfec10a475e77c877814d6..dd621c9099219108eadc1426269669696c66c3f9 100644
--- a/design/inverse-location-sequence-diagram.puml
+++ b/design/inverse-location-sequence-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/design/parametric-los-class-diagram.puml b/design/parametric-los-class-diagram.puml
index 75dcb95ca61830de1f86094da109baa26927e168..dd036c8e9389fc61c2d0c3e68ae2c410d6fd883b 100644
--- a/design/parametric-los-class-diagram.puml
+++ b/design/parametric-los-class-diagram.puml
@@ -1,4 +1,4 @@
-' Copyright 2013-2015 CS Systèmes d'Information
+' Copyright 2013-2017 CS Systèmes d'Information
 ' Licensed to CS Systèmes d'Information (CS) under one or more
 ' contributor license agreements.  See the NOTICE file distributed with
 ' this work for additional information regarding copyright ownership.
diff --git a/pom.xml b/pom.xml
index 997060c7404fc83201ac2f10cbf81cc88e7c3d61..88e8978d1d32fee7a2aba91416f1eb12038bd513 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,8 +38,8 @@
     <rugged.maven-resources-plugin.version>3.0.1</rugged.maven-resources-plugin.version>
     <rugged.jgit.buildnumber.version>1.2.10</rugged.jgit.buildnumber.version>
     <rugged.plantuml.version>7999</rugged.plantuml.version>
-    <rugged.orekit.version>8.0</rugged.orekit.version>
-    <rugged.hipparchus.version>1.0</rugged.hipparchus.version>
+    <rugged.orekit.version>9.0-SNAPSHOT</rugged.orekit.version>
+    <rugged.hipparchus.version>1.1</rugged.hipparchus.version>
     <rugged.compiler.source>1.8</rugged.compiler.source>
     <rugged.compiler.target>1.8</rugged.compiler.target>
     <rugged.implementation.build>${git.revision}; ${maven.build.timestamp}</rugged.implementation.build>
@@ -55,6 +55,13 @@
         <role>developer</role>
       </roles>
     </developer>
+    <developer>
+      <name>Guylaine Prat</name>
+      <id>guylaine</id>
+      <roles>
+        <role>developer</role>
+      </roles>
+    </developer>
   </developers>
 
   <contributors>
@@ -76,6 +83,9 @@
     <contributor>
       <name>Marina Ludwig</name>
     </contributor>
+    <contributor>
+      <name>Lars N&#230;sbye Christensen</name>
+    </contributor>
     <contributor>
       <name>Beatriz Salazar</name>
     </contributor>
@@ -461,7 +471,6 @@
         <artifactId>maven-javadoc-plugin</artifactId>
         <version>${rugged.maven-javadoc-plugin.version}</version>
         <configuration>
-          <overview>${basedir}/core/src/main/java/org/orekit/rugged/overview.html</overview>
           <links>
             <link>http://docs.oracle.com/javase/8/docs/api/</link>
             <link>https://hipparchus.org/apidocs/</link>
diff --git a/src/main/java/org/orekit/rugged/api/AlgorithmId.java b/src/main/java/org/orekit/rugged/api/AlgorithmId.java
index 54528b53f64a1be4de2e41b41c83a8a661bc552b..e3e46c9395faa2ab7e9122ef5ff8cc68da904e1f 100644
--- a/src/main/java/org/orekit/rugged/api/AlgorithmId.java
+++ b/src/main/java/org/orekit/rugged/api/AlgorithmId.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/api/BodyRotatingFrameId.java b/src/main/java/org/orekit/rugged/api/BodyRotatingFrameId.java
index 714456033f1437e5143ceeaa842b94cc2879e9fb..1ee7f79345628f7c3dcb3501690e4d74bfebd4af 100644
--- a/src/main/java/org/orekit/rugged/api/BodyRotatingFrameId.java
+++ b/src/main/java/org/orekit/rugged/api/BodyRotatingFrameId.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/api/EllipsoidId.java b/src/main/java/org/orekit/rugged/api/EllipsoidId.java
index c75a87fb959e6179f2791a4661d89391e0500707..bb371d04121e581401b9c95ee376b65643471f43 100644
--- a/src/main/java/org/orekit/rugged/api/EllipsoidId.java
+++ b/src/main/java/org/orekit/rugged/api/EllipsoidId.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/api/InertialFrameId.java b/src/main/java/org/orekit/rugged/api/InertialFrameId.java
index a7295c2a25335d48ee6aadae09d03b17d839a52e..67b1b4bb92d9f860ddb238d030b9555c7974501e 100644
--- a/src/main/java/org/orekit/rugged/api/InertialFrameId.java
+++ b/src/main/java/org/orekit/rugged/api/InertialFrameId.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/api/ProjectionTypeId.java b/src/main/java/org/orekit/rugged/api/ProjectionTypeId.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb25c877ecefef08126eda4b71531398cdf909db
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/api/ProjectionTypeId.java
@@ -0,0 +1,42 @@
+/* Copyright 2013-2017 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.rugged.api;
+
+
+/** Enumerate for cartographic projection type.
+ * @author Lucie Labat-Allee
+ */
+public enum ProjectionTypeId {
+
+    /** Constant for WGS 84 projection type. */
+    WGS84,
+
+    /** Constant for UTM zone 1N projection type. */
+    UTM1N,
+
+    /** Constant for UTM zone 60N projection type. */
+    UTM60N,
+
+    /** Constant for UTM zone 1S projection type. */
+    UTM1S,
+
+    /** Constant for UTM zone 60S projection type. */
+    UTM60S,
+
+    /** Constant for Lambert-93 projection type. */
+    LAMBERT93
+}
diff --git a/src/main/java/org/orekit/rugged/api/Rugged.java b/src/main/java/org/orekit/rugged/api/Rugged.java
index 76b6a9c4f2c360b896b2fa59387d2fd533907aed..6ef7b621a84b4b2aa4596c372fc8ade7098550e9 100644
--- a/src/main/java/org/orekit/rugged/api/Rugged.java
+++ b/src/main/java/org/orekit/rugged/api/Rugged.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -34,6 +34,7 @@ import org.orekit.rugged.linesensor.LineSensor;
 import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
 import org.orekit.rugged.linesensor.SensorPixel;
 import org.orekit.rugged.linesensor.SensorPixelCrossing;
+import org.orekit.rugged.refraction.AtmosphericRefraction;
 import org.orekit.rugged.utils.DSGenerator;
 import org.orekit.rugged.utils.ExtendedEllipsoid;
 import org.orekit.rugged.utils.NormalizedGeodeticPoint;
@@ -49,6 +50,7 @@ import org.orekit.utils.PVCoordinates;
  * @author Luc Maisonobe
  * @author Lucie LabatAllee
  * @author Jonathan Guinet
+ * @author Guylaine Prat
  */
 public class Rugged {
 
@@ -88,8 +90,10 @@ public class Rugged {
     /** Rugged name. */
     private final String name;
 
-    /**
-     * Build a configured instance.
+    /** Atmospheric refraction for line of sight correction. */
+    private AtmosphericRefraction atmosphericRefraction;
+
+    /** Build a configured instance.
      * <p>
      * By default, the instance performs both light time correction (which
      * refers to ground point motion with respect to inertial frame) and
@@ -105,19 +109,18 @@ public class Rugged {
      *        intersection
      * @param ellipsoid f reference ellipsoid
      * @param lightTimeCorrection if true, the light travel time between ground
-     * @param aberrationOfLightCorrection if true, the aberration of light is
-     *        corrected for more accurate location and spacecraft is compensated
-     *        for more accurate location
+     * @param aberrationOfLightCorrection if true, the aberration of light
+     * is corrected for more accurate location
+     * and spacecraft is compensated for more accurate location
+     * @param atmosphericRefraction the atmospheric refraction model to be used for more accurate location
      * @param scToBody transforms interpolator
      * @param sensors sensors
      * @param name RuggedName
      */
-    Rugged(final IntersectionAlgorithm algorithm,
-           final ExtendedEllipsoid ellipsoid, final boolean lightTimeCorrection,
-           final boolean aberrationOfLightCorrection,
-           final SpacecraftToObservedBody scToBody,
-           final Collection<LineSensor> sensors,
-           final String name) {
+    Rugged(final IntersectionAlgorithm algorithm, final ExtendedEllipsoid ellipsoid, final boolean lightTimeCorrection,
+           final boolean aberrationOfLightCorrection, final AtmosphericRefraction atmosphericRefraction,
+           final SpacecraftToObservedBody scToBody, final Collection<LineSensor> sensors, final String name) {
+
 
         // space reference
         this.ellipsoid = ellipsoid;
@@ -139,7 +142,7 @@ public class Rugged {
 
         this.lightTimeCorrection = lightTimeCorrection;
         this.aberrationOfLightCorrection = aberrationOfLightCorrection;
-
+        this.atmosphericRefraction       = atmosphericRefraction;
     }
 
     /**
@@ -182,9 +185,14 @@ public class Rugged {
         return aberrationOfLightCorrection;
     }
 
-    /**
-     * Get the line sensors.
-     *
+    /** Get the atmospheric refraction model.
+     * @return atmospheric refraction model
+     */
+    public AtmosphericRefraction getRefractionCorrection() {
+        return atmosphericRefraction;
+    }
+
+    /** Get the line sensors.
      * @return line sensors
      */
     public Collection<LineSensor> getLineSensors() {
@@ -267,10 +275,8 @@ public class Rugged {
         final GeodeticPoint[] gp = new GeodeticPoint[sensor.getNbPixels()];
         for (int i = 0; i < sensor.getNbPixels(); ++i) {
 
-            DumpManager.dumpDirectLocation(date, sensor.getPosition(),
-                                           sensor.getLOS(date, i),
-                                           lightTimeCorrection,
-                                           aberrationOfLightCorrection);
+            DumpManager.dumpDirectLocation(date, sensor.getPosition(), sensor.getLOS(date, i), lightTimeCorrection,
+                                           aberrationOfLightCorrection, atmosphericRefraction != null);
 
             final Vector3D obsLInert = scToInert
                             .transformVector(sensor.getLOS(date, i));
@@ -333,6 +339,13 @@ public class Rugged {
                                                     .intersection(ellipsoid, pBody, lBody));
             }
 
+            if (atmosphericRefraction != null) {
+                // apply atmospheric refraction correction
+                final Vector3D pBody = inertToBody.transformPosition(pInert);
+                final Vector3D lBody = inertToBody.transformVector(lInert);
+                gp[i] = atmosphericRefraction.applyCorrection(pBody, lBody, (NormalizedGeodeticPoint) gp[i], algorithm);
+            }
+
             DumpManager.dumpDirectLocationResult(gp[i]);
 
         }
@@ -357,8 +370,8 @@ public class Rugged {
                                         final Vector3D los)
                                                         throws RuggedException {
 
-        DumpManager.dumpDirectLocation(date, position, los, lightTimeCorrection,
-                                       aberrationOfLightCorrection);
+        DumpManager.dumpDirectLocation(date, position, los, lightTimeCorrection, aberrationOfLightCorrection,
+                                       atmosphericRefraction != null);
 
         // compute the approximate transform between spacecraft and observed
         // body
@@ -400,7 +413,7 @@ public class Rugged {
             lInert = obsLInert;
         }
 
-        final NormalizedGeodeticPoint result;
+        final NormalizedGeodeticPoint gp;
         if (lightTimeCorrection) {
             // compute DEM intersection with light time correction
             final Vector3D sP = approximate.transformPosition(position);
@@ -416,18 +429,28 @@ public class Rugged {
             final Vector3D eP2 = ellipsoid.transform(gp1);
             final double deltaT2 = eP2.distance(sP) / Constants.SPEED_OF_LIGHT;
             final Transform shifted2 = inertToBody.shiftedBy(-deltaT2);
-            result = algorithm
-                            .refineIntersection(ellipsoid,
-                                                shifted2.transformPosition(pInert),
-                                                shifted2.transformVector(lInert), gp1);
+            gp = algorithm.refineIntersection(ellipsoid,
+                                              shifted2.transformPosition(pInert),
+                                              shifted2.transformVector(lInert),
+                                              gp1);
 
         } else {
             // compute DEM intersection without light time correction
             final Vector3D pBody = inertToBody.transformPosition(pInert);
             final Vector3D lBody = inertToBody.transformVector(lInert);
-            result = algorithm
-                            .refineIntersection(ellipsoid, pBody, lBody, algorithm
-                                                .intersection(ellipsoid, pBody, lBody));
+            gp = algorithm.refineIntersection(ellipsoid, pBody, lBody,
+                                              algorithm.intersection(ellipsoid, pBody, lBody));
+        }
+
+        final NormalizedGeodeticPoint result;
+        if (atmosphericRefraction != null) {
+            // apply atmospheric refraction correction
+            final Vector3D pBody = inertToBody.transformPosition(pInert);
+            final Vector3D lBody = inertToBody.transformVector(lInert);
+            result = atmosphericRefraction.applyCorrection(pBody, lBody, gp, algorithm);
+        } else {
+            // don't apply atmospheric refraction correction
+            result = gp;
         }
 
         DumpManager.dumpDirectLocationResult(result);
@@ -461,10 +484,9 @@ public class Rugged {
      * occur after 55750. Of course, these values are only an example and should
      * be adjusted depending on mission needs.
      * </p>
-     *
-     * @param sensorName name of the line sensor
-     * @param latitude ground point latitude
-     * @param longitude ground point longitude
+     * @param sensorName name of the line  sensor
+     * @param latitude ground point latitude (rad)
+     * @param longitude ground point longitude (rad)
      * @param minLine minimum line number
      * @param maxLine maximum line number
      * @return date at which ground point is seen by line sensor
@@ -563,10 +585,9 @@ public class Rugged {
      * occur after 55750. Of course, these values are only an example and should
      * be adjusted depending on mission needs.
      * </p>
-     *
-     * @param sensorName name of the line sensor
-     * @param latitude ground point latitude
-     * @param longitude ground point longitude
+     * @param sensorName name of the line  sensor
+     * @param latitude ground point latitude (rad)
+     * @param longitude ground point longitude (rad)
      * @param minLine minimum line number
      * @param maxLine maximum line number
      * @return sensor pixel seeing ground point, or null if ground point cannot
@@ -1088,7 +1109,7 @@ public class Rugged {
                         .add(lowIndex);
 
         return new DerivativeStructure[] {
-            fixedLine, fixedPixel
+                                          fixedLine, fixedPixel
         };
 
     }
diff --git a/src/main/java/org/orekit/rugged/api/RuggedBuilder.java b/src/main/java/org/orekit/rugged/api/RuggedBuilder.java
index 53b1a867ae01ea05b94ca3bf739a56f948909807..d6b7582fce6ed49a96f3d2cb18add5f1ff89b982 100644
--- a/src/main/java/org/orekit/rugged/api/RuggedBuilder.java
+++ b/src/main/java/org/orekit/rugged/api/RuggedBuilder.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -44,6 +44,7 @@ import org.orekit.rugged.intersection.IntersectionAlgorithm;
 import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm;
 import org.orekit.rugged.linesensor.LineSensor;
 import org.orekit.rugged.raster.TileUpdater;
+import org.orekit.rugged.refraction.AtmosphericRefraction;
 import org.orekit.rugged.utils.ExtendedEllipsoid;
 import org.orekit.rugged.utils.SpacecraftToObservedBody;
 import org.orekit.time.AbsoluteDate;
@@ -84,6 +85,7 @@ import org.orekit.utils.TimeStampedPVCoordinates;
  * @see <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern (wikipedia)</a>
  * @see <a href="https://en.wikipedia.org/wiki/Fluent_interface">Fluent interface (wikipedia)</a>
  * @author Luc Maisonobe
+ * @author Guylaine Prat
  */
 public class RuggedBuilder {
 
@@ -96,7 +98,7 @@ public class RuggedBuilder {
     /** Updater used to load Digital Elevation Model tiles. */
     private TileUpdater tileUpdater;
 
-    /** Constant elevation over ellipsoid.
+    /** Constant elevation over ellipsoid (m).
      * used only with {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID. */
     private double constantElevation;
 
@@ -109,10 +111,10 @@ public class RuggedBuilder {
     /** End of search time span. */
     private AbsoluteDate maxDate;
 
-    /** Step to use for inertial frame to body frame transforms cache computations. */
+    /** Step to use for inertial frame to body frame transforms cache computations (s). */
     private double tStep;
 
-    /** OvershootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting. */
+    /** OvershootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting (s). */
     private double overshootTolerance;
 
     /** Inertial frame for position/velocity/attitude. */
@@ -139,7 +141,7 @@ public class RuggedBuilder {
     /** Propagator for position/velocity/attitude. */
     private Propagator pvaPropagator;
 
-    /** Step to use for inertial/Earth/spacraft transforms interpolations. */
+    /** Step to use for inertial/Earth/spacraft transforms interpolations (s). */
     private double iStep;
 
     /** Number of points to use for inertial/Earth/spacraft transforms interpolations. */
@@ -154,6 +156,9 @@ public class RuggedBuilder {
     /** Flag for aberration of light correction. */
     private boolean aberrationOfLightCorrection;
 
+    /** Atmospheric refraction to use for line of sight correction. */
+    private AtmosphericRefraction atmosphericRefraction;
+
     /** Sensors. */
     private final List<LineSensor> sensors;
 
@@ -313,7 +318,7 @@ public class RuggedBuilder {
      * AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID CONSTANT_ELEVATION_OVER_ELLIPSOID}.
      * If it is called for another algorithm, the elevation set here will be ignored.
      * </p>
-     * @param constantElevation constant elevation to use
+     * @param constantElevation constant elevation to use (m)
      * @return the builder instance
      * @see #setAlgorithm(AlgorithmId)
      * @see #getConstantElevation()
@@ -353,8 +358,8 @@ public class RuggedBuilder {
      * </p>
      * @param minDate start of search time span
      * @param maxDate end of search time span
-     * @param tStep step to use for inertial frame to body frame transforms cache computations
-     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting
+     * @param tStep step to use for inertial frame to body frame transforms cache computations (s)
+     * @param overshootTolerance tolerance in seconds allowed for {@code minDate} and {@code maxDate} overshooting (s)
      * @return the builder instance
      * @see #setTrajectoryAndTimeSpan(InputStream)
      * @see #getMinDate()
@@ -492,7 +497,7 @@ public class RuggedBuilder {
      * <em>must</em> be used together with the {@link #setTimeSpan(AbsoluteDate, AbsoluteDate, double, double)}
      * but should <em>not</em> be mixed with {@link #setTrajectoryAndTimeSpan(InputStream)}.
      * </p>
-     * @param interpolationStep step to use for inertial/Earth/spacraft transforms interpolations
+     * @param interpolationStep step to use for inertial/Earth/spacraft transforms interpolations (s)
      * @param interpolationNumber number of points to use for inertial/Earth/spacraft transforms interpolations
      * @param pvFilter filter for derivatives from the sample to use in position/velocity interpolation
      * @param aFilter filter for derivatives from the sample to use in attitude interpolation
@@ -758,11 +763,6 @@ public class RuggedBuilder {
                             new ArrayList<TimeStampedAngularCoordinates>();
             propagator.setMasterMode(interpolationStep, new OrekitFixedStepHandler() {
 
-                /** {@inheritDoc} */
-                @Override
-                public void init(final SpacecraftState s0, final AbsoluteDate t) {
-                }
-
                 /** {@inheritDoc} */
                 @Override
                 public void handleStep(final SpacecraftState currentState, final boolean isLast)
@@ -853,6 +853,32 @@ public class RuggedBuilder {
         return aberrationOfLightCorrection;
     }
 
+    /** Set atmospheric refraction for line of sight correction.
+     * <p>
+     * This method sets an atmospheric refraction model to be used between
+     * spacecraft and ground for the correction of intersected points on ground.
+     * Compensating for the effect of atmospheric refraction improves location
+     * accuracy.
+     * </p>
+     * @param atmosphericRefraction the atmospheric refraction model to be used for more accurate location
+     * @return the builder instance
+     * @see #getRefractionCorrection()
+     */
+    // CHECKSTYLE: stop HiddenField check
+    public RuggedBuilder setRefractionCorrection(final AtmosphericRefraction atmosphericRefraction) {
+        // CHECKSTYLE: resume HiddenField check
+        this.atmosphericRefraction = atmosphericRefraction;
+        return this;
+    }
+
+    /** Get the atmospheric refraction model.
+     * @return atmospheric refraction model
+     * @see #setRefractionCorrection(AtmosphericRefraction)
+     */
+    public AtmosphericRefraction getRefractionCorrection() {
+        return atmosphericRefraction;
+    }
+
     /** Set up line sensor model.
      * @param lineSensor line sensor model
      * @return the builder instance
@@ -888,19 +914,19 @@ public class RuggedBuilder {
         try {
             // set up the inertial frame
             switch (inertialFrameId) {
-            case GCRF :
-                return FramesFactory.getGCRF();
-            case EME2000 :
-                return FramesFactory.getEME2000();
-            case MOD :
-                return FramesFactory.getMOD(IERSConventions.IERS_1996);
-            case TOD :
-                return FramesFactory.getTOD(IERSConventions.IERS_1996, true);
-            case VEIS1950 :
-                return FramesFactory.getVeis1950();
-            default :
-                // this should never happen
-                throw RuggedException.createInternalError(null);
+                case GCRF :
+                    return FramesFactory.getGCRF();
+                case EME2000 :
+                    return FramesFactory.getEME2000();
+                case MOD :
+                    return FramesFactory.getMOD(IERSConventions.IERS_1996);
+                case TOD :
+                    return FramesFactory.getTOD(IERSConventions.IERS_1996, true);
+                case VEIS1950 :
+                    return FramesFactory.getVeis1950();
+                default :
+                    // this should never happen
+                    throw RuggedException.createInternalError(null);
             }
         } catch (OrekitException oe) {
             throw new RuggedException(oe, oe.getSpecifier(), oe.getParts().clone());
@@ -919,15 +945,15 @@ public class RuggedBuilder {
         try {
             // set up the rotating frame
             switch (bodyRotatingFrame) {
-            case ITRF :
-                return FramesFactory.getITRF(IERSConventions.IERS_2010, true);
-            case ITRF_EQUINOX :
-                return FramesFactory.getITRFEquinox(IERSConventions.IERS_1996, true);
-            case GTOD :
-                return FramesFactory.getGTOD(IERSConventions.IERS_1996, true);
-            default :
-                // this should never happen
-                throw RuggedException.createInternalError(null);
+                case ITRF :
+                    return FramesFactory.getITRF(IERSConventions.IERS_2010, true);
+                case ITRF_EQUINOX :
+                    return FramesFactory.getITRFEquinox(IERSConventions.IERS_1996, true);
+                case GTOD :
+                    return FramesFactory.getGTOD(IERSConventions.IERS_1996, true);
+                default :
+                    // this should never happen
+                    throw RuggedException.createInternalError(null);
             }
         } catch (OrekitException oe) {
             throw new RuggedException(oe, oe.getSpecifier(), oe.getParts().clone());
@@ -944,19 +970,19 @@ public class RuggedBuilder {
 
         // set up the ellipsoid
         switch (ellipsoidID) {
-        case GRS80 :
-            return new OneAxisEllipsoid(6378137.0, 1.0 / 298.257222101, bodyFrame);
-        case WGS84 :
-            return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
-                                        Constants.WGS84_EARTH_FLATTENING,
-                                        bodyFrame);
-        case IERS96 :
-            return new OneAxisEllipsoid(6378136.49, 1.0 / 298.25645, bodyFrame);
-        case IERS2003 :
-            return new OneAxisEllipsoid(6378136.6, 1.0 / 298.25642, bodyFrame);
-        default :
-            // this should never happen
-            throw RuggedException.createInternalError(null);
+            case GRS80 :
+                return new OneAxisEllipsoid(6378137.0, 1.0 / 298.257222101, bodyFrame);
+            case WGS84 :
+                return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
+                                            Constants.WGS84_EARTH_FLATTENING,
+                                            bodyFrame);
+            case IERS96 :
+                return new OneAxisEllipsoid(6378136.49, 1.0 / 298.25645, bodyFrame);
+            case IERS2003 :
+                return new OneAxisEllipsoid(6378136.6, 1.0 / 298.25642, bodyFrame);
+            default :
+                // this should never happen
+                throw RuggedException.createInternalError(null);
         }
 
     }
@@ -974,19 +1000,19 @@ public class RuggedBuilder {
 
         // set up the algorithm
         switch (algorithmID) {
-        case DUVENHAGE :
-            return new DuvenhageAlgorithm(updater, maxCachedTiles, false);
-        case DUVENHAGE_FLAT_BODY :
-            return new DuvenhageAlgorithm(updater, maxCachedTiles, true);
-        case BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY :
-            return new BasicScanAlgorithm(updater, maxCachedTiles);
-        case CONSTANT_ELEVATION_OVER_ELLIPSOID :
-            return new ConstantElevationAlgorithm(constantElevation);
-        case IGNORE_DEM_USE_ELLIPSOID :
-            return new IgnoreDEMAlgorithm();
-        default :
-            // this should never happen
-            throw RuggedException.createInternalError(null);
+            case DUVENHAGE :
+                return new DuvenhageAlgorithm(updater, maxCachedTiles, false);
+            case DUVENHAGE_FLAT_BODY :
+                return new DuvenhageAlgorithm(updater, maxCachedTiles, true);
+            case BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY :
+                return new BasicScanAlgorithm(updater, maxCachedTiles);
+            case CONSTANT_ELEVATION_OVER_ELLIPSOID :
+                return new ConstantElevationAlgorithm(constantElevation);
+            case IGNORE_DEM_USE_ELLIPSOID :
+                return new IgnoreDEMAlgorithm();
+            default :
+                // this should never happen
+                throw RuggedException.createInternalError(null);
         }
 
     }
@@ -1011,7 +1037,8 @@ public class RuggedBuilder {
         }
         createInterpolatorIfNeeded();
         return new Rugged(createAlgorithm(algorithmID, tileUpdater, maxCachedTiles, constantElevation), ellipsoid,
-                          lightTimeCorrection, aberrationOfLightCorrection, scToBody, sensors, name);
+                          lightTimeCorrection, aberrationOfLightCorrection, atmosphericRefraction, scToBody, sensors, name);
+
     }
 
 }
diff --git a/src/main/java/org/orekit/rugged/errors/Dump.java b/src/main/java/org/orekit/rugged/errors/Dump.java
index a58960fd81f79ea23cca80c20243e711e1e98e72..13a7b36827913854a7d24045224c9838650cd329 100644
--- a/src/main/java/org/orekit/rugged/errors/Dump.java
+++ b/src/main/java/org/orekit/rugged/errors/Dump.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -149,17 +149,19 @@ class Dump {
      * @param los normalized line-of-sight in spacecraft frame
      * @param lightTimeCorrection flag for light time correction
      * @param aberrationOfLightCorrection flag for aberration of light correction
+     * @param refractionCorrection flag for refraction correction
      * @exception RuggedException if date cannot be converted to UTC
      */
     public void dumpDirectLocation(final AbsoluteDate date, final Vector3D position, final Vector3D los,
-                                   final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection)
+                                   final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
+                                   final boolean refractionCorrection)
         throws RuggedException {
         writer.format(Locale.US,
-                      "direct location: date %s position %22.15e %22.15e %22.15e los %22.15e %22.15e %22.15e lightTime %b aberration %b%n",
+                      "direct location: date %s position %22.15e %22.15e %22.15e los %22.15e %22.15e %22.15e lightTime %b aberration %b refraction %b %n",
                       convertDate(date),
                       position.getX(), position.getY(), position.getZ(),
                       los.getX(),      los.getY(),      los.getZ(),
-                      lightTimeCorrection, aberrationOfLightCorrection);
+                      lightTimeCorrection, aberrationOfLightCorrection, refractionCorrection);
     }
 
     /** Dump a direct location result.
diff --git a/src/main/java/org/orekit/rugged/errors/DumpManager.java b/src/main/java/org/orekit/rugged/errors/DumpManager.java
index 219fd343102dc62a3eac6af5db4e6adfbe0efa86..8c466f590afcde7cc5406a2896a53b7539e1f293 100644
--- a/src/main/java/org/orekit/rugged/errors/DumpManager.java
+++ b/src/main/java/org/orekit/rugged/errors/DumpManager.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -139,13 +139,16 @@ public class DumpManager {
      * @param los normalized line-of-sight in spacecraft frame
      * @param lightTimeCorrection flag for light time correction
      * @param aberrationOfLightCorrection flag for aberration of light correction
+     * @param refractionCorrection flag for refraction correction
      * @exception RuggedException if date cannot be converted to UTC
      */
     public static void dumpDirectLocation(final AbsoluteDate date, final Vector3D position, final Vector3D los,
-                                          final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection)
+                                          final boolean lightTimeCorrection, final boolean aberrationOfLightCorrection,
+                                          final boolean refractionCorrection)
         throws RuggedException {
         if (isActive()) {
-            DUMP.get().dumpDirectLocation(date, position, los, lightTimeCorrection, aberrationOfLightCorrection);
+            DUMP.get().dumpDirectLocation(date, position, los, lightTimeCorrection, aberrationOfLightCorrection,
+                    refractionCorrection);
         }
     }
 
diff --git a/src/main/java/org/orekit/rugged/errors/DumpReplayer.java b/src/main/java/org/orekit/rugged/errors/DumpReplayer.java
index 991f9e2a9041e2f4d1f032509ec3309e5e544805..cb7f67188269c2790134c3403c8d4d8916656ad2 100644
--- a/src/main/java/org/orekit/rugged/errors/DumpReplayer.java
+++ b/src/main/java/org/orekit/rugged/errors/DumpReplayer.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -69,6 +69,7 @@ import org.orekit.utils.ParameterDriver;
 
 /** Replayer for Rugged debug dumps.
  * @author Luc Maisonobe
+ * @author Guylaine Prat
  * @see DumpManager
  * @see Dump
  */
@@ -1267,6 +1268,33 @@ public class DumpReplayer {
 
         }
 
+        /** {@inheritDoc} */
+        @Override
+        public double getLine(final AbsoluteDate date) {
+
+            if (datation.size() < 2) {
+                return datation.get(0).getFirst();
+            }
+
+            // find entries bracketing the date
+            int sup = 0;
+            while (sup < datation.size() - 1) {
+                if (datation.get(sup).getSecond().compareTo(date) >= 0) {
+                    break;
+                }
+                ++sup;
+            }
+            final int inf = (sup == 0) ? sup++ : (sup - 1);
+
+            final double       lInf  = datation.get(inf).getFirst();
+            final AbsoluteDate dInf  = datation.get(inf).getSecond();
+            final double       lSup  = datation.get(sup).getFirst();
+            final AbsoluteDate dSup  = datation.get(sup).getSecond();
+            final double       alpha = date.durationFrom(dInf) / dSup.durationFrom(dInf);
+            return alpha * lSup + (1 - alpha) * lInf;
+
+        }
+
         /** Set a rate.
          * @param lineNumber line number
          * @param rate lines rate
diff --git a/src/main/java/org/orekit/rugged/errors/RuggedException.java b/src/main/java/org/orekit/rugged/errors/RuggedException.java
index 37e480f4f6072b7082db9a9346cf4435895081c4..47064b07d13589ee569036fc0afd895b5fc958cf 100644
--- a/src/main/java/org/orekit/rugged/errors/RuggedException.java
+++ b/src/main/java/org/orekit/rugged/errors/RuggedException.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/errors/RuggedExceptionWrapper.java b/src/main/java/org/orekit/rugged/errors/RuggedExceptionWrapper.java
index bf0552ea5e939f1eaca69408d7cac7aeae03caad..c6c0449e6ba3a6e92a8807e802b8445bd910fbf4 100644
--- a/src/main/java/org/orekit/rugged/errors/RuggedExceptionWrapper.java
+++ b/src/main/java/org/orekit/rugged/errors/RuggedExceptionWrapper.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/errors/RuggedMessages.java b/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
index 10bf1b9aad57f17e8874c83df6ef4449e8e53030..46310939861f93ed87d8d365f6115c8cdc800da2 100644
--- a/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
+++ b/src/main/java/org/orekit/rugged/errors/RuggedMessages.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -82,6 +82,7 @@ public enum RuggedMessages implements Localizable {
     DUPLICATED_PARAMETER_NAME("a different parameter with name {0} already exists"),
     INVALID_RUGGED_NAME("invalid rugged name."),
     UNSUPPORTED_REFINING_CONTEXT("refining using {0} rugged instance is not handled.");
+    NO_LAYER_DATA("no atmospheric layer data at altitude {0} (lowest altitude: {1})");
 
     // CHECKSTYLE: resume JavadocVariable check
 
diff --git a/src/main/java/org/orekit/rugged/intersection/BasicScanAlgorithm.java b/src/main/java/org/orekit/rugged/intersection/BasicScanAlgorithm.java
index 5d9f1512c6db1e5f097d8ac7928a77626227223a..4960edb92fd32fdcd99fbdf0cedb406bdb025d14 100644
--- a/src/main/java/org/orekit/rugged/intersection/BasicScanAlgorithm.java
+++ b/src/main/java/org/orekit/rugged/intersection/BasicScanAlgorithm.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/intersection/ConstantElevationAlgorithm.java b/src/main/java/org/orekit/rugged/intersection/ConstantElevationAlgorithm.java
index bb7706d36bbf36364521b292df329c20aa1a05f9..70be321b59e982ad67e71264923eaf5df66a8084 100644
--- a/src/main/java/org/orekit/rugged/intersection/ConstantElevationAlgorithm.java
+++ b/src/main/java/org/orekit/rugged/intersection/ConstantElevationAlgorithm.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/intersection/IgnoreDEMAlgorithm.java b/src/main/java/org/orekit/rugged/intersection/IgnoreDEMAlgorithm.java
index 045696b66bc99e24407578e73076eb53eb66529c..395b5f4c5dc6d8c7cdc3a524a92bb5fc3a4e1a2d 100644
--- a/src/main/java/org/orekit/rugged/intersection/IgnoreDEMAlgorithm.java
+++ b/src/main/java/org/orekit/rugged/intersection/IgnoreDEMAlgorithm.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/intersection/IntersectionAlgorithm.java b/src/main/java/org/orekit/rugged/intersection/IntersectionAlgorithm.java
index c22922cc01e3216c6b14eef3ea852169e96b22ec..17579c2f8375302fbbbe737a25327c9c219b4aff 100644
--- a/src/main/java/org/orekit/rugged/intersection/IntersectionAlgorithm.java
+++ b/src/main/java/org/orekit/rugged/intersection/IntersectionAlgorithm.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java b/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java
index 3a68e049b6d408e922df667d28f732c8175c2533..fd53dad8c4acfba87748e367cbd0f4396832e770 100644
--- a/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java
+++ b/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -78,6 +78,8 @@ public class DuvenhageAlgorithm implements IntersectionAlgorithm {
             // compute intersection with ellipsoid
             final NormalizedGeodeticPoint gp0 = ellipsoid.pointOnGround(position, los, 0.0);
 
+            // compute atmosphere deviation
+
             // locate the entry tile along the line-of-sight
             MinMaxTreeTile tile = cache.getTile(gp0.getLatitude(), gp0.getLongitude());
 
diff --git a/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java b/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java
index ca6e748e8448c1ea8b6d6fecd201c136b98c4e46..cbf5a47a84004b85dc0656de8b68434f076f58f7 100644
--- a/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java
+++ b/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileFactory.java b/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileFactory.java
index b07ebc6d509d74d970ea54f8aa94b413919c48e8..ba43270536aa96345ae749ddeae98b13a3bbcff8 100644
--- a/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileFactory.java
+++ b/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileFactory.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/linesensor/LineDatation.java b/src/main/java/org/orekit/rugged/linesensor/LineDatation.java
index db74c94bc58c8650c9e2e9fb5a3d2ef631eae3d6..ce0e754e145b1e8c4bfa26ffac36c4062abb2c51 100644
--- a/src/main/java/org/orekit/rugged/linesensor/LineDatation.java
+++ b/src/main/java/org/orekit/rugged/linesensor/LineDatation.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -21,6 +21,7 @@ import org.orekit.time.AbsoluteDate;
 /** Interface representing line datation model.
  * @see LinearLineDatation
  * @author Luc Maisonobe
+ * @author Guylaine Prat
  */
 public interface LineDatation {
 
@@ -30,6 +31,12 @@ public interface LineDatation {
      */
     AbsoluteDate getDate(double lineNumber);
 
+    /** Get the line for a given date.
+     * @param date date
+     * @return line number
+     */
+    double getLine(AbsoluteDate date);
+
     /** Get the rate of lines scanning.
      * @param lineNumber line number
      * @return rate of lines scanning (lines / seconds)
diff --git a/src/main/java/org/orekit/rugged/linesensor/LineSensor.java b/src/main/java/org/orekit/rugged/linesensor/LineSensor.java
index 284bd4b04183fabc99fa0ef1db0b48a4b261f9ba..5e1f1071a260c34046c1d5b2044ffaaf9532baf3 100644
--- a/src/main/java/org/orekit/rugged/linesensor/LineSensor.java
+++ b/src/main/java/org/orekit/rugged/linesensor/LineSensor.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -31,6 +31,7 @@ import org.orekit.utils.ParameterDriver;
 
 /** Line sensor model.
  * @author Luc Maisonobe
+ * @author Guylaine Prat
  */
 public class LineSensor {
 
@@ -160,6 +161,18 @@ public class LineSensor {
         return date;
     }
 
+    /** Get the line number.
+     * @param date date
+     * @return line number corresponding to date
+     * @exception RuggedException if date cannot be handled
+     */
+    public double getLine(final AbsoluteDate date)
+        throws RuggedException {
+        final double lineNumber = datationModel.getLine(date);
+        DumpManager.dumpSensorDatation(this, lineNumber, date);
+        return lineNumber;
+    }
+
     /** Get the rate of lines scanning.
      * @param lineNumber line number
      * @return rate of lines scanning (lines / seconds)
diff --git a/src/main/java/org/orekit/rugged/linesensor/LinearLineDatation.java b/src/main/java/org/orekit/rugged/linesensor/LinearLineDatation.java
index b31a96c0eb848111b4f78bdb791f6b0ae7442ff0..f23f35cf61bedede23228bf7da9ab3292e7327ac 100644
--- a/src/main/java/org/orekit/rugged/linesensor/LinearLineDatation.java
+++ b/src/main/java/org/orekit/rugged/linesensor/LinearLineDatation.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -24,6 +24,8 @@ import org.orekit.time.AbsoluteDate;
  * Instances of this class are guaranteed to be immutable.
  * </p>
  * @author Luc Maisonobe
+ * @author Guylaine Prat
+
  */
 public class LinearLineDatation implements LineDatation {
 
@@ -54,6 +56,12 @@ public class LinearLineDatation implements LineDatation {
         return referenceDate.shiftedBy((lineNumber - referenceLine) / rate);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double getLine(final AbsoluteDate date) {
+        return referenceLine + rate * date.durationFrom(referenceDate);
+    }
+
     /** {@inheritDoc} */
     @Override
     public double getRate(final double lineNumber) {
diff --git a/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java b/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java
index 29ebaf320ec590128183b77a421bf5edae261217..2f1f965611084814c945d9c970661711b3e65cb9 100644
--- a/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java
+++ b/src/main/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossing.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/linesensor/SensorPixel.java b/src/main/java/org/orekit/rugged/linesensor/SensorPixel.java
index ed38da4298df8feca85d404605b7b67fc4d40624..d614931dde790af9ecfb8cb4961c6244b146fc9f 100644
--- a/src/main/java/org/orekit/rugged/linesensor/SensorPixel.java
+++ b/src/main/java/org/orekit/rugged/linesensor/SensorPixel.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/linesensor/SensorPixelCrossing.java b/src/main/java/org/orekit/rugged/linesensor/SensorPixelCrossing.java
index 60b0de51884f92c2a1c3235b68abce8de4105961..2d0e3c13b7fe1fdd71a893f23d988081edae84dc 100644
--- a/src/main/java/org/orekit/rugged/linesensor/SensorPixelCrossing.java
+++ b/src/main/java/org/orekit/rugged/linesensor/SensorPixelCrossing.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/los/FixedRotation.java b/src/main/java/org/orekit/rugged/los/FixedRotation.java
index 5746f16757bdb6ab04452392571204c13a329e3c..84c396c87b38871f83704a71f5e7f6a1dd39b33c 100644
--- a/src/main/java/org/orekit/rugged/los/FixedRotation.java
+++ b/src/main/java/org/orekit/rugged/los/FixedRotation.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/los/LOSBuilder.java b/src/main/java/org/orekit/rugged/los/LOSBuilder.java
index 4857bd5873ae8d8b02b1fdc81f0bb966a261535c..7e70b0a3020b3425be79f1a917a4fa77d60eb838 100644
--- a/src/main/java/org/orekit/rugged/los/LOSBuilder.java
+++ b/src/main/java/org/orekit/rugged/los/LOSBuilder.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/los/LOSTransform.java b/src/main/java/org/orekit/rugged/los/LOSTransform.java
index 08c18fe7706b0c19b266455ee4777c3de925b53a..9ae19bfbae2c4f17ff53116f0a289d03a2a5bc0f 100644
--- a/src/main/java/org/orekit/rugged/los/LOSTransform.java
+++ b/src/main/java/org/orekit/rugged/los/LOSTransform.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/los/PolynomialRotation.java b/src/main/java/org/orekit/rugged/los/PolynomialRotation.java
index 588ba44c9141af0cb7f8cd73a9ddb431f0e7f366..bec75818f6defd97a6a95a28e947f834551c4494 100644
--- a/src/main/java/org/orekit/rugged/los/PolynomialRotation.java
+++ b/src/main/java/org/orekit/rugged/los/PolynomialRotation.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java b/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java
index 2a17c61b7689185b5b41ca52b7939780f9c13fb1..44b8f2908f0fade1179eec44f19af44ae6e43654 100644
--- a/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java
+++ b/src/main/java/org/orekit/rugged/los/TimeDependentLOS.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java b/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java
index 17b16449a17bcfe76c9465f2e0bd9c57cad97d8d..424c146cab7f124f6471964290f8f826d9c99ab0 100644
--- a/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java
+++ b/src/main/java/org/orekit/rugged/los/TimeIndependentLOSTransform.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -46,11 +46,9 @@ public interface TimeIndependentLOSTransform {
      * </p>
      * <p>
      * Note that in order for the partial derivatives to be properly set up, the
-     * {@link #setEstimatedParameters(double[], int, int) setEstimatedParameters}
-     * <em>must</em> have been called at least once before this method and its
-     * {@code start} parameter will be used to ensure the partial derivatives are
-     * ordered in the same way in the returned vector as they were in the set
-     * parameters.
+     * {@link org.orekit.utils.ParameterDriver#setSelected(boolean) setSelected}
+     * method must have been set to {@code true} for the various parameters returned
+     * by {@link #getParametersDrivers()} that should be estimated.
      * </p>
      * @param index los pixel index
      * @param los line-of-sight to transform
diff --git a/src/main/java/org/orekit/rugged/raster/SimpleTile.java b/src/main/java/org/orekit/rugged/raster/SimpleTile.java
index 2e6570a6b2e53d53b12120fffd2f0747820dfa47..a93018822f44a3d30a64e090fce1caedbee88c5e 100644
--- a/src/main/java/org/orekit/rugged/raster/SimpleTile.java
+++ b/src/main/java/org/orekit/rugged/raster/SimpleTile.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/raster/SimpleTileFactory.java b/src/main/java/org/orekit/rugged/raster/SimpleTileFactory.java
index 35a1035b7fc9e1a269bc9cf149dd30ec6f52a230..b079428b3dd2f75aecf56f438a1e197e216ec111 100644
--- a/src/main/java/org/orekit/rugged/raster/SimpleTileFactory.java
+++ b/src/main/java/org/orekit/rugged/raster/SimpleTileFactory.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/raster/Tile.java b/src/main/java/org/orekit/rugged/raster/Tile.java
index 135f5e614de18655e390415f3d111a5dd9301eb6..d1369d2edf888d3e46a4f437692c44652364de1d 100644
--- a/src/main/java/org/orekit/rugged/raster/Tile.java
+++ b/src/main/java/org/orekit/rugged/raster/Tile.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/raster/TileFactory.java b/src/main/java/org/orekit/rugged/raster/TileFactory.java
index 3070b7b7841b68139e21254f065d29b41c7a55d2..ed62cd34d9aeeac86c803e6d5a06ff0814788157 100644
--- a/src/main/java/org/orekit/rugged/raster/TileFactory.java
+++ b/src/main/java/org/orekit/rugged/raster/TileFactory.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/raster/TileUpdater.java b/src/main/java/org/orekit/rugged/raster/TileUpdater.java
index 2943f16a73946dd80dc7dd007017cad859725bea..c76c96a8e138fa53bc4a3efb996a087201f8202c 100644
--- a/src/main/java/org/orekit/rugged/raster/TileUpdater.java
+++ b/src/main/java/org/orekit/rugged/raster/TileUpdater.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -24,7 +24,8 @@ import org.orekit.rugged.errors.RuggedException;
  * the image processing mission-specific layer, thus allowing
  * the Rugged library to access the Digital Elevation Model data.
  * </p>
- *  * @author Luc Maisonobe
+ * @author Luc Maisonobe
+ * @author Guylaine Prat
  */
 public interface TileUpdater {
 
@@ -76,8 +77,8 @@ public interface TileUpdater {
            latitudeStep, longitudeStep, latitudeRows, longitudeColumns)} call.
      *   </li>
      * </ul>
-     * @param latitude latitude that must be covered by the tile
-     * @param longitude longitude that must be covered by the tile
+     * @param latitude latitude that must be covered by the tile (rad)
+     * @param longitude longitude that must be covered by the tile (rad)
      * @param tile to update
      * @exception RuggedException if tile cannot be updated
      */
diff --git a/src/main/java/org/orekit/rugged/raster/TilesCache.java b/src/main/java/org/orekit/rugged/raster/TilesCache.java
index 3cbe08d838fd4826d20453f0b9402286a839e0a8..11712412ffd77cb867ad05b306b74fa750f0fbde 100644
--- a/src/main/java/org/orekit/rugged/raster/TilesCache.java
+++ b/src/main/java/org/orekit/rugged/raster/TilesCache.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/raster/UpdatableTile.java b/src/main/java/org/orekit/rugged/raster/UpdatableTile.java
index 0e38a7f71dca2df99656daf25f32ac65797e1dda..606aec1b744a98580d878e8db8d3698223f1afa5 100644
--- a/src/main/java/org/orekit/rugged/raster/UpdatableTile.java
+++ b/src/main/java/org/orekit/rugged/raster/UpdatableTile.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -20,14 +20,15 @@ import org.orekit.rugged.errors.RuggedException;
 
 /** Interface representing one tile of a raster Digital Elevation Model.
  * @author Luc Maisonobe
+ * @author Guylaine Prat
  */
 public interface UpdatableTile {
 
     /** Set the tile global geometry.
-     * @param minLatitude minimum latitude
-     * @param minLongitude minimum longitude
-     * @param latitudeStep step in latitude (size of one raster element)
-     * @param longitudeStep step in longitude (size of one raster element)
+     * @param minLatitude minimum latitude (rad)
+     * @param minLongitude minimum longitude (rad)
+     * @param latitudeStep step in latitude (size of one raster element) (rad)
+     * @param longitudeStep step in longitude (size of one raster element) (rad)
      * @param latitudeRows number of latitude rows
      * @param longitudeColumns number of longitude columns
      * @exception RuggedException if tile is empty (zero rows or columns)
diff --git a/src/main/java/org/orekit/rugged/refining/measures/SensorToGroundMapping.java b/src/main/java/org/orekit/rugged/refining/measures/SensorToGroundMapping.java
index 266635760cb92c24d9651e2801205bf26f98b9bd..aa9f0e76e90f03653a5c60c8a5ff1dfac421bc1f 100644
--- a/src/main/java/org/orekit/rugged/refining/measures/SensorToGroundMapping.java
+++ b/src/main/java/org/orekit/rugged/refining/measures/SensorToGroundMapping.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/refraction/AtmosphericRefraction.java b/src/main/java/org/orekit/rugged/refraction/AtmosphericRefraction.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ff9e3e291b4eeb624fdf98802179b6d10b33fcc
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/refraction/AtmosphericRefraction.java
@@ -0,0 +1,46 @@
+/* Copyright 2013-2017 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.rugged.refraction;
+
+
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.intersection.IntersectionAlgorithm;
+import org.orekit.rugged.utils.NormalizedGeodeticPoint;
+
+/**
+ * Interface for atmospheric refraction model.
+ * @author Sergio Esteves
+ * @since 2.0
+ */
+public interface AtmosphericRefraction {
+
+    /** Apply correction to the intersected point with an atmospheric refraction model.
+     * @param satPos satellite position, in <em>body frame</em>
+     * @param satLos satellite line of sight, in <em>body frame</em>
+     * @param rawIntersection intersection point before refraction correction
+     * @param algorithm intersection algorithm
+     * @return corrected point with the effect of atmospheric refraction
+     * @throws RuggedException if there is no refraction data at altitude of rawIntersection or see
+     * {@link org.orekit.rugged.utils.ExtendedEllipsoid#pointAtAltitude(Vector3D, Vector3D, double)} or see
+     * {@link IntersectionAlgorithm#refineIntersection(ExtendedEllipsoid, Vector3D, Vector3D, NormalizedGeodeticPoint)}
+     */
+    NormalizedGeodeticPoint applyCorrection(Vector3D satPos, Vector3D satLos, NormalizedGeodeticPoint rawIntersection,
+                                            IntersectionAlgorithm algorithm)
+        throws RuggedException;
+
+}
diff --git a/src/main/java/org/orekit/rugged/refraction/ConstantRefractionLayer.java b/src/main/java/org/orekit/rugged/refraction/ConstantRefractionLayer.java
new file mode 100644
index 0000000000000000000000000000000000000000..cadc13dd9f34acf422777970fdac5aa1f75b8882
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/refraction/ConstantRefractionLayer.java
@@ -0,0 +1,49 @@
+/* Copyright 2013-2017 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.rugged.refraction;
+
+/**
+ * Class that represents a constant refraction layer to be used with {@link MultiLayerModel}.
+ * @author Sergio Esteves
+ * @since 2.0
+ */
+public class ConstantRefractionLayer {
+
+    /** lowest altitude of this layer. */
+    private final Double lowestAltitude;
+
+    /** refractive index of this layer. */
+    private final double refractiveIndex;
+
+    /** Simple constructor.
+     * @param lowestAltitude lowest altitude of the layer
+     * @param refractiveIndex refractive index of the layer
+     */
+    public ConstantRefractionLayer(final double lowestAltitude, final double refractiveIndex) {
+        this.lowestAltitude = lowestAltitude;
+        this.refractiveIndex = refractiveIndex;
+    }
+
+    public double getLowestAltitude() {
+        return lowestAltitude;
+    }
+
+    public double getRefractiveIndex() {
+        return refractiveIndex;
+    }
+
+}
diff --git a/src/main/java/org/orekit/rugged/refraction/MultiLayerModel.java b/src/main/java/org/orekit/rugged/refraction/MultiLayerModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a26f7db50258851524b6d517a06f0ea3a5fa4a2
--- /dev/null
+++ b/src/main/java/org/orekit/rugged/refraction/MultiLayerModel.java
@@ -0,0 +1,216 @@
+/* Copyright 2013-2017 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.rugged.refraction;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.util.FastMath;
+import org.orekit.bodies.GeodeticPoint;
+import org.orekit.errors.OrekitException;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedMessages;
+import org.orekit.rugged.intersection.IntersectionAlgorithm;
+import org.orekit.rugged.utils.ExtendedEllipsoid;
+import org.orekit.rugged.utils.NormalizedGeodeticPoint;
+
+/**
+ * Atmospheric refraction model based on multiple layers with associated refractive index.
+ * @author Sergio Esteves, Luc Maisonobe
+ * @since 2.0
+ */
+public class MultiLayerModel implements AtmosphericRefraction {
+
+    /** Observed body ellipsoid. */
+    private final ExtendedEllipsoid ellipsoid;
+
+    /** Constant refraction layers. */
+    private final List<ConstantRefractionLayer> refractionLayers;
+
+    /** Atmosphere lowest altitude. */
+    private final double atmosphereLowestAltitude;
+
+    /** Simple constructor.
+     * <p>
+     * This model uses a built-in set of layers.
+     * </p>
+     * @param ellipsoid the ellipsoid to be used.
+     */
+    public MultiLayerModel(final ExtendedEllipsoid ellipsoid) {
+        this.ellipsoid = ellipsoid;
+
+        refractionLayers = new ArrayList<ConstantRefractionLayer>(15);
+        refractionLayers.add(new ConstantRefractionLayer(100000.00, 1.000000));
+        refractionLayers.add(new ConstantRefractionLayer( 50000.00, 1.000000));
+        refractionLayers.add(new ConstantRefractionLayer( 40000.00, 1.000001));
+        refractionLayers.add(new ConstantRefractionLayer( 30000.00, 1.000004));
+        refractionLayers.add(new ConstantRefractionLayer( 23000.00, 1.000012));
+        refractionLayers.add(new ConstantRefractionLayer( 18000.00, 1.000028));
+        refractionLayers.add(new ConstantRefractionLayer( 14000.00, 1.000052));
+        refractionLayers.add(new ConstantRefractionLayer( 11000.00, 1.000083));
+        refractionLayers.add(new ConstantRefractionLayer(  9000.00, 1.000106));
+        refractionLayers.add(new ConstantRefractionLayer(  7000.00, 1.000134));
+        refractionLayers.add(new ConstantRefractionLayer(  5000.00, 1.000167));
+        refractionLayers.add(new ConstantRefractionLayer(  3000.00, 1.000206));
+        refractionLayers.add(new ConstantRefractionLayer(  1000.00, 1.000252));
+        refractionLayers.add(new ConstantRefractionLayer(     0.00, 1.000278));
+        refractionLayers.add(new ConstantRefractionLayer( -1000.00, 1.000306));
+
+        atmosphereLowestAltitude = refractionLayers.get(refractionLayers.size() - 1).getLowestAltitude();
+    }
+
+    /** Simple constructor.
+     * @param ellipsoid the ellipsoid to be used.
+     * @param refractionLayers the refraction layers to be used with this model (layers can be in any order).
+     */
+    public MultiLayerModel(final ExtendedEllipsoid ellipsoid, final List<ConstantRefractionLayer> refractionLayers) {
+        this.ellipsoid = ellipsoid;
+        this.refractionLayers = new ArrayList<>(refractionLayers);
+        Collections.sort(this.refractionLayers,
+            (l1, l2) -> Double.compare(l2.getLowestAltitude(), l1.getLowestAltitude()));
+        atmosphereLowestAltitude = this.refractionLayers.get(this.refractionLayers.size() - 1).getLowestAltitude();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NormalizedGeodeticPoint applyCorrection(final Vector3D satPos, final Vector3D satLos,
+                                                   final NormalizedGeodeticPoint rawIntersection,
+                                                   final IntersectionAlgorithm algorithm)
+        throws RuggedException {
+
+        try {
+            if (rawIntersection.getAltitude() < atmosphereLowestAltitude) {
+                throw new RuggedException(RuggedMessages.NO_LAYER_DATA, rawIntersection.getAltitude(),
+                        atmosphereLowestAltitude);
+            }
+
+            Vector3D pos = satPos;
+            Vector3D los = satLos.normalize();
+            double n1 = -1;
+            GeodeticPoint gp = ellipsoid.transform(satPos, ellipsoid.getBodyFrame(), null);
+
+            for (ConstantRefractionLayer refractionLayer : refractionLayers) {
+
+                if (refractionLayer.getLowestAltitude() > gp.getAltitude()) {
+                    continue;
+                }
+
+                final double n2 = refractionLayer.getRefractiveIndex();
+
+                if (n1 > 0) {
+
+                    // when we get here, we have already performed one iteration in the loop
+                    // so gp is the los intersection with the layers interface (it was a
+                    // point on ground at loop initialization, but is overridden at each iteration)
+
+                    // get new los by applying Snell's law at atmosphere layers interfaces
+                    // we avoid computing sequences of inverse-trigo/trigo/inverse-trigo functions
+                    // we just use linear algebra and square roots, it is faster and more accurate
+
+                    // at interface crossing, the interface normal is z, the local zenith direction
+                    // the ray direction (i.e. los) is u in the upper layer and v in the lower layer
+                    // v is in the (u, zenith) plane, so we can say
+                    //  (1) v = α u + β z
+                    // with α>0 as u and v are roughly in the same direction as the ray is slightly bent
+
+                    // let θ₁ be the los incidence angle at interface crossing
+                    // θ₁ = π - angle(u, zenith) is between 0 and π/2 for a downwards observation
+                    // let θ₂ be the exit angle at interface crossing
+                    // from Snell's law, we have n₁ sin θ₁ = n₂ sin θ₂ and θ₂ is also between 0 and π/2
+                    // we have:
+                    //   (2) u·z = -cos θ₁
+                    //   (3) v·z = -cos θ₂
+                    // combining equations (1), (2) and (3) and remembering z·z = 1 as z is normalized , we get
+                    //   (4) β = α cos θ₁ - cos θ₂
+                    // with all the expressions above, we can rewrite the fact v is normalized:
+                    //       1 = v·v
+                    //         = α² u·u + 2αβ u·z + β² z·z
+                    //         = α² - 2αβ cos θ₁ + β²
+                    //         = α² - 2α² cos² θ₁ + 2 α cos θ₁ cos θ₂ + α² cos² θ₁ - 2 α cos θ₁ cos θ₂ + cos² θ₂
+                    //         = α²(1 - cos² θ₁) + cos² θ₂
+                    // hence α² = (1 - cos² θ₂)/(1 - cos² θ₁)
+                    //          = sin² θ₂ / sin² θ₁
+                    // as α is positive, and both θ₁ and θ₂ are between 0 and π/2, we finally get
+                    //       α  = sin θ₂ / sin θ₁
+                    //   (5) α  = n₁/n₂
+                    // the α coefficient is independent from the incidence angle,
+                    // it depends only on the ratio of refractive indices!
+                    //
+                    // back to equation (4) and using again the fact θ₂ is between 0 and π/2, we can now write
+                    //       β = α cos θ₁ - cos θ₂
+                    //         = n₁/n₂ cos θ₁ - cos θ₂
+                    //         = n₁/n₂ cos θ₁ - √(1 - sin² θ₂)
+                    //         = n₁/n₂ cos θ₁ - √(1 - (n₁/n₂)² sin² θ₁)
+                    //         = n₁/n₂ cos θ₁ - √(1 - (n₁/n₂)² (1 - cos² θ₁))
+                    //         = n₁/n₂ cos θ₁ - √(1 - (n₁/n₂)² + (n₁/n₂)² cos² θ₁)
+                    //   (6) β = -k - √(k² - ζ)
+                    // where ζ = (n₁/n₂)² - 1 and k = n₁/n₂ u·z, which is negative, and close to -1 for
+                    // nadir observations. As we expect atmosphere models to have small transitions between
+                    // layers, we have to handle accurately the case where n₁/n₂ ≈ 1 so ζ ≈ 0. In this case,
+                    // a cancellation occurs inside the square root: √(k² - ζ) ≈ √k² ≈ -k (because k is negative).
+                    // So β ≈ -k + k ≈ 0 and another cancellation occurs, outside of the square root.
+                    // This means that despite equation (6) is mathematically correct, it is prone to numerical
+                    // errors when consecutive layers have close refractive indices. A better equivalent
+                    // expression is needed. The fact β is close to 0 in this case was expected because
+                    // equation (1) reads v = α u + β z, and α = n₁/n₂, so when n₁/n₂ ≈ 1, we have
+                    // α ≈ 1 and β ≈ 0, so v ≈ u: when two layers have similar refractive indices, the
+                    // propagation direction is almost unchanged.
+                    //
+                    // The first step for the accurate computation of β is to compute ζ = (n₁/n₂)² - 1
+                    // accurately and avoid a cancellation just after a division (which is the least accurate
+                    // of the four operations) and a squaring. We will simply use:
+                    //   ζ = (n₁/n₂)² - 1
+                    //     = (n₁ - n₂) (n₁ + n₂) / n₂²
+                    // The cancellation is still there, but it occurs in the subtraction n₁ - n₂, which are
+                    // the most accurate values we can get.
+                    // The second step for the accurate computation of β is to rewrite equation (6)
+                    // by both multiplying and dividing by the dual factor -k + √(k² - ζ):
+                    //     β = -k - √(k² - ζ)
+                    //       = (-k - √(k² - ζ)) * (-k + √(k² - ζ)) / (-k + √(k² - ζ))
+                    //       = (k² - (k² - ζ)) / (-k + √(k² - ζ))
+                    // (7) β = ζ / (-k + √(k² - ζ))
+                    // expression (7) is more stable numerically than expression (6), because when ζ ≈ 0
+                    // its denominator is about -2k, there are no cancellation anymore after the square root.
+                    // β is computed with the same accuracy as ζ
+                    final double alpha = n1 / n2;
+                    final double k     = alpha * Vector3D.dotProduct(los, gp.getZenith());
+                    final double zeta  = (n1 - n2) * (n1 + n2) / (n2 * n2);
+                    final double beta  = zeta / (FastMath.sqrt(k * k - zeta) - k);
+                    los = new Vector3D(alpha, los, beta, gp.getZenith());
+                }
+
+                if (rawIntersection.getAltitude() > refractionLayer.getLowestAltitude()) {
+                    break;
+                }
+
+                // get intersection point
+                pos = ellipsoid.pointAtAltitude(pos, los, refractionLayer.getLowestAltitude());
+                gp  = ellipsoid.transform(pos, ellipsoid.getBodyFrame(), null);
+
+                n1 = n2;
+
+            }
+
+            return algorithm.refineIntersection(ellipsoid, pos, los, rawIntersection);
+
+        } catch (OrekitException oe) {
+            throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
+        }
+    }
+}
diff --git a/src/main/java/org/orekit/rugged/utils/DSGenerator.java b/src/main/java/org/orekit/rugged/utils/DSGenerator.java
index 1ea0c5584583141278332d0fd32674c2f678f974..8cdbd1a2c65b5dfa14abdb675264bb7937ca4eaa 100644
--- a/src/main/java/org/orekit/rugged/utils/DSGenerator.java
+++ b/src/main/java/org/orekit/rugged/utils/DSGenerator.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/utils/ExtendedEllipsoid.java b/src/main/java/org/orekit/rugged/utils/ExtendedEllipsoid.java
index a90dd050a9b6fe0a418860e7ff779780444b1582..3324e0ad27f484055c5392090a4e4ec061443daf 100644
--- a/src/main/java/org/orekit/rugged/utils/ExtendedEllipsoid.java
+++ b/src/main/java/org/orekit/rugged/utils/ExtendedEllipsoid.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -47,7 +47,7 @@ public class ExtendedEllipsoid extends OneAxisEllipsoid {
     private final double b2;
 
     /** Simple constructor.
-     * @param ae equatorial radius
+     * @param ae equatorial radius (m)
      * @param f the flattening (f = (a-b)/a)
      * @param bodyFrame body frame related to body shape
      * @see org.orekit.frames.FramesFactory#getITRF(org.orekit.utils.IERSConventions, boolean)
@@ -75,13 +75,13 @@ public class ExtendedEllipsoid extends OneAxisEllipsoid {
     }
 
     /** Get point at some latitude along a pixel line of sight.
-     * @param position cell position (in body frame)
+     * @param position cell position (in body frame) (m)
      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
-     * @param latitude latitude with respect to ellipsoid
+     * @param latitude latitude with respect to ellipsoid (rad)
      * @param closeReference reference point used to select the closest solution
      * when there are two points at the desired latitude along the line, it should
-     * be close to los surface intersection
-     * @return point at latitude
+     * be close to los surface intersection (m)
+     * @return point at latitude (m)
      * @exception RuggedException if no such point exists
      */
     public Vector3D pointAtLatitude(final Vector3D position, final Vector3D los,
@@ -161,10 +161,10 @@ public class ExtendedEllipsoid extends OneAxisEllipsoid {
     }
 
     /** Get point at some longitude along a pixel line of sight.
-     * @param position cell position (in body frame)
+     * @param position cell position (in body frame) (m)
      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
-     * @param longitude longitude with respect to ellipsoid
-     * @return point at longitude
+     * @param longitude longitude with respect to ellipsoid (rad)
+     * @return point at longitude (m)
      * @exception RuggedException if no such point exists
      */
     public Vector3D pointAtLongitude(final Vector3D position, final Vector3D los, final double longitude)
@@ -186,10 +186,10 @@ public class ExtendedEllipsoid extends OneAxisEllipsoid {
     }
 
     /** Get point on ground along a pixel line of sight.
-     * @param position cell position (in body frame)
+     * @param position cell position (in body frame) (m)
      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
      * @param centralLongitude reference longitude lc such that the point longitude will
-     * be normalized between lc-Ï€ and lc+Ï€
+     * be normalized between lc-Ï€ and lc+Ï€ (rad)
      * @return point on ground
      * @exception RuggedException if no such point exists (typically line-of-sight missing body)
      */
@@ -212,10 +212,10 @@ public class ExtendedEllipsoid extends OneAxisEllipsoid {
     }
 
     /** Get point at some altitude along a pixel line of sight.
-     * @param position cell position (in body frame)
+     * @param position cell position (in body frame) (m)
      * @param los pixel line-of-sight, not necessarily normalized (in body frame)
-     * @param altitude altitude with respect to ellipsoid
-     * @return point at altitude
+     * @param altitude altitude with respect to ellipsoid (m)
+     * @return point at altitude (m)
      * @exception RuggedException if no such point exists (typically too negative altitude)
      */
     public Vector3D pointAtAltitude(final Vector3D position, final Vector3D los, final double altitude)
@@ -321,11 +321,11 @@ public class ExtendedEllipsoid extends OneAxisEllipsoid {
     }
 
     /** Transform a cartesian point to a surface-relative point.
-     * @param point cartesian point
+     * @param point cartesian point (m)
      * @param frame frame in which cartesian point is expressed
      * @param date date of the computation (used for frames conversions)
      * @param centralLongitude reference longitude lc such that the point longitude will
-     * be normalized between lc-Ï€ and lc+Ï€
+     * be normalized between lc-Ï€ and lc+Ï€ (rad)
      * @return point at the same location but as a surface-relative point
      * @exception OrekitException if point cannot be converted to body frame
      */
diff --git a/src/main/java/org/orekit/rugged/utils/MaxSelector.java b/src/main/java/org/orekit/rugged/utils/MaxSelector.java
index f4a8439c66f90fcad7c37b6c4d438b4eba591c3f..ada0e0b4c949f3728472480eea90f7267532b1d1 100644
--- a/src/main/java/org/orekit/rugged/utils/MaxSelector.java
+++ b/src/main/java/org/orekit/rugged/utils/MaxSelector.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/utils/MinSelector.java b/src/main/java/org/orekit/rugged/utils/MinSelector.java
index 98336235b5f48d731040cff031f730cbfb045bad..9c0fd0a7eeaa33985eb3ca16207b5d1d2ddb592c 100644
--- a/src/main/java/org/orekit/rugged/utils/MinSelector.java
+++ b/src/main/java/org/orekit/rugged/utils/MinSelector.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/utils/NormalizedGeodeticPoint.java b/src/main/java/org/orekit/rugged/utils/NormalizedGeodeticPoint.java
index e0458e44827b3d7edc939079851c1c3f53a69f48..bfae125cd300fffaf21f176181cab04a18197e65 100644
--- a/src/main/java/org/orekit/rugged/utils/NormalizedGeodeticPoint.java
+++ b/src/main/java/org/orekit/rugged/utils/NormalizedGeodeticPoint.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/utils/RoughVisibilityEstimator.java b/src/main/java/org/orekit/rugged/utils/RoughVisibilityEstimator.java
index 4548af93833ae489d58159cf19fdfe4640cc820e..300ab31de5ed20c9f0246dc67cac82d5acdba952 100644
--- a/src/main/java/org/orekit/rugged/utils/RoughVisibilityEstimator.java
+++ b/src/main/java/org/orekit/rugged/utils/RoughVisibilityEstimator.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/utils/Selector.java b/src/main/java/org/orekit/rugged/utils/Selector.java
index 6173090ec53ab09e53d76d8f083237fb4ca73be0..1fd39a10dc63569d99f0b2f243603f55e9bf9e96 100644
--- a/src/main/java/org/orekit/rugged/utils/Selector.java
+++ b/src/main/java/org/orekit/rugged/utils/Selector.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/main/java/org/orekit/rugged/utils/SpacecraftToObservedBody.java b/src/main/java/org/orekit/rugged/utils/SpacecraftToObservedBody.java
index 461613edbe7a280b030f1625fe1dcbe6f1a951ed..1e1e22047d2ff0c26b211f90f31f2e7e66c1efee 100644
--- a/src/main/java/org/orekit/rugged/utils/SpacecraftToObservedBody.java
+++ b/src/main/java/org/orekit/rugged/utils/SpacecraftToObservedBody.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -20,6 +20,7 @@ import org.hipparchus.util.FastMath;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.orekit.errors.OrekitException;
 import org.orekit.frames.Frame;
@@ -37,6 +38,7 @@ import org.orekit.utils.TimeStampedPVCoordinates;
 
 /** Provider for observation transforms.
  * @author Luc Maisonobe
+ * @author Guylaine Prat
  */
 public class SpacecraftToObservedBody implements Serializable {
 
@@ -110,7 +112,7 @@ public class SpacecraftToObservedBody implements Serializable {
             if (minPVDate.durationFrom(minDate) > overshootTolerance) {
                 throw new RuggedException(RuggedMessages.OUT_OF_TIME_RANGE, minDate, minPVDate, maxPVDate);
             }
-            if (maxDate.durationFrom(maxDate) > overshootTolerance) {
+            if (maxDate.durationFrom(maxPVDate) > overshootTolerance) {
                 throw new RuggedException(RuggedMessages.OUT_OF_TIME_RANGE, maxDate, minPVDate, maxPVDate);
             }
 
@@ -163,7 +165,7 @@ public class SpacecraftToObservedBody implements Serializable {
                 }
                 final TimeStampedAngularCoordinates interpolatedQuaternion =
                         TimeStampedAngularCoordinates.interpolate(aInterpolationDate, aFilter,
-                                                                  aCache.getNeighbors(aInterpolationDate));
+                                                                  aCache.getNeighbors(aInterpolationDate).collect(Collectors.toList()));
                 final TimeStampedAngularCoordinates quaternion = interpolatedQuaternion.shiftedBy(date.durationFrom(aInterpolationDate));
 
                 // store transform from spacecraft frame to inertial frame
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_da.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_da.utf8
new file mode 100644
index 0000000000000000000000000000000000000000..356f1ce7d811215cdf4e0e59e0d04fda33884f1b
--- /dev/null
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_da.utf8
@@ -0,0 +1,87 @@
+# internal error, contact maintenance at {0}
+INTERNAL_ERROR = intern fejl. Kontakt vedligeholdsansvarlig {0}
+
+# no data at indices [{0}, {1}], tile only covers from [0, 0] to [{2}, {3}] (inclusive)
+OUT_OF_TILE_INDICES = ingen data fundet på [{0}, {1}], flisen dækker kun fra [0, 0] til [{2}, {3}] (inkluderet)
+
+# no data at latitude {0} and longitude {1}, tile covers only latitudes {2} to {3} and longitudes {4} to {5}
+OUT_OF_TILE_ANGLES = ingen data på breddegrad {0} og længdegrad {1}, flisen dækker kun breddegraderne {2} til {3} og længdegraderne {4} til {5} 
+
+# no Digital Elevation Model data at latitude {0} and longitude {1}
+NO_DEM_DATA = ingen digital højdemodel ved breddegrad {0} og længdegrad {1}
+
+# the tile selected for latitude {0} and longitude {1} does not contain required point neighborhood
+TILE_WITHOUT_REQUIRED_NEIGHBORS_SELECTED = den valgte flise for breddegrad {0} og længdegrad {1} indeholder ikke påkrævet nabopunkt
+
+# date {0} is out of time span [{1}, {2}] (UTC)
+OUT_OF_TIME_RANGE = datoen {0} er udenfor perioden [{1}, {2}] (UTC)
+
+# general context has not been initialized (missing call to {0})
+UNINITIALIZED_CONTEXT = generel kontekst har ikke været initialiseret ({0} har ikke været kaldt)
+
+# tile is empty: {0} ⨉ {1}
+EMPTY_TILE = flisen er tom: {0} ⨉ {1}
+
+# unknown sensor {0}
+UNKNOWN_SENSOR = ukendt måler {0}
+
+# line-of-sight does not reach ground
+LINE_OF_SIGHT_DOES_NOT_REACH_GROUND = synslinjen når ikke bakken
+
+# line-of-sight never crosses latitude {0}
+LINE_OF_SIGHT_NEVER_CROSSES_LATITUDE = synslinjen krydser aldrig breddegrad {0}
+
+# line-of-sight never crosses longitude {0}
+LINE_OF_SIGHT_NEVER_CROSSES_LONGITUDE = synslinjen krydser aldrig længdegrad {0}
+
+# line never crosses altitude {0}
+LINE_OF_SIGHT_NEVER_CROSSES_ALTITUDE = linjen krydser aldrig højden {0}
+
+# line-of-sight enters the Digital Elevation Model behind spacecraft!
+DEM_ENTRY_POINT_IS_BEHIND_SPACECRAFT = synslinjen går ind i den digitale højdemodel bag rumfartøjet!
+
+# frame {0} does not match frame {1} from interpolator dump
+FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP = rammen {0} stemmer ikke overens med rammen {1} fra interpolar-dumpet
+
+# data is not an interpolator dump
+NOT_INTERPOLATOR_DUMP_DATA = dataen er ikke et interpolar-dump
+
+# number of estimated parameters mismatch, expected {0} got {1}
+ESTIMATED_PARAMETERS_NUMBER_MISMATCH = antal forventede parametre stemmer ikke overens, forventede {0} og modtog {1}
+
+# debug dump is already active for this thread
+DEBUG_DUMP_ALREADY_ACTIVE = debug-dump er allerede aktivt for denne tråd
+
+# unable to active debug dump with file {0}: {1}
+DEBUG_DUMP_ACTIVATION_ERROR = kunne ikke aktivere debug-dump med fil {0}: {1}
+
+# debug dump is not active for this thread
+DEBUG_DUMP_NOT_ACTIVE = debug-dump er ikke aktivt for denne tråd
+
+# cannot parse line {0}, file {1}: {2}
+CANNOT_PARSE_LINE = kan ikke fortolke linje {0}, fil {1}: {2}
+
+# light time correction redefined, line {0}, file {1}: {2}
+LIGHT_TIME_CORRECTION_REDEFINED = lystidskorrektion omdefineret, linje {0}, fil {1}: {2}
+
+# aberration of light correction redefined, line {0}, file {1}: {2}
+ABERRATION_OF_LIGHT_CORRECTION_REDEFINED = aberrationskorrektion omdefineret, linje {0}, fil {1}: {2}
+
+# tile {0} already defined, line {1}, file {2}: {3}
+TILE_ALREADY_DEFINED = flise {0} allerede defineret, linje {1}, fil {2}: {3}
+
+# unknown tile {0}, line {1}, file {2}: {3}
+UNKNOWN_TILE = ukendt flise {0}, linje {1}, fil {2}: {3}
+
+# no parameters have been selected for estimation
+NO_PARAMETERS_SELECTED =  <MISSING TRANSLATION>
+
+# no reference mappings for parameters estimation
+NO_REFERENCE_MAPPINGS =  <MISSING TRANSLATION>
+
+# a different parameter with name {0} already exists
+DUPLICATED_PARAMETER_NAME =  <MISSING TRANSLATION>
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = <MISSING TRANSLATION>
+
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
index 3982b7c81c5e907f82a7e02ac6bcf531a29afa84..9f978f98367def25fb9a5f38cac2d0e63d0f5ec5 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_de.utf8
@@ -72,7 +72,7 @@ TILE_ALREADY_DEFINED = <MISSING TRANSLATION>
 
 # unknown tile {0}, line {1}, file {2}: {3}
 UNKNOWN_TILE = <MISSING TRANSLATION>
- 
+
 # no parameters have been selected for estimation
 NO_PARAMETERS_SELECTED = <MISSING TRANSLATION>
 
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = <MISSING TRANSLATION>
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = <MISSING TRANSLATION>
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
index 75e9cab184c011ba22509e85755f2b3e8d259cd9..37ef4daf2f87b2df24588077beff39f6c64008ec 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = no reference mappings for parameters estimation
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = a different parameter with name {0} already exists
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = no atmospheric layer data at altitude {0} (lowest altitude: {1})
\ No newline at end of file
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
index 113d92b8786fb75794279a1573cd9c4d01a0f41d..f705a7740ec223fa04a9b58b89fd93c19de4b7c9 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_es.utf8
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = <MISSING TRANSLATION>
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = <MISSING TRANSLATION>
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = <MISSING TRANSLATION>
\ No newline at end of file
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
index 9854f68f8f2e0097ae438466f97025375cc40406..57372640220b8401363c7e8fbd4063e20519a24e 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = aucune projection de référence pour l''estimation des
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = il existe déjà un autre paramètre nommé {0}
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = pas de couche atmosphérique définie pour l''altitude {0} (altitude la plus basse : {1})
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
index 434a10aa9dc551d036a7d0d040ad334c6f74830e..64196159d46c932ca7adb895dbe06320dcd28867 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_gl.utf8
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = <MISSING TRANSLATION>
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = <MISSING TRANSLATION>
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = <MISSING TRANSLATION>
\ No newline at end of file
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
index 90dd36c0c710ac1a145bc7fac04a2f2e2d76ccd8..0b980381a7dc41449ea91975586d8925080b940a 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_it.utf8
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = <MISSING TRANSLATION>
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = <MISSING TRANSLATION>
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = <MISSING TRANSLATION>
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8
index ccca6d63ce2d2964c8c9554ec9b6e011ff462ecf..ac6749485160004cf136f1c24c3c3248e038d834 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_no.utf8
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = <MISSING TRANSLATION>
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = <MISSING TRANSLATION>
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = <MISSING TRANSLATION>
\ No newline at end of file
diff --git a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8 b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
index 7aa5bbb8f01de15484ebc31a9115d3099260da48..9ad508f2a1e289f9473bb8f56cd0cd40a59e01dd 100644
--- a/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
+++ b/src/main/resources/assets/org/orekit/rugged/RuggedMessages_ro.utf8
@@ -81,3 +81,6 @@ NO_REFERENCE_MAPPINGS = <MISSING TRANSLATION>
 
 # a different parameter with name {0} already exists
 DUPLICATED_PARAMETER_NAME = <MISSING TRANSLATION>
+
+# no atmospheric layer data at altitude {0} (lowest altitude: {1})
+NO_LAYER_DATA = <MISSING TRANSLATION>
\ No newline at end of file
diff --git a/src/site/markdown/building.md b/src/site/markdown/building.md
index bb120efde0268b6ba23c614613477f3f3de42d6c..4ffb5aa2a6bfc16a8dcb576a47bfeaf3ac425d4e 100644
--- a/src/site/markdown/building.md
+++ b/src/site/markdown/building.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md
index 1a1e16b2a3af08c9fd4500cacd7df6da1e68cd6c..12ae628f9bb99b66ddd8263bb79c6bc9e5eb59f2 100644
--- a/src/site/markdown/configuration.md
+++ b/src/site/markdown/configuration.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/contact.md b/src/site/markdown/contact.md
index dac36c8ca986cabcb1e8db85a82534f87eba7be3..5bbb40d7c993e8b4fe989080241c00b2f7eb0e44 100644
--- a/src/site/markdown/contact.md
+++ b/src/site/markdown/contact.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
@@ -51,5 +51,5 @@ please use the following address:
     31506 Toulouse CEDEX 5
     FRANCE
 
-    phone: +33 5-61-17-66-66 (ask for Luc Maisonobe or Aude Espesset)
+    phone: +33 5-61-17-66-66 (ask for Luc Maisonobe)
     fax:   +33 5-61-34-84-15
diff --git a/src/site/markdown/contributing.md b/src/site/markdown/contributing.md
index 61a38cd8667d78d91123693f727458569d78844e..7983d10c8f042d98d9841bd826fd893330afb45e 100644
--- a/src/site/markdown/contributing.md
+++ b/src/site/markdown/contributing.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/design/digital-elevation-model.md b/src/site/markdown/design/digital-elevation-model.md
index 8cee4460fe8f09ccdf01db13659c9e73ca224789..3efdf808b865debb0b8b0a0dc7a5b4eded396c48 100644
--- a/src/site/markdown/design/digital-elevation-model.md
+++ b/src/site/markdown/design/digital-elevation-model.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/design/overview.md b/src/site/markdown/design/overview.md
index 667087b1f0b497a0a4c6f57ab331f7fb2b9089cd..7d3c92d6feecb8ef616a8c81075fb69a393f17ee 100644
--- a/src/site/markdown/design/overview.md
+++ b/src/site/markdown/design/overview.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/design/preliminary-design.md b/src/site/markdown/design/preliminary-design.md
index 06095201b150d3d94a04c6d188b7beecc900871a..077de384fe515f8efeba5d2fe7366140d8693f09 100644
--- a/src/site/markdown/design/preliminary-design.md
+++ b/src/site/markdown/design/preliminary-design.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/design/technical-choices.md b/src/site/markdown/design/technical-choices.md
index 75b0e0d7db44516c136903672096f2825d9efd37..db864f3de8b8ec88f4ae3aae2e9d2f66b805975e 100644
--- a/src/site/markdown/design/technical-choices.md
+++ b/src/site/markdown/design/technical-choices.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/downloads.md b/src/site/markdown/downloads.md
index cf46772590b6526e0be5a7d8ae00a383a705489b..f4946b6b0d201da317ab36dc05617447e175485c 100644
--- a/src/site/markdown/downloads.md
+++ b/src/site/markdown/downloads.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/faq.md b/src/site/markdown/faq.md
index 7799c9ac1c1269c411abb25235023f9cea737a51..563190782cbbe01b4a4fa4fd43dc11b0efe19a56 100644
--- a/src/site/markdown/faq.md
+++ b/src/site/markdown/faq.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/guidelines.md b/src/site/markdown/guidelines.md
index 03e419e0ee620ece5748bdd291c9211f34156b23..d35f13efedfd5fda13a7f022f18ac53ed6581391 100644
--- a/src/site/markdown/guidelines.md
+++ b/src/site/markdown/guidelines.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 2fea0defc6f0bc2e01ed90f367d4e4b9146d6e38..4098a43b78d168bfacbe73c7e0d78b7a4d3ab526 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
@@ -64,7 +64,7 @@ Features
     * aberration of light correction (about 20m)
     * line-of-sight curvature in geodetic coordinates,
       (0m at nadir, 10m at 30° dive angle, hundreds of meters for skimming los)
-    * refraction not available in early 2016, but expected to be added soon
+    * atmospheric refraction
 
   * not limited to Earth
 
@@ -72,20 +72,14 @@ Features
 
   * Localized in several languages
 
+    * Danish
     * English
-
     * French
-
     * Galician
-
     * German
-
     * Italian
-
     * Norwegian
-
     * Romanian
-
     * Spanish
 
 Free software
diff --git a/src/site/markdown/sources.md b/src/site/markdown/sources.md
index 2197cf1a3814bfb91d8c7ba09066e092570196e6..95cbd732b5ca63ff681ba40d85cc4047e0bc4dd6 100644
--- a/src/site/markdown/sources.md
+++ b/src/site/markdown/sources.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/markdown/tutorials/direct-location-with-DEM.md b/src/site/markdown/tutorials/direct-location-with-DEM.md
index 0ce8751e921bbd9f8c12a1f938625ca23d4b630f..ab2ec7729d6395a412a1161d466d563c08b4773f 100644
--- a/src/site/markdown/tutorials/direct-location-with-DEM.md
+++ b/src/site/markdown/tutorials/direct-location-with-DEM.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
@@ -39,7 +39,7 @@ The class `VolcanicConeElevationUpdater` implements the interface `TileUpdater`
 
 Here's the source code of the class `VolcanicConeElevationUpdater` :
 
-import org.hipparchus.util.FastMath;
+    import org.hipparchus.util.FastMath;
     import org.orekit.rugged.raster.TileUpdater;
     import org.orekit.rugged.raster.UpdatableTile;
     import org.orekit.bodies.GeodeticPoint;
@@ -88,7 +88,7 @@ import org.hipparchus.util.FastMath;
 
 ### Important notes on DEM tiles :
 
-* Ground point elevation are obtained by bilinear interpolation between 4 neighbouring cells. There is no specific algorithm for border management. As a consequence, a point falling on the border of the tile is considered outside. DEM tiles must be overlapping by at least one line/column in all directions, in a similar way as for the SRTM DEMs. 
+* Ground point elevation are obtained by bilinear interpolation between 4 neighbouring cells. There is no specific algorithm for border management. As a consequence, a point falling on the border of the tile is considered outside. **DEM tiles must be overlapping by at least one line/column in all directions**, in a similar way as for the SRTM DEMs. 
 
 * In Rugged terminology, the minimum latitude and longitude correspond to the centre of the farthest Southwest cell of the DEM. Be careful if using GDAL to pass the correct information as there is half a pixel shift with respect to the lower left corner coordinates in gdalinfo.
 
@@ -169,4 +169,4 @@ In a similar way as in the first tutorial [DirectLocation](./direct-location.htm
     }
 
 ## Source code
-The source code is available in DirectLocationWithDEM.java
+The source code is available in DirectLocationWithDEM.java (package fr.cs.examples under src/tutorials)
diff --git a/src/site/markdown/tutorials/direct-location.md b/src/site/markdown/tutorials/direct-location.md
index 0866e25b8294db303cfcc5511e4fb4a3ec80e186..b9d042cd95fd55da4c9c94ed2cfbb5b5dcd7e49c 100644
--- a/src/site/markdown/tutorials/direct-location.md
+++ b/src/site/markdown/tutorials/direct-location.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
@@ -55,7 +55,7 @@ The raw viewing direction of pixel i with respect to the instrument is defined b
 
     List<Vector3D> rawDirs = new ArrayList<Vector3D>();
     for (int i = 0; i < 2000; i++) {
-        //20° field of view, 2000 pixels
+        // 20° field of view, 2000 pixels
         rawDirs.add(new Vector3D(0d, i*FastMath.toRadians(20)/2000d, 1d));
     }
 
@@ -161,7 +161,7 @@ Each attitude sample (quaternion, time) is added to the list,
 
 where, for instance, gpsDateAsString is set to "2009-12-11T10:49:55.899994"
 
-### Position and velocities
+### Positions and velocities
 
 
 Similarly the positions and velocities will be set in a list of `TimeStampedPVCoordinates`. Before being
@@ -307,11 +307,17 @@ Finally everything is set to do some real work. Let's try to locate a point on E
 for upper left point (first line, first pixel): 
 
     import org.orekit.bodies.GeodeticPoint;
-    Vector3D position = lineSensor.getPosition(); // This returns a zero vector since we set the relative position of the sensor w.r.T the satellite to 0.
+    Vector3D position = lineSensor.getPosition(); // This returns a zero vector since we set the relative position of the sensor w.r.t. the satellite to 0.
     AbsoluteDate firstLineDate = lineSensor.getDate(0);
     Vector3D los = lineSensor.getLOS(firstLineDate, 0);
     GeodeticPoint upLeftPoint = rugged.directLocation(firstLineDate, position, los);
 
+The line number can have negative values; the associated date must belong to the time span given when building Rugged with setTimeSpan(acquisitionStartDate, acquisitionStopDate,...).
+Otherwise a RuggedException will be thrown.
+
+The pixel number must be given between 0 and the (Sensor pixel size – 1); in our example between 0 and 1999.
+Otherwise an ArrayIndexOutOfBoundsException will be thrown.
+
 ## Source code 
 
-The source code is available in DirectLocation.java
+The source code is available in DirectLocation.java (package fr.cs.examples under src/tutorials)
diff --git a/src/site/markdown/tutorials/inverse-location.md b/src/site/markdown/tutorials/inverse-location.md
index 42bb4a550efcb57fdbd21899c08c91d6a88c99f4..d425200336a716c8ab2e5c4d6bb7626ba96866c1 100644
--- a/src/site/markdown/tutorials/inverse-location.md
+++ b/src/site/markdown/tutorials/inverse-location.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
@@ -19,10 +19,10 @@ The aim of this tutorial is to compute the inverse location of a point on Earth
 We will also explain how to find the date at which sensor sees a ground point, which is a kind of inverse location only focusing on date.
 
 ## Inverse location of a point on Earth
-The initialisation of Rugged is similar as in the [Direct location](direct-location.html) tutorial up to the addition of the lines sensor.
+The initialization of Rugged is similar as in the [Direct location](direct-location.html) tutorial up to rugged initialization..
 
 ### Point defined by its latitude, longitude and altitude
-Once Rugged initialised, one can compute the line number and the pixel number of a point defined by its Geodetic coordinates:
+Once Rugged initialized, one can compute the line number and the pixel number of a point defined by its Geodetic coordinates:
 
     import org.orekit.bodies.GeodeticPoint;
     import org.orekit.rugged.linesensor.SensorPixel;
@@ -30,7 +30,7 @@ Once Rugged initialised, one can compute the line number and the pixel number of
     SensorPixel sensorPixel = rugged.inverseLocation(sensorName, gp, minLine, maxLine);
 where minLine (maxLine, respectively) is the minimum line number for the search interval (maximum line number, respectively). 
 
-The inverse location will give the sensor pixel number and the associated line number seeing the point. In case the point cannot be seen between the prescribed line numbers, the return result is null. No exception will be thrown in this particular case.
+The inverse location will give the sensor pixel number and the associated line number seeing the point on ground. *In case the point cannot be seen between the prescribed line numbers, the return result is null. No exception will be thrown in this particular case*.
    
 ### Point defined by its latitude and longitude (no altitude)
 Similarly, one can compute the line number and the pixel number of a point defined solely by its latitude en longitude. The altitude will be determined automatically with the DEM.
@@ -38,7 +38,7 @@ Similarly, one can compute the line number and the pixel number of a point defin
      SensorPixel sensorPixel = rugged.inverseLocation(sensorName, latitude, longitude, minLine, maxLine);
 
 ## Date location 
-Once Rugged initialised, one can compute the date at which sensor sees a point on Earth.
+Once Rugged initialized, one can compute the date at which sensor sees a point on Earth.
 
 ### Point defined by its latitude, longitude and altitude
 For a point defined by its Geodetic coordinates:
@@ -50,5 +50,27 @@ Similarly, for a point defined solely by its latitude en longitude (altitude det
 
      AbsoluteDate dateLine = rugged.dateLocation(sensorName, latitude, longitude, minLine, maxLine);
 
+## Determine the min/max lines interval
+Rugged provides a way to determine a **very** rough estimation of the line using only the position-velocities of the satellite. It assumes the position-velocities are regular enough and without holes.
+
+     OneAxisEllipsoid oneAxisEllipsoid = ruggedBuilder.getEllipsoid();
+     Frame pvFrame = ruggedBuilder.getInertialFrame();
+     RoughVisibilityEstimator roughVisibilityEstimator= new RoughVisibilityEstimator(oneAxisEllipsoid, pvFrame, satellitePVList);
+
+One can compute the approximated line with the rough visibility estimator:
+
+     AbsoluteDate roughLineDate = roughVisibilityEstimator.estimateVisibility(gp);
+     double roughLine = lineSensor.getLine(roughLineDate);
+
+The result will never be null, but may be really far from reality if ground point is away from trajectory.
+With this rough line, taken some margin around (for instance 100), one can initialize the min/max lines as search boundaries for inverse location, taken into account sensor min and max lines:
+
+     int minLineRough = (int) FastMath.max(FastMath.floor(roughLine - margin), sensorMinLine);
+     int maxLineRough = (int) FastMath.min(FastMath.floor(roughLine + margin), sensorMaxLine);
+
+then one can compute the inverse location:
+
+     SensorPixel sensorPixel = rugged.inverseLocation(sensorName, gp, minLineRough, maxLineRough);
+
 ## Source code
-The source code is available in InverseLocation.java 
+The source code is available in InverseLocation.java (package fr.cs.examples under src/tutorials)
diff --git a/src/site/markdown/tutorials/tile-updater.md b/src/site/markdown/tutorials/tile-updater.md
index d2f3795ae804f12cbd3d36075270612ed6d9eafb..fdcf99c2f81685718658f45c4ce1e37b80d1e726 100644
--- a/src/site/markdown/tutorials/tile-updater.md
+++ b/src/site/markdown/tutorials/tile-updater.md
@@ -1,4 +1,4 @@
-<!--- Copyright 2013-2016 CS Systèmes d'Information
+<!--- Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/site.xml b/src/site/site.xml
index 9459b26f45a1dbb2b956837832f6797157328c86..a6fcc7850762b194ee55227dc910636c8f822455 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  Copyright 2013-2016 CS Systèmes d'Information
+  Copyright 2013-2017 CS Systèmes d'Information
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 6793d01721b392791ff3e144e736fce3a232af0e..374c0c4cd7b7d2ef1d330b3392b449d9743b16e1 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<!-- Copyright 2013-2016 CS Systèmes d'Information
+<!-- Copyright 2013-2017 CS Systèmes d'Information
   Licensed to CS Systèmes d'Information (CS) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
@@ -21,6 +21,18 @@
   </properties>
   <body>
     <release version="2.0" date="TBD" description="TTBD">
+      <action dev="luc" type="add" due-to="Lars Næsbye Christensen">
+        Added Danish translations.
+        Fixes issue #343.
+      </action>
+      <action dev="luc" type="update">
+        Updated dependency to Hipparchus 1.1, released on 2017, March 16th.
+      </action>
+      <action dev="luc" type="add" due-to="Sergio Esteves">
+        Added atmospheric refraction.
+        This work was done under the frame of ESA SOCIS 2016 program.
+        Implements feature #185.
+      </action>
       <action dev="luc" type="update">
         Replaced the ad-hoc parameters with Orekit ParameterDriver that are
         used in Orekit orbit determination feature.
diff --git a/src/test/java/org/orekit/rugged/TestUtils.java b/src/test/java/org/orekit/rugged/TestUtils.java
index a550b8b7ff522cd5154df25dc2312b7e70bff4bc..45c7a31f3c3f3d5d351488549e62ff78d7af1e21 100644
--- a/src/test/java/org/orekit/rugged/TestUtils.java
+++ b/src/test/java/org/orekit/rugged/TestUtils.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java b/src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java
index 6d12cfa78e04727e54b0c8dd51e06ee105bf15bf..a5c101e5edf2b15e22e28c6fc88f5061cadb8fb1 100644
--- a/src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java
+++ b/src/test/java/org/orekit/rugged/api/RuggedBuilderTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/api/RuggedTest.java b/src/test/java/org/orekit/rugged/api/RuggedTest.java
index eccea55b7711e9139c07bbacf9c9e3382a6c74df..600887db7f96376bf5ff7ad46710bc74cab76fcf 100644
--- a/src/test/java/org/orekit/rugged/api/RuggedTest.java
+++ b/src/test/java/org/orekit/rugged/api/RuggedTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -31,6 +31,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 
+import org.hipparchus.analysis.differentiation.DSFactory;
 import org.hipparchus.analysis.differentiation.DerivativeStructure;
 import org.hipparchus.analysis.differentiation.FiniteDifferencesDifferentiator;
 import org.hipparchus.analysis.differentiation.UnivariateDifferentiableFunction;
@@ -689,6 +690,14 @@ public class RuggedTest {
         checkDateLocation(2000, true,  false, 8.0e-7);
         checkDateLocation(2000, true,  true,  3.0e-6);
     }
+    
+    @Test
+    public void testLineDatation()
+        throws RuggedException, OrekitException, URISyntaxException {
+        checkLineDatation(2000, 7.0e-7);
+        checkLineDatation(10000, 8.0e-7);
+    }
+
 
     @Test
     public void testInverseLocNearLineEnd() throws OrekitException, RuggedException, URISyntaxException {
@@ -1044,28 +1053,28 @@ public class RuggedTest {
     public void testInverseLocationDerivativesWithoutCorrections()
         throws RuggedException, OrekitException {
         doTestInverseLocationDerivatives(2000, false, false,
-                                         7.0e-9, 4.0e-11, 2.0e-12, 7.0e-8);
+                                         8.0e-9, 3.0e-10, 5.0e-12, 9.0e-8);
     }
 
     @Test
     public void testInverseLocationDerivativesWithLightTimeCorrection()
         throws RuggedException, OrekitException {
         doTestInverseLocationDerivatives(2000, true, false,
-                                         3.0e-9, 9.0e-9, 3.0e-13, 7.0e-8);
+                                         3.0e-9, 9.0e-9, 2.1e-12, 9.0e-8);
     }
 
     @Test
     public void testInverseLocationDerivativesWithAberrationOfLightCorrection()
         throws RuggedException, OrekitException {
         doTestInverseLocationDerivatives(2000, false, true,
-                                         3.0e-10, 3.0e-10, 7.0e-13, 7.0e-8);
+                                         4.2e-10, 3.0e-10, 3.4e-12, 7.0e-8);
     }
 
     @Test
     public void testInverseLocationDerivativesWithAllCorrections()
         throws RuggedException, OrekitException {
         doTestInverseLocationDerivatives(2000, true, true,
-                                         3.0e-10, 5.0e-10, 7.0e-14, 7.0e-8);
+                                         3.0e-10, 5.0e-10, 2.0e-12, 7.0e-8);
     }
 
     private void doTestInverseLocationDerivatives(int dimension,
@@ -1160,6 +1169,7 @@ public class RuggedTest {
             Assert.assertEquals(1, result[0].getOrder());
 
             // check the partial derivatives
+            DSFactory factory = new DSFactory(1, 1);
             double h = 1.0e-6;
             FiniteDifferencesDifferentiator differentiator = new FiniteDifferencesDifferentiator(8, h);
 
@@ -1175,7 +1185,7 @@ public class RuggedTest {
                                     throw new RuggedExceptionWrapper(e);
                                 }
                             });
-            double dLdR = lineVSroll.value(new DerivativeStructure(1, 1, 0, 0.0)).getPartialDerivative(1);
+            double dLdR = lineVSroll.value(factory.variable(0, 0.0)).getPartialDerivative(1);
             Assert.assertEquals(dLdR, result[0].getPartialDerivative(1, 0), dLdR * lineDerivativeRelativeTolerance);
 
             UnivariateDifferentiableFunction lineVSpitch =
@@ -1190,7 +1200,7 @@ public class RuggedTest {
                                     throw new RuggedExceptionWrapper(e);
                                 }
                             });
-            double dLdP = lineVSpitch.value(new DerivativeStructure(1, 1, 0, 0.0)).getPartialDerivative(1);
+            double dLdP = lineVSpitch.value(factory.variable(0, 0.0)).getPartialDerivative(1);
             Assert.assertEquals(dLdP, result[0].getPartialDerivative(0, 1), dLdP * lineDerivativeRelativeTolerance);
 
             UnivariateDifferentiableFunction pixelVSroll =
@@ -1205,7 +1215,7 @@ public class RuggedTest {
                                     throw new RuggedExceptionWrapper(e);
                                 }
                             });
-            double dXdR = pixelVSroll.value(new DerivativeStructure(1, 1, 0, 0.0)).getPartialDerivative(1);
+            double dXdR = pixelVSroll.value(factory.variable(0, 0.0)).getPartialDerivative(1);
             Assert.assertEquals(dXdR, result[1].getPartialDerivative(1, 0), dXdR * pixelDerivativeRelativeTolerance);
 
             UnivariateDifferentiableFunction pixelVSpitch =
@@ -1220,7 +1230,7 @@ public class RuggedTest {
                                     throw new RuggedExceptionWrapper(e);
                                 }
                             });
-            double dXdP = pixelVSpitch.value(new DerivativeStructure(1, 1, 0, 0.0)).getPartialDerivative(1);
+            double dXdP = pixelVSpitch.value(factory.variable(0, 0.0)).getPartialDerivative(1);
             Assert.assertEquals(dXdP, result[1].getPartialDerivative(0, 1), dXdP * pixelDerivativeRelativeTolerance);
 
         } catch (InvocationTargetException | NoSuchMethodException |
@@ -1538,8 +1548,41 @@ public class RuggedTest {
                                                     -20 * gp2[dimension / 2].getLatitude()  + 21 * gp3[dimension / 2].getLatitude(),
                                                     -20 * gp2[dimension / 2].getLongitude() + 21 * gp3[dimension / 2].getLongitude(),
                                                     0, dimension));
-
+        
     }
 
+    private void checkLineDatation(int dimension, double maxLineError)
+    				throws RuggedException, OrekitException, URISyntaxException {
+
+    	String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+    	DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+
+    	AbsoluteDate crossing = new AbsoluteDate("2012-01-01T12:30:00.000", TimeScalesFactory.getUTC());
+
+    	// one line sensor
+    	// position: 1.5m in front (+X) and 20 cm above (-Z) of the S/C center of mass
+    	// los: swath in the (YZ) plane, looking at 50° roll, 2.6" per pixel
+    	Vector3D position = new Vector3D(1.5, 0, -0.2);
+    	TimeDependentLOS los = TestUtils.createLOSPerfectLine(new Rotation(Vector3D.PLUS_I,
+    			FastMath.toRadians(50.0),
+    			RotationConvention.VECTOR_OPERATOR).applyTo(Vector3D.PLUS_K),
+    			Vector3D.PLUS_I,
+    			FastMath.toRadians(dimension * 2.6 / 3600.0), dimension).build();
+
+    	// linear datation model: at reference time we get the middle line, and the rate is one line every 1.5ms
+    	LineDatation lineDatation = new LinearLineDatation(crossing, dimension / 2, 1.0 / 1.5e-3);
+    	int firstLine = 0;
+    	int lastLine  = dimension;
+    	LineSensor lineSensor = new LineSensor("line", lineDatation, position, los);
+    	AbsoluteDate minDate = lineSensor.getDate(firstLine).shiftedBy(-1.0);
+    	AbsoluteDate maxDate = lineSensor.getDate(lastLine).shiftedBy(+1.0);
+    	
+    	// Recompute the lines from the date with the appropriate shift of date
+    	double recomputedFirstLine = lineSensor.getLine(minDate.shiftedBy(+1.0));
+    	double recomputedLastLine = lineSensor.getLine(maxDate.shiftedBy(-1.0));
+
+    	Assert.assertEquals(firstLine, recomputedFirstLine, maxLineError);
+    	Assert.assertEquals(lastLine, recomputedLastLine, maxLineError);
+    }
 }
 
diff --git a/src/test/java/org/orekit/rugged/errors/DumpManagerTest.java b/src/test/java/org/orekit/rugged/errors/DumpManagerTest.java
index f431a7483812de1822098b9ec92bca1414411ea6..d2c4dd1b021f8d05480f86b36dc496da62d6f3fd 100644
--- a/src/test/java/org/orekit/rugged/errors/DumpManagerTest.java
+++ b/src/test/java/org/orekit/rugged/errors/DumpManagerTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/errors/DumpReplayerTest.java b/src/test/java/org/orekit/rugged/errors/DumpReplayerTest.java
index 3ee8eb45bef4e8d3f106884e39df7e0a1b9683d9..dae89afafdda5056c77c504addee4ce205c9dfa2 100644
--- a/src/test/java/org/orekit/rugged/errors/DumpReplayerTest.java
+++ b/src/test/java/org/orekit/rugged/errors/DumpReplayerTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -19,8 +19,10 @@ package org.orekit.rugged.errors;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -220,8 +222,9 @@ public class DumpReplayerTest {
 
             // split all data lines into fields
             final List<String[]> lines = new ArrayList<>();
-            try (FileReader fr = new FileReader(file);
-                 BufferedReader br = new BufferedReader(fr)) {
+            try (FileInputStream fis = new FileInputStream(file);
+                 InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
+                 BufferedReader br = new BufferedReader(isr)) {
                 br.lines().
                    filter(line -> {
                        String trimmed = line.trim();
@@ -233,8 +236,8 @@ public class DumpReplayerTest {
             // for each field of each line, delete the field and check parsing fails
             for (int i = 0; i < lines.size(); ++i) {
                 for (int j = 0; j < lines.get(i).length; ++j) {
-                    File corrupted = tempFolder.newFile();
-                    try (PrintWriter pw = new PrintWriter(corrupted)) {
+                    final File corrupted = tempFolder.newFile();
+                    try (PrintWriter pw = new PrintWriter(corrupted, "UTF-8")) {
                         for (int k = 0; k < lines.size(); ++k) {
                             for (int l = 0; l < lines.get(k).length; ++l) {
                                 if (k != i || l != j) {
diff --git a/src/test/java/org/orekit/rugged/errors/RuggedExceptionTest.java b/src/test/java/org/orekit/rugged/errors/RuggedExceptionTest.java
index 4bfc24cadc5e9c0b73ec15f2f324a32dc2585949..5afc61f63150a19f857c6b5db61f3b2aa59da698 100644
--- a/src/test/java/org/orekit/rugged/errors/RuggedExceptionTest.java
+++ b/src/test/java/org/orekit/rugged/errors/RuggedExceptionTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java b/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
index ed059b2efad41c430482e7e8cd546b84b3e87eac..9e96872742bd115899f483cecc200c3921f63242 100644
--- a/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
+++ b/src/test/java/org/orekit/rugged/errors/RuggedMessagesTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -28,14 +28,15 @@ import org.orekit.rugged.errors.RuggedMessages;
 
 public class RuggedMessagesTest {
 
+    private final String[] LANGUAGES_LIST = { "da", "de", "en", "es", "fr", "gl", "it", "no", "ro" } ;
     @Test
     public void testMessageNumber() {
-        Assert.assertEquals(28, RuggedMessages.values().length);
+        Assert.assertEquals(29, RuggedMessages.values().length);
     }
 
     @Test
     public void testAllKeysPresentInPropertiesFiles() {
-        for (final String language : new String[] { "de", "en", "es", "fr", "gl", "it", "no", "ro" } ) {
+        for (final String language : LANGUAGES_LIST) {
             ResourceBundle bundle =
                 ResourceBundle.getBundle("assets/org/orekit/rugged/RuggedMessages",
                                          new Locale(language), new RuggedMessages.UTF8Control());
@@ -55,7 +56,7 @@ public class RuggedMessagesTest {
 
     @Test
     public void testAllPropertiesCorrespondToKeys() {
-        for (final String language : new String[] { "de", "en", "es", "fr", "gl", "it", "no", "ro" } ) {
+        for (final String language : LANGUAGES_LIST) {
             ResourceBundle bundle =
                 ResourceBundle.getBundle("assets/org/orekit/rugged/RuggedMessages",
                                          new Locale(language), new RuggedMessages.UTF8Control());
@@ -98,7 +99,7 @@ public class RuggedMessagesTest {
 
     @Test
     public void testVariablePartsConsistency() {
-        for (final String language : new String[] { "de", "en", "es", "fr", "gl", "it", "no", "ro" } ) {
+        for (final String language : LANGUAGES_LIST) {
             Locale locale = new Locale(language);
             for (RuggedMessages message : RuggedMessages.values()) {
                 MessageFormat source     = new MessageFormat(message.getSourceString());
diff --git a/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java b/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java
index 54c769d85b4bfbce6a8be0fc265ba362cc217bc0..358458269b0acf2cb6bd276bd516becfc79b464a 100644
--- a/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java
+++ b/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/intersection/BasicScanAlgorithmTest.java b/src/test/java/org/orekit/rugged/intersection/BasicScanAlgorithmTest.java
index 9f930db138249d820eb7a9b441e848f61af04b93..443927f729c1bdd85dfbb8bc4cf737e134b79beb 100644
--- a/src/test/java/org/orekit/rugged/intersection/BasicScanAlgorithmTest.java
+++ b/src/test/java/org/orekit/rugged/intersection/BasicScanAlgorithmTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/intersection/ConstantElevationAlgorithmTest.java b/src/test/java/org/orekit/rugged/intersection/ConstantElevationAlgorithmTest.java
index 6d06b71a6d2124fcde6d90aa6b745a7a55bbc215..976d44f739829029c83a152396a00158e7733b42 100644
--- a/src/test/java/org/orekit/rugged/intersection/ConstantElevationAlgorithmTest.java
+++ b/src/test/java/org/orekit/rugged/intersection/ConstantElevationAlgorithmTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java b/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java
index 58364e78547cbcbfef58f428820867c2b682d76d..cf512a6c3468fefbf77ee8a86a9feca9a7bb37bb 100644
--- a/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java
+++ b/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileTest.java b/src/test/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileTest.java
index 10d23411799d311131b720a195b830dcb5dda716..77cf236a8bf6c5db6b40e8b9a1675303fb9bad3c 100644
--- a/src/test/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileTest.java
+++ b/src/test/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTileTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/linesensor/FixedRotationTest.java b/src/test/java/org/orekit/rugged/linesensor/FixedRotationTest.java
index 9d2e56bbd9785fbec61b2f54a1db32bee0c52115..895596a465d25085cb2701546b787a54f759f805 100644
--- a/src/test/java/org/orekit/rugged/linesensor/FixedRotationTest.java
+++ b/src/test/java/org/orekit/rugged/linesensor/FixedRotationTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import org.hipparchus.analysis.UnivariateMatrixFunction;
+import org.hipparchus.analysis.differentiation.DSFactory;
 import org.hipparchus.analysis.differentiation.DerivativeStructure;
 import org.hipparchus.analysis.differentiation.FiniteDifferencesDifferentiator;
 import org.hipparchus.analysis.differentiation.UnivariateDifferentiableMatrixFunction;
@@ -163,27 +164,29 @@ public class FixedRotationTest {
                 selected.add(driver);
             }
 
+            final DSFactory factoryS = new DSFactory(selected.size(), 1);
             DSGenerator generator = new DSGenerator() {
 
                 /** {@inheritDoc} */
                 @Override
-               public ParameterDriversList getSelected() {
-                      return selected;
+                public ParameterDriversList getSelected() {
+                    return selected;
                 }
 
                 /** {@inheritDoc} */
                 @Override
                 public DerivativeStructure constant(final double value) {
-                    return new DerivativeStructure(selected.getNbParams(), 1, value);
+                    return factoryS.constant(value);
                 }
 
                 /** {@inheritDoc} */
                 @Override
                 public DerivativeStructure variable(final ParameterDriver driver) {
                     int index = 0;
-                    for (ParameterDriver d : getSelected().getDrivers()) {
-                        if (d.getName().equals(driver.getName())) {
-                            return new DerivativeStructure(getSelected().getNbParams(), 1, index, driver.getValue());
+                    for (ParameterDriver d : getSelected()) {
+                        if (d == driver) {
+                            return factoryS.variable(index, driver.getValue());
+
                         }
                         ++index;
                     }
@@ -198,8 +201,9 @@ public class FixedRotationTest {
             FiniteDifferencesDifferentiator differentiator =
                             new FiniteDifferencesDifferentiator(4, 0.001);
             int index = 0;
-            for (final ParameterDriver driver : selected.getDrivers()) {
-                int[] orders = new int[selected.getNbParams()];
+            DSFactory factory11 = new DSFactory(1, 1);
+            for (final ParameterDriver driver : selected) {
+                int[] orders = new int[selected.size()];
                 orders[index] = 1;
                 UnivariateDifferentiableMatrixFunction f =
                                 differentiator.differentiate((UnivariateMatrixFunction) x -> {
@@ -216,7 +220,7 @@ public class FixedRotationTest {
                                         throw new OrekitExceptionWrapper(oe);
                                     }
                                 });
-                DerivativeStructure[][] mDS = f.value(new DerivativeStructure(1, 1, 0, driver.getValue()));
+                DerivativeStructure[][] mDS = f.value(factory11.variable(0, driver.getValue()));
                 for (int i = 0; i < raw.size(); ++i) {
                     Vector3D los = tdl.getLOS(i, AbsoluteDate.J2000_EPOCH);
                     FieldVector3D<DerivativeStructure> losDS =
diff --git a/src/test/java/org/orekit/rugged/linesensor/PolynomialRotationTest.java b/src/test/java/org/orekit/rugged/linesensor/PolynomialRotationTest.java
index e54319872ff4959587df5ec483c373de2601235e..c24a4f48ea51eb5830a196af714fd5a5e2851279 100644
--- a/src/test/java/org/orekit/rugged/linesensor/PolynomialRotationTest.java
+++ b/src/test/java/org/orekit/rugged/linesensor/PolynomialRotationTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import org.hipparchus.analysis.UnivariateMatrixFunction;
+import org.hipparchus.analysis.differentiation.DSFactory;
 import org.hipparchus.analysis.differentiation.DerivativeStructure;
 import org.hipparchus.analysis.differentiation.FiniteDifferencesDifferentiator;
 import org.hipparchus.analysis.differentiation.UnivariateDifferentiableMatrixFunction;
@@ -40,8 +41,8 @@ import org.junit.Test;
 import org.orekit.errors.OrekitException;
 import org.orekit.errors.OrekitExceptionWrapper;
 import org.orekit.rugged.errors.RuggedException;
-import org.orekit.rugged.los.PolynomialRotation;
 import org.orekit.rugged.los.LOSBuilder;
+import org.orekit.rugged.los.PolynomialRotation;
 import org.orekit.rugged.los.TimeDependentLOS;
 import org.orekit.rugged.utils.DSGenerator;
 import org.orekit.time.AbsoluteDate;
@@ -59,8 +60,8 @@ public class PolynomialRotationTest {
         for (int k = 0; k < 20; ++k) {
             LOSBuilder builder = new LOSBuilder(raw);
             builder.addTransform(new PolynomialRotation("identity",
-                                                   new Vector3D(rvg.nextVector()),
-                                                   AbsoluteDate.J2000_EPOCH, 0.0));
+                                                        new Vector3D(rvg.nextVector()),
+                                                        AbsoluteDate.J2000_EPOCH, 0.0));
             TimeDependentLOS tdl = builder.build();
             for (int i = 0; i < raw.size(); ++i) {
                 Assert.assertEquals(0.0,
@@ -154,23 +155,23 @@ public class PolynomialRotationTest {
             LOSBuilder builder = new LOSBuilder(raw);
 
             builder.addTransform(new PolynomialRotation("r1",
-                                                   new Vector3D(rvg.nextVector()),
-                                                   AbsoluteDate.J2000_EPOCH,
-                                                   2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
-                                                   1.0e-4 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3)));
+                                                        new Vector3D(rvg.nextVector()),
+                                                        AbsoluteDate.J2000_EPOCH,
+                                                        2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
+                                                        1.0e-4 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3)));
             builder.addTransform(new PolynomialRotation("r2",
-                                                   new Vector3D(rvg.nextVector()),
-                                                   AbsoluteDate.J2000_EPOCH,
-                                                   new PolynomialFunction(new double[] {
-                                                       2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
-                                                       1.0e-4 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
-                                                       1.0e-8 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3)
-                                                   })));
+                                                        new Vector3D(rvg.nextVector()),
+                                                        AbsoluteDate.J2000_EPOCH,
+                                                        new PolynomialFunction(new double[] {
+                                                                                             2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
+                                                                                             1.0e-4 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
+                                                                                             1.0e-8 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3)
+                                                        })));
             builder.addTransform(new PolynomialRotation("r3",
-                                                   new Vector3D(rvg.nextVector()),
-                                                   AbsoluteDate.J2000_EPOCH,
-                                                   2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
-                                                   1.0e-4 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3)));
+                                                        new Vector3D(rvg.nextVector()),
+                                                        AbsoluteDate.J2000_EPOCH,
+                                                        2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3),
+                                                        1.0e-4 * 2 * FastMath.PI * rng.nextNormalizedDouble() / FastMath.sqrt(3)));
             TimeDependentLOS tdl = builder.build();
             final ParameterDriversList selected = new ParameterDriversList();
             final List<ParameterDriver> list = tdl.getParametersDrivers().collect(Collectors.toList());
@@ -179,6 +180,7 @@ public class PolynomialRotationTest {
                 selected.add(driver);
             }
 
+            final DSFactory factoryS = new DSFactory(selected.size(), 1);
             DSGenerator generator = new DSGenerator() {
 
                 /** {@inheritDoc} */
@@ -190,16 +192,16 @@ public class PolynomialRotationTest {
                 /** {@inheritDoc} */
                 @Override
                 public DerivativeStructure constant(final double value) {
-                    return new DerivativeStructure(selected.getNbParams(), 1, value);
+                    return factoryS.constant(value);
                 }
 
                 /** {@inheritDoc} */
                 @Override
                 public DerivativeStructure variable(final ParameterDriver driver) {
                     int index = 0;
-                    for (ParameterDriver d : getSelected().getDrivers()) {
-                        if (d.getName().equals(driver.getName())) {
-                            return new DerivativeStructure(getSelected().getNbParams(), 1, index, driver.getValue());
+                    for (ParameterDriver d : getSelected()) {
+                        if (d == driver) {
+                            return factoryS.variable(index, driver.getValue());
                         }
                         ++index;
                     }
@@ -213,6 +215,7 @@ public class PolynomialRotationTest {
             FiniteDifferencesDifferentiator differentiator =
                             new FiniteDifferencesDifferentiator(4, 0.0001);
             int index = 0;
+            DSFactory factory11 = new DSFactory(1, 1);
             final AbsoluteDate date = AbsoluteDate.J2000_EPOCH.shiftedBy(7.0);
             for (final ParameterDriver driver : selected.getDrivers()) {
                 int[] orders = new int[selected.getNbParams()];
@@ -232,7 +235,7 @@ public class PolynomialRotationTest {
                                         throw new OrekitExceptionWrapper(oe);
                                     }
                                 });
-                DerivativeStructure[][] mDS = f.value(new DerivativeStructure(1, 1, 0, driver.getValue()));
+                DerivativeStructure[][] mDS = f.value(factory11.variable(0, driver.getValue()));
                 for (int i = 0; i < raw.size(); ++i) {
                     Vector3D los = tdl.getLOS(i, date);
                     FieldVector3D<DerivativeStructure> losDS = tdl.getLOSDerivatives(i, date, generator);
diff --git a/src/test/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossingTest.java b/src/test/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossingTest.java
index 22bde2a93d56b98cb1922a8279dee1487725d002..37d9a623dddd327ba425f667bb101b1d5ec537b1 100644
--- a/src/test/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossingTest.java
+++ b/src/test/java/org/orekit/rugged/linesensor/SensorMeanPlaneCrossingTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -129,7 +129,7 @@ public class SensorMeanPlaneCrossingTest {
 
     @Test
     public void testDerivativeWithoutCorrections() throws RuggedException, OrekitException {
-        doTestDerivative(false, false, 2.2e-11);
+        doTestDerivative(false, false, 3.1e-11);
     }
 
     @Test
@@ -205,7 +205,7 @@ public class SensorMeanPlaneCrossingTest {
             // the simple model from which reference results have been compute applies here
             Assert.assertEquals(refLine, result.getLine(), 7.0e-14 * refLine);
             Assert.assertEquals(0.0, result.getDate().durationFrom(refDate), 2.0e-13);
-            Assert.assertEquals(0.0, Vector3D.angle(los.get(refPixel), result.getTargetDirection()), 5.1e-15);
+            Assert.assertEquals(0.0, Vector3D.angle(los.get(refPixel), result.getTargetDirection()), 5.4e-15);
         }
 
         double deltaL = 0.5;
diff --git a/src/test/java/org/orekit/rugged/raster/CheckedPatternElevationUpdater.java b/src/test/java/org/orekit/rugged/raster/CheckedPatternElevationUpdater.java
index d780424dc2567b63632e5294104aae95621ce360..ab813e9996269df3c56a0c57ad002788175b73ac 100644
--- a/src/test/java/org/orekit/rugged/raster/CheckedPatternElevationUpdater.java
+++ b/src/test/java/org/orekit/rugged/raster/CheckedPatternElevationUpdater.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/raster/CliffsElevationUpdater.java b/src/test/java/org/orekit/rugged/raster/CliffsElevationUpdater.java
index 4480d10eaa3cd7a0811344a9187b7775e27c1cdc..f9d1befe2b3fb7647e550a8ac8af39b138efaa83 100644
--- a/src/test/java/org/orekit/rugged/raster/CliffsElevationUpdater.java
+++ b/src/test/java/org/orekit/rugged/raster/CliffsElevationUpdater.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/raster/CountingFactory.java b/src/test/java/org/orekit/rugged/raster/CountingFactory.java
index b8f73efd289a98ba816bc0bff3c5bb5e6336e0e6..dd903def9361ea0a549118e0b9b7bbe04e859385 100644
--- a/src/test/java/org/orekit/rugged/raster/CountingFactory.java
+++ b/src/test/java/org/orekit/rugged/raster/CountingFactory.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/raster/RandomLandscapeUpdater.java b/src/test/java/org/orekit/rugged/raster/RandomLandscapeUpdater.java
index 0ea5fc28e3b4b843f7025ea1096c9c6644ff1f71..0cf94ac1935cb1a4c33bcdd79fc7ce15f29017e4 100644
--- a/src/test/java/org/orekit/rugged/raster/RandomLandscapeUpdater.java
+++ b/src/test/java/org/orekit/rugged/raster/RandomLandscapeUpdater.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -32,15 +32,11 @@ public class RandomLandscapeUpdater implements TileUpdater {
 
     public RandomLandscapeUpdater(double baseH, double initialScale, double reductionFactor,
                                   long seed, double size, int n)
-        throws MathIllegalArgumentException {
+                                                  throws MathIllegalArgumentException {
 
         if (!ArithmeticUtils.isPowerOfTwo(n - 1)) {
-
-// TODO hipparchus migration : change to the appropriate message
-//            throw new MathIllegalArgumentException(LocalizedFormats.SIMPLE_MESSAGE,
-//                                                   "tile size must be a power of two plus one");
             throw new MathIllegalArgumentException(LocalizedCoreFormats.SIMPLE_MESSAGE,
-                                                   "tile size must be a power of two plus one");
+                            "tile size must be a power of two plus one");
         }
 
         this.size = size;
@@ -69,7 +65,7 @@ public class RandomLandscapeUpdater implements TileUpdater {
                                           i - span / 2, j + span / 2,
                                           i + span / 2, j - span / 2,
                                           i + span / 2, j + span / 2) +
-                                     scale * (random.nextDouble() - 0.5);
+                                    scale * (random.nextDouble() - 0.5);
                     heightMap[i][j] = middleH;
                 }
             }
@@ -82,7 +78,7 @@ public class RandomLandscapeUpdater implements TileUpdater {
                                           i + span / 2, j,
                                           i,            j - span / 2,
                                           i,            j + span / 2) +
-                                     scale * (random.nextDouble() - 0.5);
+                                    scale * (random.nextDouble() - 0.5);
                     heightMap[i][j] = middleH;
                     if (i == 0) {
                         heightMap[n - 1][j] = middleH;
@@ -101,8 +97,9 @@ public class RandomLandscapeUpdater implements TileUpdater {
 
     }
 
+    @Override
     public void updateTile(double latitude, double longitude, UpdatableTile tile)
-        throws RuggedException {
+                    throws RuggedException {
 
         double step         = size / (n - 1);
         double minLatitude  = size * FastMath.floor(latitude  / size);
@@ -118,9 +115,9 @@ public class RandomLandscapeUpdater implements TileUpdater {
 
     private double mean(int i1, int j1, int i2, int j2, int i3, int j3, int i4, int j4) {
         return (heightMap[(i1 + n) % n][(j1 + n) % n] +
-                heightMap[(i2 + n) % n][(j2 + n) % n] +
-                heightMap[(i3 + n) % n][(j3 + n) % n] +
-                heightMap[(i4 + n) % n][(j4 + n) % n]) / 4;
+                        heightMap[(i2 + n) % n][(j2 + n) % n] +
+                        heightMap[(i3 + n) % n][(j3 + n) % n] +
+                        heightMap[(i4 + n) % n][(j4 + n) % n]) / 4;
     }
 
 }
diff --git a/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java b/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java
index 6eb9ed18621e4c99b88df66aba25e0d6b18763bc..5d7f96db7ae10cd48cb0593c6071c1ffc274a24d 100644
--- a/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java
+++ b/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java b/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java
index 259462f8b455100abcff685f361a54a2c0fd0eb8..5dd97dac3b0f8cdd76c305dade8b16796b89d705 100644
--- a/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java
+++ b/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/raster/VolcanicConeElevationUpdater.java b/src/test/java/org/orekit/rugged/raster/VolcanicConeElevationUpdater.java
index e58c1bbd68780ddc68898c4cd230f33f44c56b1c..ef19659426eb3c2ef1779a8815868bcc0ed9523f 100644
--- a/src/test/java/org/orekit/rugged/raster/VolcanicConeElevationUpdater.java
+++ b/src/test/java/org/orekit/rugged/raster/VolcanicConeElevationUpdater.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/refraction/MultiLayerModelTest.java b/src/test/java/org/orekit/rugged/refraction/MultiLayerModelTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab2afbd269c3e04973c47b9c6b2682594380d660
--- /dev/null
+++ b/src/test/java/org/orekit/rugged/refraction/MultiLayerModelTest.java
@@ -0,0 +1,267 @@
+/* Copyright 2013-2017 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.rugged.refraction;
+
+import org.hipparchus.analysis.UnivariateFunction;
+import org.hipparchus.geometry.euclidean.threed.Rotation;
+import org.hipparchus.geometry.euclidean.threed.RotationConvention;
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+import org.orekit.errors.OrekitException;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedMessages;
+import org.orekit.rugged.intersection.AbstractAlgorithmTest;
+import org.orekit.rugged.intersection.IntersectionAlgorithm;
+import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm;
+import org.orekit.rugged.raster.TileUpdater;
+import org.orekit.rugged.utils.NormalizedGeodeticPoint;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MultiLayerModelTest extends AbstractAlgorithmTest {
+
+    @Test
+    public void testAlmostNadir() throws OrekitException, RuggedException {
+
+        setUpMayonVolcanoContext();
+        final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8);
+        final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098);
+        final Vector3D los = new Vector3D( 0.5127552821932051, -0.8254313129088879, -0.2361041470463311);
+        final NormalizedGeodeticPoint rawIntersection =
+                        algorithm.refineIntersection(earth, position, los,
+                                                     algorithm.intersection(earth, position, los));
+
+        MultiLayerModel model = new MultiLayerModel(earth);
+        NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm);
+        double distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection));
+
+        // this is almost a Nadir observation (LOS deviates between 1.4 and 1.6 degrees from vertical)
+        // so the refraction correction is small
+        Assert.assertEquals(0.0553796, distance, 1.0e-6);
+
+    }
+
+    @Test
+    public void testNoOpRefraction() throws OrekitException, RuggedException {
+
+        setUpMayonVolcanoContext();
+        final IntersectionAlgorithm   algorithm       = createAlgorithm(updater, 8);
+        final Vector3D                position        = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098);
+        final Vector3D                los             = los(position, FastMath.toRadians(50.0));
+        final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los,
+                                                                                     algorithm.intersection(earth, position, los));
+
+        MultiLayerModel model = new MultiLayerModel(earth);
+        NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm);
+        double distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection));
+
+        // a test with indices all set to 1.0 - correction must be zero
+        final int numberOfLayers = 16;
+        List<ConstantRefractionLayer> refractionLayers = new ArrayList<ConstantRefractionLayer>(numberOfLayers);
+        for(int i = numberOfLayers - 1; i >= 0; i--) {
+            refractionLayers.add(new ConstantRefractionLayer(i * 1.0e4, 1.0));
+        }
+        model = new MultiLayerModel(earth, refractionLayers);
+        correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm);
+        distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection));
+        Assert.assertEquals(0.0, distance, 1.7e-9);
+
+    }
+
+    @Test
+    public void testReversedAtmosphere()
+        throws OrekitException, RuggedException, IllegalArgumentException,
+               IllegalAccessException, NoSuchFieldException, SecurityException {
+
+        setUpMayonVolcanoContext();
+        final IntersectionAlgorithm   algorithm       = createAlgorithm(updater, 8);
+        final Vector3D                position        = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098);
+        final Vector3D                los             = los(position, FastMath.toRadians(50.0));
+        final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los,
+                                                                                     algorithm.intersection(earth, position, los));
+
+        MultiLayerModel baseModel = new MultiLayerModel(earth);
+        NormalizedGeodeticPoint correctedIntersection = baseModel.applyCorrection(position, los, rawIntersection, algorithm);
+
+        // an intentionally flawed atmosphere with refractive indices decreasing with altitude,
+        // that should exhibit a LOS bending upwards
+        Field refractionLayersField = MultiLayerModel.class.getDeclaredField("refractionLayers");
+        refractionLayersField.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        List<ConstantRefractionLayer> baseRefractionLayers =
+                        (List<ConstantRefractionLayer>) refractionLayersField.get(baseModel);
+        List<ConstantRefractionLayer> denserRefractionLayers = new ArrayList<>();
+        for (final ConstantRefractionLayer layer : baseRefractionLayers) {
+            denserRefractionLayers.add(new ConstantRefractionLayer(layer.getLowestAltitude(),
+                                                               1.0 / layer.getRefractiveIndex()));
+        }
+        MultiLayerModel reversedModel = new MultiLayerModel(earth, denserRefractionLayers);
+        NormalizedGeodeticPoint reversedIntersection = reversedModel.applyCorrection(position, los, rawIntersection, algorithm);
+        double anglePosRawIntersection       = Vector3D.angle(position, earth.transform(rawIntersection));
+        double anglePosCorrectedIntersection = Vector3D.angle(position, earth.transform(correctedIntersection));
+        double anglePosReversedIntersection  = Vector3D.angle(position, earth.transform(reversedIntersection));
+
+        // with regular atmosphere, the ray bends downwards,
+        // so the ground point is closer to the sub-satellite point than the raw intersection
+        Assert.assertTrue(anglePosCorrectedIntersection < anglePosRawIntersection);
+
+        // with reversed atmosphere, the ray bends upwards,
+        // so the ground point is farther from the sub-satellite point than the raw intersection
+        Assert.assertTrue(anglePosReversedIntersection > anglePosRawIntersection);
+
+        // the points are almost aligned (for distances around 20m, Earth curvature is small enough)
+        double dRawCorrected      = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection));
+        double dRawReversed       = Vector3D.distance(earth.transform(rawIntersection), earth.transform(reversedIntersection));
+        double dReversedCorrected = Vector3D.distance(earth.transform(reversedIntersection), earth.transform(correctedIntersection));
+        Assert.assertEquals(dRawCorrected + dRawReversed, dReversedCorrected, 1.0e-12 * dReversedCorrected);
+
+    }
+
+    @Test
+    public void testTwoAtmospheres()
+        throws OrekitException, RuggedException, IllegalArgumentException,
+               IllegalAccessException, NoSuchFieldException, SecurityException {
+
+        setUpMayonVolcanoContext();
+        final IntersectionAlgorithm   algorithm       = createAlgorithm(updater, 8);
+        final Vector3D                position        = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098);
+        final Vector3D                los             = los(position, FastMath.toRadians(50.0));
+        final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los,
+                                                                                     algorithm.intersection(earth, position, los));
+
+        // a comparison between two atmospheres, one more dense than the other and showing correction
+        // is more important with high indices
+        MultiLayerModel baseModel = new MultiLayerModel(earth);
+        Field refractionLayersField = MultiLayerModel.class.getDeclaredField("refractionLayers");
+        refractionLayersField.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        List<ConstantRefractionLayer> baseRefractionLayers =
+                        (List<ConstantRefractionLayer>) refractionLayersField.get(baseModel);
+        List<ConstantRefractionLayer> denserRefractionLayers = new ArrayList<>();
+        double previousBaseN   = 1.0;
+        double previousDenserN = 1.0;
+        double factor          = 1.00001;
+        for (final ConstantRefractionLayer layer : baseRefractionLayers) {
+            final double currentBaseN   = layer.getRefractiveIndex();
+            final double baseRatio      = currentBaseN / previousBaseN;
+            final double currentDenserN = previousDenserN * factor * baseRatio;
+            denserRefractionLayers.add(new ConstantRefractionLayer(layer.getLowestAltitude(),
+                                                                   currentDenserN));
+            previousBaseN   = currentBaseN;
+            previousDenserN = currentDenserN;
+        }
+        MultiLayerModel denserModel = new MultiLayerModel(earth, denserRefractionLayers);
+        NormalizedGeodeticPoint baseIntersection   = baseModel.applyCorrection(position, los, rawIntersection, algorithm);
+        NormalizedGeodeticPoint denserIntersection = denserModel.applyCorrection(position, los, rawIntersection, algorithm);
+        double baseDistance   = Vector3D.distance(earth.transform(rawIntersection),
+                                                  earth.transform(baseIntersection));
+        double denserDistance = Vector3D.distance(earth.transform(rawIntersection),
+                                                  earth.transform(denserIntersection));
+        Assert.assertTrue(denserDistance > baseDistance);
+
+    }
+
+    @Test
+    public void testMissingLayers() throws OrekitException, RuggedException {
+
+        setUpMayonVolcanoContext();
+        final IntersectionAlgorithm   algorithm       = createAlgorithm(updater, 8);
+        final Vector3D                position        = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098);
+        final Vector3D                los             = los(position, FastMath.toRadians(50.0));
+        final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los,
+                                                                                     algorithm.intersection(earth, position, los));
+        final double h = rawIntersection.getAltitude();
+
+        MultiLayerModel model = new MultiLayerModel(earth,
+                                                    Collections.singletonList(new ConstantRefractionLayer(h + 100.0,
+                                                                                                          1.5)));
+        try {
+            model.applyCorrection(position, los, rawIntersection, algorithm);
+            Assert.fail("an exception should have been thrown");
+        } catch (RuggedException re) {
+            Assert.assertEquals(RuggedMessages.NO_LAYER_DATA, re.getSpecifier());
+            Assert.assertEquals(h,         ((Double) re.getParts()[0]).doubleValue(), 1.0e-6);
+            Assert.assertEquals(h + 100.0, ((Double) re.getParts()[1]).doubleValue(), 1.0e-6);
+        }
+
+    }
+
+    @Test
+    public void testLayersBelowDEM() throws OrekitException, RuggedException {
+
+        setUpMayonVolcanoContext();
+        final IntersectionAlgorithm   algorithm       = createAlgorithm(updater, 8);
+        final Vector3D                position        = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098);
+        final Vector3D                los             = los(position, FastMath.toRadians(50.0));
+        final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los,
+                                                                                     algorithm.intersection(earth, position, los));
+
+        MultiLayerModel model = new MultiLayerModel(earth,
+                                                    Collections.singletonList(new ConstantRefractionLayer(rawIntersection.getAltitude() - 100.0,
+                                                                                                          1.5)));
+        NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm);
+        double distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection));
+        Assert.assertEquals(0.0, distance, 1.3e-9);
+
+    }
+
+    @Test
+    public void testDivingAngleChange() throws OrekitException, RuggedException {
+
+        setUpMayonVolcanoContext();
+        final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8);
+        final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098);
+        AtmosphericRefraction model = new MultiLayerModel(earth);
+
+        // deviation should increase from 0 to about 17m
+        // as the angle between los and nadir increases from 0 to 50 degrees
+        // the reference model below has been obtained by fitting the test results themselves
+        // it is NOT considered a full featured model, it's just a helper function for this specific test
+        UnivariateFunction reference = alpha -> 1.17936 * FastMath.tan((2.94613 - 1.40162 * alpha) * alpha);
+
+        for (double alpha = 0; alpha < FastMath.toRadians(50.0); alpha += 0.1) {
+            final Vector3D rotatingLos = los(position, alpha);
+            final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, rotatingLos,
+                                                                                         algorithm.intersection(earth, position, rotatingLos));
+
+            final NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, rotatingLos, rawIntersection, algorithm);
+            final double distance = Vector3D.distance(earth.transform(rawIntersection),
+                                                      earth.transform(correctedIntersection));
+            Assert.assertEquals(reference.value(alpha), distance, 0.12);
+        }
+
+    }
+
+    private Vector3D los(final Vector3D position, final double angleFromNadir)
+        throws OrekitException {
+        final Vector3D nadir       = earth.transform(position, earth.getBodyFrame(), null).getNadir();
+        final Rotation losRotation = new Rotation(nadir.orthogonal(), angleFromNadir,
+                                                  RotationConvention.VECTOR_OPERATOR);
+        return losRotation.applyTo(nadir);
+    }
+
+    @Override
+    protected IntersectionAlgorithm createAlgorithm(TileUpdater updater, int maxCachedTiles) {
+        return new DuvenhageAlgorithm(updater, maxCachedTiles, false);
+    }
+
+}
diff --git a/src/test/java/org/orekit/rugged/utils/ExtendedEllipsoidTest.java b/src/test/java/org/orekit/rugged/utils/ExtendedEllipsoidTest.java
index 787a4e3526b772e15159a88f01f882eb09dde8ea..bbd254875d46b8f64532cea0722f0054d3fccb90 100644
--- a/src/test/java/org/orekit/rugged/utils/ExtendedEllipsoidTest.java
+++ b/src/test/java/org/orekit/rugged/utils/ExtendedEllipsoidTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -132,7 +132,7 @@ public class ExtendedEllipsoidTest {
 
         Vector3D pPlus = ellipsoid.pointAtLatitude(p, d, latitude, new Vector3D(1, p, +2.0e7, d));
         GeodeticPoint gpPlus = ellipsoid.transform(pPlus, ellipsoid.getBodyFrame(), null);
-        Assert.assertEquals(latitude, gpPlus.getLatitude(), 3.0e-16);
+        Assert.assertEquals(latitude, gpPlus.getLatitude(), 4.0e-16);
         Assert.assertEquals(20646364.047, Vector3D.dotProduct(d, pPlus.subtract(p)), 0.001);
 
         Vector3D pMinus = ellipsoid.pointAtLatitude(p, d, latitude, new Vector3D(1, p, -3.0e7, d));
diff --git a/src/test/java/org/orekit/rugged/utils/RoughVisibilityEstimatorTest.java b/src/test/java/org/orekit/rugged/utils/RoughVisibilityEstimatorTest.java
index 178cf7a9a611326730af4d3056e7de65c3bd5c23..f9af850afa9483a515e93adff22b8fa6f3a11fa4 100644
--- a/src/test/java/org/orekit/rugged/utils/RoughVisibilityEstimatorTest.java
+++ b/src/test/java/org/orekit/rugged/utils/RoughVisibilityEstimatorTest.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
diff --git a/src/test/java/org/orekit/rugged/utils/SpacecraftToObservedBodyTest.java b/src/test/java/org/orekit/rugged/utils/SpacecraftToObservedBodyTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..439a71770867ee3d84cee49ee05ab88e2b5d5a3d
--- /dev/null
+++ b/src/test/java/org/orekit/rugged/utils/SpacecraftToObservedBodyTest.java
@@ -0,0 +1,153 @@
+/* Copyright 2013-2017 CS Systèmes d'Information
+ * Licensed to CS Systèmes d'Information (CS) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * CS licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.orekit.rugged.utils;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.hipparchus.geometry.euclidean.threed.Vector3D;
+import org.hipparchus.util.FastMath;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.orekit.bodies.BodyShape;
+import org.orekit.data.DataProvidersManager;
+import org.orekit.data.DirectoryCrawler;
+import org.orekit.errors.OrekitException;
+import org.orekit.frames.FramesFactory;
+import org.orekit.orbits.Orbit;
+import org.orekit.rugged.TestUtils;
+import org.orekit.rugged.errors.RuggedException;
+import org.orekit.rugged.errors.RuggedMessages;
+import org.orekit.rugged.linesensor.LineSensor;
+import org.orekit.rugged.linesensor.LinearLineDatation;
+import org.orekit.rugged.los.LOSBuilder;
+import org.orekit.time.AbsoluteDate;
+import org.orekit.utils.AngularDerivativesFilter;
+import org.orekit.utils.CartesianDerivativesFilter;
+import org.orekit.utils.Constants;
+import org.orekit.utils.TimeStampedAngularCoordinates;
+import org.orekit.utils.TimeStampedPVCoordinates;
+
+
+@RunWith(Parameterized.class)
+public class SpacecraftToObservedBodyTest {
+
+	@Parameters
+	public static Collection<Object[]> data() {
+		return Arrays.asList(new Object[][] {
+			// Run multiple times the tests, constructing this class with the following parameters
+			{ +10, +1., -1., +1}, { -1., -10., -1., +1}, { -1., +1., +15., +1}, { -1., +1., -1., -15.}
+		});
+	}
+	   
+	// configuration of the run : shift of date for PV and Q (min and max values)
+	private double shiftPVmin;
+	private double shiftPVmax;
+	private double shiftQmin;
+	private double shiftQmax;
+
+	
+    @Test
+    public void testIssue256() throws RuggedException, OrekitException {
+        
+        AbsoluteDate minSensorDate = sensor.getDate(0);
+        AbsoluteDate maxSensorDate = sensor.getDate(2000);
+        
+        AbsoluteDate minPVdate = minSensorDate.shiftedBy(this.shiftPVmin);
+        AbsoluteDate maxPVdate = maxSensorDate.shiftedBy(this.shiftPVmax);
+        List<TimeStampedPVCoordinates> pvList = TestUtils.orbitToPV(orbit, earth, minPVdate, maxPVdate, 0.25);
+        
+        AbsoluteDate minQdate = minSensorDate.shiftedBy(this.shiftQmin);
+        AbsoluteDate maxQdate = maxSensorDate.shiftedBy(this.shiftQmax);
+        List<TimeStampedAngularCoordinates> qList = TestUtils.orbitToQ(orbit, earth, minQdate, maxQdate, 0.25);
+        
+        try {
+        
+    	new SpacecraftToObservedBody(FramesFactory.getEME2000(), earth.getBodyFrame(),
+                minSensorDate, maxSensorDate, 0.01,
+                5.0,
+                pvList,
+                8, CartesianDerivativesFilter.USE_PV,
+                qList,
+                2, AngularDerivativesFilter.USE_R);
+        Assert.fail("an exception should have been thrown");
+    	
+        } catch (RuggedException re){
+            Assert.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
+        }        	
+    }
+
+    
+	public SpacecraftToObservedBodyTest(double shiftPVmin, double shiftPVmax, double shiftQmin, double shiftQmax) {
+		super();
+		this.shiftPVmin = shiftPVmin;
+		this.shiftPVmax = shiftPVmax;
+		this.shiftQmin = shiftQmin;
+		this.shiftQmax = shiftQmax;
+	}
+	
+
+    @Before
+    public void setUp() {
+        try {
+
+            String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
+            DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path)));
+
+            earth = TestUtils.createEarth();
+            orbit = TestUtils.createOrbit(Constants.EIGEN5C_EARTH_MU);
+            
+            // build lists of pixels regularly spread on a perfect plane
+            final Vector3D position  = new Vector3D(1.5, Vector3D.PLUS_I);
+            final Vector3D normal    = Vector3D.PLUS_I;
+            final Vector3D fovCenter = Vector3D.PLUS_K;
+            final Vector3D cross     = Vector3D.crossProduct(normal, fovCenter);
+            
+            final List<Vector3D> los = new ArrayList<Vector3D>();
+            for (int i = -1000; i <= 1000; ++i) {
+                final double alpha = i * 0.17 / 1000;
+                los.add(new Vector3D(FastMath.cos(alpha), fovCenter, FastMath.sin(alpha), cross));
+            }
+            sensor = new LineSensor("perfect line", new LinearLineDatation(AbsoluteDate.J2000_EPOCH, 0.0, 1.0 / 1.5e-3),
+                                    position, new LOSBuilder(los).build());
+            
+
+        } catch (OrekitException oe) {
+            Assert.fail(oe.getLocalizedMessage());
+        } catch (URISyntaxException use) {
+            Assert.fail(use.getLocalizedMessage());
+        }
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+	private BodyShape  earth = null;
+	private Orbit      orbit = null;
+	private LineSensor sensor = null;
+	
+}
diff --git a/src/tutorials/java/fr/cs/examples/DirectLocation.java b/src/tutorials/java/fr/cs/examples/DirectLocation.java
index 5d5044a25d5013c12d960b86cba98efa77f5f6bb..e8d3d33e0a3a2ba8dbc3a88db2cbb9add92931e3 100644
--- a/src/tutorials/java/fr/cs/examples/DirectLocation.java
+++ b/src/tutorials/java/fr/cs/examples/DirectLocation.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -64,10 +64,14 @@ public class DirectLocation {
             File orekitData = new File(home, "orekit-data");
             DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(orekitData));
 
+            // Sensor's definition 
+            // ===================
+            // Line of sight
+            // -------------            
             // The raw viewing direction of pixel i with respect to the instrument is defined by the vector:
             List<Vector3D> rawDirs = new ArrayList<Vector3D>();
             for (int i = 0; i < 2000; i++) {
-                //20° field of view, 2000 pixels
+                // 20° field of view, 2000 pixels
                 rawDirs.add(new Vector3D(0d, i*FastMath.toRadians(20)/2000d, 1d));
             }
 
@@ -78,14 +82,23 @@ public class DirectLocation {
 
             TimeDependentLOS lineOfSight = losBuilder.build();
 
+            // Datation model 
+            // --------------
             // We use Orekit for handling time and dates, and Rugged for defining the datation model:
             TimeScale gps = TimeScalesFactory.getGPS();
             AbsoluteDate absDate = new AbsoluteDate("2009-12-11T16:59:30.0", gps);
             LinearLineDatation lineDatation = new LinearLineDatation(absDate, 1d, 20);
 
-            // With the LOS and the datation now defined , we can initialize a line sensor object in Rugged:
+            // Line sensor
+            // -----------
+            // With the LOS and the datation now defined, we can initialize a line sensor object in Rugged:
             LineSensor lineSensor = new LineSensor("mySensor", lineDatation, Vector3D.ZERO, lineOfSight);
 
+            // Satellite position, velocity and attitude
+            // =========================================
+
+            // Reference frames
+            // ----------------
             // In our application, we simply need to know the name of the frames we are working with. Positions and
             // velocities are given in the ITRF terrestrial frame, while the quaternions are given in EME2000
             // inertial frame.
@@ -93,8 +106,9 @@ public class DirectLocation {
             boolean simpleEOP = true; // we don't want to compute tiny tidal effects at millimeter level
             Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, simpleEOP);
 
+            // Satellite attitude
+            // ------------------
             ArrayList<TimeStampedAngularCoordinates> satelliteQList = new ArrayList<TimeStampedAngularCoordinates>();
-            ArrayList<TimeStampedPVCoordinates> satellitePVList = new ArrayList<TimeStampedPVCoordinates>();
 
             addSatelliteQ(gps, satelliteQList, "2009-12-11T16:58:42.592937", -0.340236d, 0.333952d, -0.844012d, -0.245684d);
             addSatelliteQ(gps, satelliteQList, "2009-12-11T16:59:06.592937", -0.354773d, 0.329336d, -0.837871d, -0.252281d);
@@ -107,6 +121,10 @@ public class DirectLocation {
             addSatelliteQ(gps, satelliteQList, "2009-12-11T17:01:54.592937", -0.452976d, 0.294556d, -0.787571d, -0.296279d);
             addSatelliteQ(gps, satelliteQList, "2009-12-11T17:02:18.592937", -0.466207d, 0.28935d, -0.779516d, -0.302131d);
 
+            // Positions and velocities
+            // ------------------------
+            ArrayList<TimeStampedPVCoordinates> satellitePVList = new ArrayList<TimeStampedPVCoordinates>();
+
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:58:42.592937", -726361.466d, -5411878.485d, 4637549.599d, -2463.635d, -4447.634d, -5576.736d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:59:04.192937", -779538.267d, -5506500.533d, 4515934.894d, -2459.848d, -4312.676d, -5683.906d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:59:25.792937", -832615.368d, -5598184.195d, 4392036.13d, -2454.395d, -4175.564d, -5788.201d);
@@ -119,6 +137,8 @@ public class DirectLocation {
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T17:01:56.992937", -1198331.706d, -6154056.146d, 3466192.446d, -2369.401d, -3161.764d, -6433.531d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T17:02:18.592937", -1249311.381d, -6220723.191d, 3326367.397d, -2350.574d, -3010.159d, -6513.056d);
 
+            // Rugged initialization
+            // ---------------------
             Rugged rugged = new RuggedBuilder().
                     setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
                     setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
@@ -129,6 +149,8 @@ public class DirectLocation {
                                   addLineSensor(lineSensor).
                                   build();
 
+            // Direct location of a single sensor pixel (first line, first pixel)
+            // ------------------------------------------------------------------
             Vector3D position = lineSensor.getPosition(); // This returns a zero vector since we set the relative position of the sensor w.r.T the satellite to 0.
             AbsoluteDate firstLineDate = lineSensor.getDate(0);
             Vector3D los = lineSensor.getLOS(firstLineDate, 0);
@@ -154,8 +176,8 @@ public class DirectLocation {
                                   double px, double py, double pz, double vx, double vy, double vz)
         throws OrekitException {
         AbsoluteDate ephemerisDate = new AbsoluteDate(absDate, gps);
-        Vector3D position = new Vector3D(px, py, pz);
-        Vector3D velocity = new Vector3D(vx, vy, vz);
+        Vector3D position = new Vector3D(px, py, pz); // in ITRF, unit: m 
+        Vector3D velocity = new Vector3D(vx, vy, vz); // in ITRF, unit: m/s
         PVCoordinates pvITRF = new PVCoordinates(position, velocity);
         Transform transform = itrf.getTransformTo(eme2000, ephemerisDate);
         PVCoordinates pvEME2000 = transform.transformPVCoordinates(pvITRF);
@@ -165,7 +187,7 @@ public class DirectLocation {
     private static void addSatelliteQ(TimeScale gps, ArrayList<TimeStampedAngularCoordinates> satelliteQList, String absDate,
                                       double q0, double q1, double q2, double q3) {
         AbsoluteDate attitudeDate = new AbsoluteDate(absDate, gps);
-        Rotation rotation = new Rotation(q0, q1, q2, q3, true);
+        Rotation rotation = new Rotation(q0, q1, q2, q3, true);  // q0 is the scalar term
         TimeStampedAngularCoordinates pair =
                 new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
         satelliteQList.add(pair);
diff --git a/src/tutorials/java/fr/cs/examples/DirectLocationWithDEM.java b/src/tutorials/java/fr/cs/examples/DirectLocationWithDEM.java
index 8a9318671e73b2fed9d186570aa4b831ee820d0c..83ae31997bc8710f59c2ce75196c484d3a4c8b63 100644
--- a/src/tutorials/java/fr/cs/examples/DirectLocationWithDEM.java
+++ b/src/tutorials/java/fr/cs/examples/DirectLocationWithDEM.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -67,10 +67,14 @@ public class DirectLocationWithDEM {
             File orekitData = new File(home, "orekit-data");
             DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(orekitData));
 
+            // Sensor's definition
+            // ===================
+            // Line of sight
+            // -------------
             // The raw viewing direction of pixel i with respect to the instrument is defined by the vector:
             List<Vector3D> rawDirs = new ArrayList<Vector3D>();
             for (int i = 0; i < 2000; i++) {
-                //20° field of view, 2000 pixels
+                // 20° field of view, 2000 pixels
                 rawDirs.add(new Vector3D(0d, i*FastMath.toRadians(20)/2000d, 1d));
             }
 
@@ -81,14 +85,23 @@ public class DirectLocationWithDEM {
 
             TimeDependentLOS lineOfSight = losBuilder.build();
 
+            // Datation model
+            // --------------
             // We use Orekit for handling time and dates, and Rugged for defining the datation model:
             TimeScale gps = TimeScalesFactory.getGPS();
             AbsoluteDate absDate = new AbsoluteDate("2009-12-11T16:59:30.0", gps);
             LinearLineDatation lineDatation = new LinearLineDatation(absDate, 1d, 20);
 
-            // With the LOS and the datation now defined , we can initialize a line sensor object in Rugged:
+            // Line sensor
+            // -----------
+            // With the LOS and the datation now defined, we can initialize a line sensor object in Rugged:
             LineSensor lineSensor = new LineSensor("mySensor", lineDatation, Vector3D.ZERO, lineOfSight);
 
+            // Satellite position, velocity and attitude
+            // =========================================
+
+            // Reference frames
+            // ----------------
             // In our application, we simply need to know the name of the frames we are working with. Positions and
             // velocities are given in the ITRF terrestrial frame, while the quaternions are given in EME2000
             // inertial frame.
@@ -96,8 +109,9 @@ public class DirectLocationWithDEM {
             boolean simpleEOP = true; // we don't want to compute tiny tidal effects at millimeter level
             Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, simpleEOP);
 
+            // Satellite attitude
+            // ------------------
             ArrayList<TimeStampedAngularCoordinates> satelliteQList = new ArrayList<TimeStampedAngularCoordinates>();
-            ArrayList<TimeStampedPVCoordinates> satellitePVList = new ArrayList<TimeStampedPVCoordinates>();
 
             addSatelliteQ(gps, satelliteQList, "2009-12-11T16:58:42.592937", -0.340236d, 0.333952d, -0.844012d, -0.245684d);
             addSatelliteQ(gps, satelliteQList, "2009-12-11T16:59:06.592937", -0.354773d, 0.329336d, -0.837871d, -0.252281d);
@@ -110,6 +124,9 @@ public class DirectLocationWithDEM {
             addSatelliteQ(gps, satelliteQList, "2009-12-11T17:01:54.592937", -0.452976d, 0.294556d, -0.787571d, -0.296279d);
             addSatelliteQ(gps, satelliteQList, "2009-12-11T17:02:18.592937", -0.466207d, 0.28935d, -0.779516d, -0.302131d);
 
+            // Positions and velocities
+            // ------------------------
+            ArrayList<TimeStampedPVCoordinates> satellitePVList = new ArrayList<TimeStampedPVCoordinates>();
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:58:42.592937", -726361.466d, -5411878.485d, 4637549.599d, -2463.635d, -4447.634d, -5576.736d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:59:04.192937", -779538.267d, -5506500.533d, 4515934.894d, -2459.848d, -4312.676d, -5683.906d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:59:25.792937", -832615.368d, -5598184.195d, 4392036.13d, -2454.395d, -4175.564d, -5788.201d);
@@ -122,23 +139,27 @@ public class DirectLocationWithDEM {
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T17:01:56.992937", -1198331.706d, -6154056.146d, 3466192.446d, -2369.401d, -3161.764d, -6433.531d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T17:02:18.592937", -1249311.381d, -6220723.191d, 3326367.397d, -2350.574d, -3010.159d, -6513.056d);
 
+            // Rugged initialization
+            // ---------------------
             Rugged rugged = new RuggedBuilder().
-                    setAlgorithm(AlgorithmId.DUVENHAGE).
-                    setDigitalElevationModel(new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(37.58),
-                                                                                                FastMath.toRadians(-96.95),
-                                                                                                2463.0),
-                                                                              FastMath.toRadians(30.0), 16.0,
-                                                                              FastMath.toRadians(1.0), 1201),
-
-                                             8).
-                    setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
-                    setTimeSpan(absDate, absDate.shiftedBy(60.0), 0.01, 5 / lineSensor.getRate(0)).
-                    setTrajectory(InertialFrameId.EME2000,
-                                  satellitePVList, 4, CartesianDerivativesFilter.USE_P,
-                                  satelliteQList,  4,  AngularDerivativesFilter.USE_R).
-                                  addLineSensor(lineSensor).
-                                  build();
-
+                            setAlgorithm(AlgorithmId.DUVENHAGE).
+                            setDigitalElevationModel(new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(37.58),
+                                                                                                        FastMath.toRadians(-96.95),
+                                                                                                        2463.0),
+                                                                                      FastMath.toRadians(30.0), 16.0,
+                                                                                      FastMath.toRadians(1.0), 1201),
+
+                                                     8).
+                            setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
+                            setTimeSpan(absDate, absDate.shiftedBy(60.0), 0.01, 5 / lineSensor.getRate(0)).
+                            setTrajectory(InertialFrameId.EME2000,
+                                          satellitePVList, 4, CartesianDerivativesFilter.USE_P,
+                                          satelliteQList,  4,  AngularDerivativesFilter.USE_R).
+                            addLineSensor(lineSensor).
+                            build();
+
+            // Direct location of a single sensor pixel (first line, first pixel)
+            // ------------------------------------------------------------------
             Vector3D position = lineSensor.getPosition(); // This returns a zero vector since we set the relative position of the sensor w.r.T the satellite to 0.
             AbsoluteDate firstLineDate = lineSensor.getDate(0);
             Vector3D los = lineSensor.getLOS(firstLineDate, 0);
@@ -159,13 +180,13 @@ public class DirectLocationWithDEM {
     }
 
     private static void addSatellitePV(TimeScale gps, Frame eme2000, Frame itrf,
-                                  ArrayList<TimeStampedPVCoordinates> satellitePVList,
-                                  String absDate,
-                                  double px, double py, double pz, double vx, double vy, double vz)
-        throws OrekitException {
+                                       ArrayList<TimeStampedPVCoordinates> satellitePVList,
+                                       String absDate,
+                                       double px, double py, double pz, double vx, double vy, double vz)
+                                                       throws OrekitException {
         AbsoluteDate ephemerisDate = new AbsoluteDate(absDate, gps);
-        Vector3D position = new Vector3D(px, py, pz);
-        Vector3D velocity = new Vector3D(vx, vy, vz);
+        Vector3D position = new Vector3D(px, py, pz); // in ITRF, unit: m
+        Vector3D velocity = new Vector3D(vx, vy, vz); // in ITRF, unit: m/s
         PVCoordinates pvITRF = new PVCoordinates(position, velocity);
         Transform transform = itrf.getTransformTo(eme2000, ephemerisDate);
         PVCoordinates pvEME2000 = transform.transformPVCoordinates(pvITRF);
@@ -176,9 +197,9 @@ public class DirectLocationWithDEM {
     private static void addSatelliteQ(TimeScale gps, ArrayList<TimeStampedAngularCoordinates> satelliteQList, String absDate,
                                       double q0, double q1, double q2, double q3) {
         AbsoluteDate attitudeDate = new AbsoluteDate(absDate, gps);
-        Rotation rotation = new Rotation(q0, q1, q2, q3, true);
+        Rotation rotation = new Rotation(q0, q1, q2, q3, true);  // q0 is the scalar term
         TimeStampedAngularCoordinates pair =
-                new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
+                        new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
         satelliteQList.add(pair);
     }
 
@@ -199,8 +220,9 @@ public class DirectLocationWithDEM {
             this.n      = n;
         }
 
+        @Override
         public void updateTile(double latitude, double longitude, UpdatableTile tile)
-            throws RuggedException {
+                        throws RuggedException {
             double step         = size / (n - 1);
             double minLatitude  = size * FastMath.floor(latitude  / size);
             double minLongitude = size * FastMath.floor(longitude / size);
@@ -211,8 +233,8 @@ public class DirectLocationWithDEM {
                 for (int j = 0; j < n; ++j) {
                     double cellLongitude = minLongitude + j * step;
                     double distance       = Constants.WGS84_EARTH_EQUATORIAL_RADIUS *
-                                            FastMath.hypot(cellLatitude  - summit.getLatitude(),
-                                                           cellLongitude - summit.getLongitude());
+                                    FastMath.hypot(cellLatitude  - summit.getLatitude(),
+                                                   cellLongitude - summit.getLongitude());
                     double altitude = FastMath.max(summit.getAltitude() - distance * sinSlope,
                                                    base);
                     tile.setElevation(i, j, altitude);
diff --git a/src/tutorials/java/fr/cs/examples/InverseLocation.java b/src/tutorials/java/fr/cs/examples/InverseLocation.java
index a73779a2ed9e0184c73cbeb96fb22b843ac88c80..01dd414d94ed993fff76edf3b663e62fb07f8ac1 100644
--- a/src/tutorials/java/fr/cs/examples/InverseLocation.java
+++ b/src/tutorials/java/fr/cs/examples/InverseLocation.java
@@ -1,4 +1,4 @@
-/* Copyright 2013-2016 CS Systèmes d'Information
+/* Copyright 2013-2017 CS Systèmes d'Information
  * Licensed to CS Systèmes d'Information (CS) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -25,6 +25,7 @@ import org.hipparchus.geometry.euclidean.threed.Rotation;
 import org.hipparchus.geometry.euclidean.threed.Vector3D;
 import org.hipparchus.util.FastMath;
 import org.orekit.bodies.GeodeticPoint;
+import org.orekit.bodies.OneAxisEllipsoid;
 import org.orekit.data.DataProvidersManager;
 import org.orekit.data.DirectoryCrawler;
 import org.orekit.errors.OrekitException;
@@ -44,6 +45,7 @@ import org.orekit.rugged.linesensor.SensorPixel;
 import org.orekit.rugged.los.FixedRotation;
 import org.orekit.rugged.los.LOSBuilder;
 import org.orekit.rugged.los.TimeDependentLOS;
+import org.orekit.rugged.utils.RoughVisibilityEstimator;
 import org.orekit.time.AbsoluteDate;
 import org.orekit.time.TimeScale;
 import org.orekit.time.TimeScalesFactory;
@@ -65,10 +67,14 @@ public class InverseLocation {
             File orekitData = new File(home, "orekit-data");
             DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(orekitData));
 
+            // Sensor's definition
+            // ===================
+            // Line of sight
+            // -------------
             // The raw viewing direction of pixel i with respect to the instrument is defined by the vector:
             List<Vector3D> rawDirs = new ArrayList<Vector3D>();
             for (int i = 0; i < 2000; i++) {
-                //20° field of view, 2000 pixels
+                // 20° field of view, 2000 pixels
                 rawDirs.add(new Vector3D(0d, i*FastMath.toRadians(20)/2000d, 1d));
             }
 
@@ -79,17 +85,24 @@ public class InverseLocation {
 
             TimeDependentLOS lineOfSight = losBuilder.build();
 
+            // Datation model
+            // --------------
             // We use Orekit for handling time and dates, and Rugged for defining the datation model:
             TimeScale gps = TimeScalesFactory.getGPS();
             AbsoluteDate absDate = new AbsoluteDate("2009-12-11T16:59:30.0", gps);
             LinearLineDatation lineDatation = new LinearLineDatation(absDate, 1d, 20);
 
+            // Line sensor
+            // -----------
             // With the LOS and the datation now defined , we can initialize a line sensor object in Rugged:
             String sensorName = "mySensor";
             LineSensor lineSensor = new LineSensor(sensorName, lineDatation, Vector3D.ZERO, lineOfSight);
 
+            // Satellite position, velocity and attitude
+            // =========================================
 
-
+            // Reference frames
+            // ----------------
             // In our application, we simply need to know the name of the frames we are working with. Positions and
             // velocities are given in the ITRF terrestrial frame, while the quaternions are given in EME2000
             // inertial frame.
@@ -97,8 +110,9 @@ public class InverseLocation {
             boolean simpleEOP = true; // we don't want to compute tiny tidal effects at millimeter level
             Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, simpleEOP);
 
+            // Satellite attitude
+            // ------------------
             ArrayList<TimeStampedAngularCoordinates> satelliteQList = new ArrayList<TimeStampedAngularCoordinates>();
-            ArrayList<TimeStampedPVCoordinates> satellitePVList = new ArrayList<TimeStampedPVCoordinates>();
 
             addSatelliteQ(gps, satelliteQList, "2009-12-11T16:58:42.592937", -0.340236d, 0.333952d, -0.844012d, -0.245684d);
             addSatelliteQ(gps, satelliteQList, "2009-12-11T16:59:06.592937", -0.354773d, 0.329336d, -0.837871d, -0.252281d);
@@ -111,6 +125,10 @@ public class InverseLocation {
             addSatelliteQ(gps, satelliteQList, "2009-12-11T17:01:54.592937", -0.452976d, 0.294556d, -0.787571d, -0.296279d);
             addSatelliteQ(gps, satelliteQList, "2009-12-11T17:02:18.592937", -0.466207d, 0.28935d, -0.779516d, -0.302131d);
 
+            // Positions and velocities
+            // ------------------------
+            ArrayList<TimeStampedPVCoordinates> satellitePVList = new ArrayList<TimeStampedPVCoordinates>();
+
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:58:42.592937", -726361.466d, -5411878.485d, 4637549.599d, -2463.635d, -4447.634d, -5576.736d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:59:04.192937", -779538.267d, -5506500.533d, 4515934.894d, -2459.848d, -4312.676d, -5683.906d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T16:59:25.792937", -832615.368d, -5598184.195d, 4392036.13d, -2454.395d, -4175.564d, -5788.201d);
@@ -123,37 +141,77 @@ public class InverseLocation {
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T17:01:56.992937", -1198331.706d, -6154056.146d, 3466192.446d, -2369.401d, -3161.764d, -6433.531d);
             addSatellitePV(gps, eme2000, itrf, satellitePVList, "2009-12-11T17:02:18.592937", -1249311.381d, -6220723.191d, 3326367.397d, -2350.574d, -3010.159d, -6513.056d);
 
-            Rugged rugged = new RuggedBuilder().
-                    setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
-                    setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
-                    setTimeSpan(absDate, absDate.shiftedBy(60.0), 0.01, 5 / lineSensor.getRate(0)).
-                    setTrajectory(InertialFrameId.EME2000,
-                                  satellitePVList, 4, CartesianDerivativesFilter.USE_P,
-                                  satelliteQList,  4,  AngularDerivativesFilter.USE_R).
-                                  addLineSensor(lineSensor).
-                                  build();
+            // Rugged initialization
+            // ---------------------
+            RuggedBuilder ruggedBuilder = new RuggedBuilder();
 
+            ruggedBuilder.setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID);
+            ruggedBuilder.setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF);
+            ruggedBuilder.setTimeSpan(absDate, absDate.shiftedBy(60.0), 0.01, 5 / lineSensor.getRate(0));
+            ruggedBuilder.setTrajectory(InertialFrameId.EME2000,
+                                        satellitePVList, 4, CartesianDerivativesFilter.USE_P,
+                                        satelliteQList,  4,  AngularDerivativesFilter.USE_R).
+            addLineSensor(lineSensor);
+
+            Rugged rugged = ruggedBuilder.build();
+
+            // Inverse location of a Geodetic Point
+            // ------------------------------------
             // Point defined by its latitude, longitude and altitude
             double latitude = FastMath.toRadians(37.585);
             double longitude = FastMath.toRadians(-96.949);
             double altitude = 0.0d;
+            // For a GeodeticPoint : angles are given in radians and altitude in meters
             GeodeticPoint gp = new GeodeticPoint(latitude, longitude, altitude);
 
             // Search the sensor pixel seeing point
+            // ....................................
+            // interval of lines where to search the point
             int minLine = 0;
             int maxLine = 100;
             SensorPixel sensorPixel = rugged.inverseLocation(sensorName, gp, minLine, maxLine);
+
             // we need to test if the sensor pixel is found in the prescribed lines otherwise the sensor pixel is null
+            // Be careful: no exception is thrown if the pixel is not found
             if (sensorPixel != null){
+                // The pixel was found
                 System.out.format(Locale.US, "Sensor Pixel found : line = %5.3f, pixel = %5.3f %n", sensorPixel.getLineNumber(), sensorPixel.getPixelNumber());
             } else {
+                // The pixel was not found: set to null
                 System.out.println("Sensor Pixel is null: point cannot be seen between the prescribed line numbers\n");
             }
 
             // Find the date at which the sensor sees the ground point
+            // .......................................................
             AbsoluteDate dateLine = rugged.dateLocation(sensorName, gp, minLine, maxLine);
             System.out.println("Date at which the sensor sees the ground point : " + dateLine);
 
+
+
+            // How to find min and max lines ? with the RoughVisibilityEstimator
+            // -------------------------------
+            // Create a RoughVisibilityEstimator for inverse location
+            OneAxisEllipsoid oneAxisEllipsoid = ruggedBuilder.getEllipsoid();
+            Frame pvFrame = ruggedBuilder.getInertialFrame();
+
+            // Initialize the RoughVisibilityEstimator
+            RoughVisibilityEstimator roughVisibilityEstimator = new RoughVisibilityEstimator(oneAxisEllipsoid, pvFrame, satellitePVList);
+
+            // Compute the approximated line with a rough estimator
+            AbsoluteDate roughLineDate = roughVisibilityEstimator.estimateVisibility(gp);
+            double roughLine = lineSensor.getLine(roughLineDate);
+
+            // Compute the min / max lines interval using a margin around the roughLine
+            int sensorMinLine= 0;
+            int sensorMaxLine = 1000;
+
+            int margin = 100;
+            int minLineRough = (int) FastMath.max(roughLine - margin, sensorMinLine);
+            int maxLineRough = (int) FastMath.min(roughLine + margin, sensorMaxLine);
+            SensorPixel sensorPixelRoughLine = rugged.inverseLocation(sensorName, gp, minLineRough, maxLineRough);
+
+            System.out.format(Locale.US, "Rough line found = %5.1f; InverseLocation gives (margin of %d around rough line): line = %5.3f, pixel = %5.3f %n", roughLine, margin, sensorPixelRoughLine.getLineNumber(), sensorPixel.getPixelNumber());
+
         } catch (OrekitException oe) {
             System.err.println(oe.getLocalizedMessage());
             System.exit(1);
@@ -165,13 +223,13 @@ public class InverseLocation {
     }
 
     private static void addSatellitePV(TimeScale gps, Frame eme2000, Frame itrf,
-                                  ArrayList<TimeStampedPVCoordinates> satellitePVList,
-                                  String absDate,
-                                  double px, double py, double pz, double vx, double vy, double vz)
-        throws OrekitException {
+                                       ArrayList<TimeStampedPVCoordinates> satellitePVList,
+                                       String absDate,
+                                       double px, double py, double pz, double vx, double vy, double vz)
+                                                       throws OrekitException {
         AbsoluteDate ephemerisDate = new AbsoluteDate(absDate, gps);
-        Vector3D position = new Vector3D(px, py, pz);
-        Vector3D velocity = new Vector3D(vx, vy, vz);
+        Vector3D position = new Vector3D(px, py, pz); // in ITRF, unit: m
+        Vector3D velocity = new Vector3D(vx, vy, vz); // in ITRF, unit: m/s
         PVCoordinates pvITRF = new PVCoordinates(position, velocity);
         Transform transform = itrf.getTransformTo(eme2000, ephemerisDate);
         PVCoordinates pvEME2000 = transform.transformPVCoordinates(pvITRF);
@@ -181,9 +239,9 @@ public class InverseLocation {
     private static void addSatelliteQ(TimeScale gps, ArrayList<TimeStampedAngularCoordinates> satelliteQList, String absDate,
                                       double q0, double q1, double q2, double q3) {
         AbsoluteDate attitudeDate = new AbsoluteDate(absDate, gps);
-        Rotation rotation = new Rotation(q0, q1, q2, q3, true);
+        Rotation rotation = new Rotation(q0, q1, q2, q3, true);  // q0 is the scalar term
         TimeStampedAngularCoordinates pair =
-                new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
+                        new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
         satelliteQList.add(pair);
     }