1   /* Copyright 2013-2019 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.rugged.errors;
18  
19  import java.io.BufferedReader;
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStreamReader;
26  import java.io.ObjectOutputStream;
27  import java.lang.reflect.InvocationTargetException;
28  import java.lang.reflect.Method;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.NavigableMap;
35  import java.util.TreeMap;
36  import java.util.stream.Stream;
37  
38  import org.hipparchus.analysis.differentiation.DerivativeStructure;
39  import org.hipparchus.exception.LocalizedCoreFormats;
40  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
41  import org.hipparchus.geometry.euclidean.threed.Rotation;
42  import org.hipparchus.geometry.euclidean.threed.Vector3D;
43  import org.hipparchus.util.FastMath;
44  import org.hipparchus.util.OpenIntToDoubleHashMap;
45  import org.hipparchus.util.Pair;
46  import org.orekit.bodies.GeodeticPoint;
47  import org.orekit.bodies.OneAxisEllipsoid;
48  import org.orekit.frames.Frame;
49  import org.orekit.frames.FramesFactory;
50  import org.orekit.frames.Predefined;
51  import org.orekit.frames.Transform;
52  import org.orekit.rugged.api.AlgorithmId;
53  import org.orekit.rugged.api.Rugged;
54  import org.orekit.rugged.api.RuggedBuilder;
55  import org.orekit.rugged.linesensor.LineDatation;
56  import org.orekit.rugged.linesensor.LineSensor;
57  import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing;
58  import org.orekit.rugged.linesensor.SensorMeanPlaneCrossing.CrossingResult;
59  import org.orekit.rugged.linesensor.SensorPixel;
60  import org.orekit.rugged.los.TimeDependentLOS;
61  import org.orekit.rugged.raster.TileUpdater;
62  import org.orekit.rugged.raster.UpdatableTile;
63  import org.orekit.rugged.refraction.AtmosphericRefraction;
64  import org.orekit.rugged.refraction.MultiLayerModel;
65  import org.orekit.rugged.utils.DSGenerator;
66  import org.orekit.rugged.utils.ExtendedEllipsoid;
67  import org.orekit.rugged.utils.SpacecraftToObservedBody;
68  import org.orekit.time.AbsoluteDate;
69  import org.orekit.time.TimeScalesFactory;
70  import org.orekit.utils.ParameterDriver;
71  
72  /** Replayer for Rugged debug dumps.
73   * @author Luc Maisonobe
74   * @author Guylaine Prat
75   * @see DumpManager
76   * @see Dump
77   */
78  public class DumpReplayer {
79  
80      /** Comment start marker. */
81      private static final String COMMENT_START = "#";
82  
83      /** Keyword for latitude fields. */
84      private static final String LATITUDE = "latitude";
85  
86      /** Keyword for longitude fields. */
87      private static final String LONGITUDE = "longitude";
88  
89      /** Keyword for elevation fields. */
90      private static final String ELEVATION = "elevation";
91  
92      /** Keyword for ellipsoid equatorial radius fields. */
93      private static final String AE = "ae";
94  
95      /** Keyword for ellipsoid flattening fields. */
96      private static final String F = "f";
97  
98      /** Keyword for frame fields. */
99      private static final String FRAME = "frame";
100 
101     /** Keyword for date fields. */
102     private static final String DATE = "date";
103 
104     /** Keyword for sensor position fields. */
105     private static final String POSITION = "position";
106 
107     /** Keyword for sensor line-of-sight fields. */
108     private static final String LOS = "los";
109 
110     /** Keyword for light-time correction fields. */
111     private static final String LIGHT_TIME = "lightTime";
112 
113     /** Keyword for aberration of light correction fields. */
114     private static final String ABERRATION = "aberration";
115 
116     /** Keyword for atmospheric refraction correction fields. */
117     private static final String REFRACTION = "refraction";
118 
119     /** Keyword for min date fields. */
120     private static final String MIN_DATE = "minDate";
121 
122     /** Keyword for max date fields. */
123     private static final String MAX_DATE = "maxDate";
124 
125     /** Keyword for time step fields. */
126     private static final String T_STEP = "tStep";
127 
128     /** Keyword for overshoot tolerance fields. */
129     private static final String TOLERANCE = "tolerance";
130 
131     /** Keyword for inertial frames fields. */
132     private static final String INERTIAL_FRAME = "inertialFrame";
133 
134     /** Keyword for observation transform index fields. */
135     private static final String INDEX = "index";
136 
137     /** Keyword for body meta-fields. */
138     private static final String BODY = "body";
139 
140     /** Keyword for rotation fields. */
141     private static final String R = "r";
142 
143     /** Keyword for rotation rate fields. */
144     private static final String OMEGA = "Ω";
145 
146     /** Keyword for rotation acceleration fields. */
147     private static final String OMEGA_DOT = "ΩDot";
148 
149     /** Keyword for spacecraft meta-fields. */
150     private static final String SPACECRAFT = "spacecraft";
151 
152     /** Keyword for position fields. */
153     private static final String P = "p";
154 
155     /** Keyword for velocity fields. */
156     private static final String V = "v";
157 
158     /** Keyword for acceleration fields. */
159     private static final String A = "a";
160 
161     /** Keyword for minimum latitude fields. */
162     private static final String LAT_MIN = "latMin";
163 
164     /** Keyword for latitude step fields. */
165     private static final String LAT_STEP = "latStep";
166 
167     /** Keyword for latitude rows fields. */
168     private static final String LAT_ROWS = "latRows";
169 
170     /** Keyword for minimum longitude fields. */
171     private static final String LON_MIN = "lonMin";
172 
173     /** Keyword for longitude step fields. */
174     private static final String LON_STEP = "lonStep";
175 
176     /** Keyword for longitude columns fields. */
177     private static final String LON_COLS = "lonCols";
178 
179     /** Keyword for latitude index fields. */
180     private static final String LAT_INDEX = "latIndex";
181 
182     /** Keyword for longitude index fields. */
183     private static final String LON_INDEX = "lonIndex";
184 
185     /** Keyword for sensor name. */
186     private static final String SENSOR_NAME = "sensorName";
187 
188     /** Keyword for min line. */
189     private static final String MIN_LINE = "minLine";
190 
191     /** Keyword for max line. */
192     private static final String MAX_LINE = "maxLine";
193 
194     /** Keyword for line number. */
195     private static final String LINE_NUMBER = "lineNumber";
196 
197     /** Keyword for number of pixels. */
198     private static final String NB_PIXELS = "nbPixels";
199 
200     /** Keyword for pixel number. */
201     private static final String PIXEL_NUMBER = "pixelNumber";
202 
203     /** Keyword for max number of evaluations. */
204     private static final String MAX_EVAL = "maxEval";
205 
206     /** Keyword for accuracy. */
207     private static final String ACCURACY = "accuracy";
208 
209     /** Keyword for normal. */
210     private static final String NORMAL = "normal";
211 
212     /** Keyword for rate. */
213     private static final String RATE = "rate";
214 
215     /** Keyword for cached results. */
216     private static final String CACHED_RESULTS = "cachedResults";
217 
218     /** Keyword for target. */
219     private static final String TARGET = "target";
220 
221     /** Keyword for target direction. */
222     private static final String TARGET_DIRECTION = "targetDirection";
223 
224     /** Keyword for null result. */
225     private static final String NULL_RESULT = "NULL";
226 
227     /** Constant elevation for constant elevation algorithm. */
228     private double constantElevation;
229 
230     /** Algorithm identifier. */
231     private AlgorithmId algorithmId;
232 
233     /** Ellipsoid. */
234     private OneAxisEllipsoid ellipsoid;
235 
236     /** Tiles list. */
237     private final List<ParsedTile> tiles;
238 
239     /** Sensors list. */
240     private final List<ParsedSensor> sensors;
241 
242     /** Interpolator min date. */
243     private AbsoluteDate minDate;
244 
245     /** Interpolator max date. */
246     private AbsoluteDate maxDate;
247 
248     /** Interpolator step. */
249     private double tStep;
250 
251     /** Interpolator overshoot tolerance. */
252     private double tolerance;
253 
254     /** Inertial frame. */
255     private Frame inertialFrame;
256 
257     /** Transforms sample from observed body frame to inertial frame. */
258     private NavigableMap<Integer, Transform> bodyToInertial;
259 
260     /** Transforms sample from spacecraft frame to inertial frame. */
261     private NavigableMap<Integer, Transform> scToInertial;
262 
263     /** Flag for light time correction. */
264     private boolean lightTimeCorrection;
265 
266     /** Flag for aberration of light correction. */
267     private boolean aberrationOfLightCorrection;
268 
269     /** Flag for atmospheric refraction. */
270     private boolean atmosphericRefraction;
271 
272     /** Dumped calls. */
273     private final List<DumpedCall> calls;
274 
275     /** Simple constructor.
276      */
277     public DumpReplayer() {
278         tiles   = new ArrayList<ParsedTile>();
279         sensors = new ArrayList<ParsedSensor>();
280         calls   = new ArrayList<DumpedCall>();
281     }
282 
283     /** Parse a dump file.
284      * @param file dump file to parse
285      */
286     public void parse(final File file) {
287         try {
288             final BufferedReader reader =
289                     new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
290             int l = 0;
291             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
292                 LineParser.parse(++l, file, line, this);
293             }
294             reader.close();
295         } catch (IOException ioe) {
296             throw new RuggedException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
297         }
298     }
299 
300     /** Create a Rugged instance from parsed data.
301      * @return rugged instance
302      */
303     public Rugged createRugged() {
304         try {
305             final RuggedBuilderhtml#RuggedBuilder">RuggedBuilder builder = new RuggedBuilder();
306 
307             if (algorithmId == null) {
308                 algorithmId = AlgorithmId.IGNORE_DEM_USE_ELLIPSOID;
309             }
310             builder.setAlgorithm(algorithmId);
311             if (algorithmId == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) {
312                 builder.setConstantElevation(constantElevation);
313             } else if (algorithmId != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID) {
314                 builder.setDigitalElevationModel(new TileUpdater() {
315 
316                     /** {@inheritDoc} */
317                     @Override
318                     public void updateTile(final double latitude, final double longitude, final UpdatableTile tile) {
319                         for (final ParsedTile parsedTile : tiles) {
320                             if (parsedTile.isInterpolable(latitude, longitude)) {
321                                 parsedTile.updateTile(tile);
322                                 return;
323                             }
324                         }
325                         throw new RuggedException(RuggedMessages.NO_DEM_DATA,
326                                                   FastMath.toDegrees(latitude), FastMath.toDegrees(longitude));
327                     }
328                 }, 8);
329             }
330 
331             builder.setEllipsoid(ellipsoid);
332 
333             builder.setLightTimeCorrection(lightTimeCorrection);
334             builder.setAberrationOfLightCorrection(aberrationOfLightCorrection);
335             if (atmosphericRefraction) { // Use the default model with the default configuration values
336                 final ExtendedEllipsoid extendedEllipsoid = builder.getEllipsoid();
337                 final AtmosphericRefraction atmosphericModel = new MultiLayerModel(extendedEllipsoid);
338                 // Build Rugged with atmospheric refraction model
339                 builder.setRefractionCorrection(atmosphericModel);
340             }
341 
342 
343             // build missing transforms by extrapolating the parsed ones
344             final int n = (int) FastMath.ceil(maxDate.durationFrom(minDate) / tStep);
345             final List<Transform> b2iList = new ArrayList<Transform>(n);
346             final List<Transform> s2iList = new ArrayList<Transform>(n);
347             for (int i = 0; i < n; ++i) {
348                 if (bodyToInertial.containsKey(i)) {
349                     // the i-th transform was dumped
350                     b2iList.add(bodyToInertial.get(i));
351                     s2iList.add(scToInertial.get(i));
352                 } else {
353                     // the i-th transformed was not dumped, we have to extrapolate it
354                     final Map.Entry<Integer, Transform> lower  = bodyToInertial.lowerEntry(i);
355                     final Map.Entry<Integer, Transform> higher = bodyToInertial.higherEntry(i);
356                     final int closest;
357                     if (lower == null) {
358                         closest = higher.getKey();
359                     } else if (higher == null) {
360                         closest = lower.getKey();
361                     } else {
362                         closest = (i - lower.getKey() <= higher.getKey() - i) ? lower.getKey() : higher.getKey();
363                     }
364                     b2iList.add(bodyToInertial.get(closest).shiftedBy((i - closest) * tStep));
365                     s2iList.add(scToInertial.get(closest).shiftedBy((i - closest) * tStep));
366                 }
367             }
368 
369             // we use Rugged transforms reloading mechanism to ensure the spacecraft
370             // to body transforms will be the same as the ones dumped
371             final SpacecraftToObservedBody scToBody =
372                     new SpacecraftToObservedBody(inertialFrame, ellipsoid.getBodyFrame(),
373                                                  minDate, maxDate, tStep, tolerance,
374                                                  b2iList, s2iList);
375             final ByteArrayOutputStream bos = new ByteArrayOutputStream();
376             new ObjectOutputStream(bos).writeObject(scToBody);
377             final ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
378             builder.setTrajectoryAndTimeSpan(bis);
379 
380             final List<SensorMeanPlaneCrossing> planeCrossings = new ArrayList<SensorMeanPlaneCrossing>();
381             for (final ParsedSensor parsedSensor : sensors) {
382                 final LineSensoreSensor.html#LineSensor">LineSensor sensor = new LineSensor(parsedSensor.name,
383                                                          parsedSensor,
384                                                          parsedSensor.position,
385                                                          parsedSensor);
386                 if (parsedSensor.meanPlane != null) {
387                     planeCrossings.add(new SensorMeanPlaneCrossing(sensor, scToBody,
388                                                                    parsedSensor.meanPlane.minLine,
389                                                                    parsedSensor.meanPlane.maxLine,
390                                                                    lightTimeCorrection, aberrationOfLightCorrection,
391                                                                    parsedSensor.meanPlane.maxEval,
392                                                                    parsedSensor.meanPlane.accuracy,
393                                                                    parsedSensor.meanPlane.normal,
394                                                                    Arrays.stream(parsedSensor.meanPlane.cachedResults)));
395                 }
396                 builder.addLineSensor(sensor);
397             }
398 
399             final Rugged rugged = builder.build();
400 
401             final Method setPlaneCrossing = Rugged.class.getDeclaredMethod("setPlaneCrossing",
402                                                                            SensorMeanPlaneCrossing.class);
403             setPlaneCrossing.setAccessible(true);
404             for (final SensorMeanPlaneCrossing planeCrossing : planeCrossings) {
405                 setPlaneCrossing.invoke(rugged, planeCrossing);
406             }
407 
408             return rugged;
409 
410         } catch (IOException ioe) {
411             throw new RuggedException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
412         } catch (SecurityException e) {
413             // this should never happen
414             throw new RuggedInternalError(e);
415         } catch (NoSuchMethodException e) {
416             // this should never happen
417             throw new RuggedInternalError(e);
418         } catch (IllegalArgumentException e) {
419             // this should never happen
420             throw new RuggedInternalError(e);
421         } catch (IllegalAccessException e) {
422             // this should never happen
423             throw new RuggedInternalError(e);
424         } catch (InvocationTargetException e) {
425             // this should never happen
426             throw new RuggedInternalError(e);
427         }
428     }
429 
430     /** Get a sensor by name.
431      * @param name sensor name
432      * @return parsed sensor
433      */
434     private ParsedSensor getSensor(final String name) {
435         for (final ParsedSensor sensor : sensors) {
436             if (sensor.name.equals(name)) {
437                 return sensor;
438             }
439         }
440         final ParsedSensor sensor = new ParsedSensor(name);
441         sensors.add(sensor);
442         return sensor;
443     }
444 
445     /** Execute all dumped calls.
446      * <p>
447      * The dumped calls correspond to computation methods like direct or inverse
448      * location.
449      * </p>
450      * @param rugged Rugged instance on which calls will be performed
451      * @return results of all dumped calls
452      */
453     public Result[] execute(final Rugged rugged) {
454         final Result[] results = new Result[calls.size()];
455         for (int i = 0; i < calls.size(); ++i) {
456             results[i] = new Result(calls.get(i).expected,
457                                     calls.get(i).execute(rugged));
458         }
459         return results;
460     }
461 
462     /** Container for replay results. */
463     public static class Result {
464 
465         /** Expected result. */
466         private final Object expected;
467 
468         /** Replayed result. */
469         private final Object replayed;
470 
471         /** Simple constructor.
472          * @param expected expected result
473          * @param replayed replayed result
474          */
475         private Result(final Object expected, final Object replayed) {
476             this.expected = expected;
477             this.replayed = replayed;
478         }
479 
480         /** Get the expected result.
481          * @return expected result
482          */
483         public Object getExpected() {
484             return expected;
485         }
486 
487         /** Get the replayed result.
488          * @return replayed result
489          */
490         public Object getReplayed() {
491             return replayed;
492         }
493 
494     }
495 
496     /** Line parsers. */
497     private enum LineParser {
498 
499         /** Parser for algorithm dump lines. */
500         ALGORITHM() {
501 
502             /** {@inheritDoc} */
503             @Override
504             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
505                 try {
506                     if (fields.length < 1) {
507                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
508                     }
509                     global.algorithmId = AlgorithmId.valueOf(fields[0]);
510                     if (global.algorithmId == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) {
511                         if (fields.length < 3 || !fields[1].equals(ELEVATION)) {
512                             throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
513                         }
514                         global.constantElevation = Double.parseDouble(fields[2]);
515                     }
516                 } catch (IllegalArgumentException iae) {
517                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
518                 }
519             }
520 
521         },
522 
523         /** Parser for ellipsoid dump lines. */
524         ELLIPSOID() {
525 
526             /** {@inheritDoc} */
527             @Override
528             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
529                 if (fields.length < 6 || !fields[0].equals(AE) || !fields[2].equals(F) || !fields[4].equals(FRAME)) {
530                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
531                 }
532                 final double ae   = Double.parseDouble(fields[1]);
533                 final double f    = Double.parseDouble(fields[3]);
534                 final Frame  bodyFrame;
535                 try {
536                     bodyFrame = FramesFactory.getFrame(Predefined.valueOf(fields[5]));
537                 } catch (IllegalArgumentException iae) {
538                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
539                 }
540                 global.ellipsoid = new OneAxisEllipsoid(ae, f, bodyFrame);
541             }
542 
543         },
544 
545         /** Parser for direct location calls dump lines. */
546         DIRECT_LOCATION() {
547 
548             /** {@inheritDoc} */
549             @Override
550             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
551                 if (fields.length < 16 ||
552                         !fields[0].equals(DATE) ||
553                         !fields[2].equals(POSITION) || !fields[6].equals(LOS) ||
554                         !fields[10].equals(LIGHT_TIME) || !fields[12].equals(ABERRATION) ||
555                         !fields[14].equals(REFRACTION)) {
556                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
557                 }
558                 final AbsoluteDate date = new AbsoluteDate(fields[1], TimeScalesFactory.getUTC());
559                 final Vector3D position = new Vector3D(Double.parseDouble(fields[3]),
560                         Double.parseDouble(fields[4]),
561                         Double.parseDouble(fields[5]));
562                 final Vector3D los      = new Vector3D(Double.parseDouble(fields[7]),
563                         Double.parseDouble(fields[8]),
564                         Double.parseDouble(fields[9]));
565                 if (global.calls.isEmpty()) {
566                     global.lightTimeCorrection         = Boolean.parseBoolean(fields[11]);
567                     global.aberrationOfLightCorrection = Boolean.parseBoolean(fields[13]);
568                     global.atmosphericRefraction       = Boolean.parseBoolean(fields[15]);
569                 } else {
570                     if (global.lightTimeCorrection != Boolean.parseBoolean(fields[11])) {
571                         throw new RuggedException(RuggedMessages.LIGHT_TIME_CORRECTION_REDEFINED,
572                                 l, file.getAbsolutePath(), line);
573                     }
574                     if (global.aberrationOfLightCorrection != Boolean.parseBoolean(fields[13])) {
575                         throw new RuggedException(RuggedMessages.ABERRATION_OF_LIGHT_CORRECTION_REDEFINED,
576                                 l, file.getAbsolutePath(), line);
577                     }
578                     if (global.atmosphericRefraction != Boolean.parseBoolean(fields[15])) {
579                         throw new RuggedException(RuggedMessages.ATMOSPHERIC_REFRACTION_REDEFINED,
580                                 l, file.getAbsolutePath(), line);
581                     }
582                 }
583                 global.calls.add(new DumpedCall() {
584 
585                     /** {@inheritDoc} */
586                     @Override
587                     public Object execute(final Rugged rugged) {
588                         return rugged.directLocation(date, position, los);
589                     }
590 
591                 });
592             }
593         },
594 
595         /** Parser for direct location result dump lines. */
596         DIRECT_LOCATION_RESULT() {
597 
598             /** {@inheritDoc} */
599             @Override
600             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
601                 if (fields.length == 1) {
602                     if (fields[0].equals(NULL_RESULT)) {
603                         final GeodeticPoint gp = null;
604                         final DumpedCall last = global.calls.get(global.calls.size() - 1);
605                         last.expected = gp;
606                     } else {
607                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
608                     }
609                 } else if (fields.length < 6 || !fields[0].equals(LATITUDE) ||
610                            !fields[2].equals(LONGITUDE) || !fields[4].equals(ELEVATION)) {
611                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
612                 } else {
613                     final GeodeticPoint gp = new GeodeticPoint(Double.parseDouble(fields[1]),
614                                                                Double.parseDouble(fields[3]),
615                                                                Double.parseDouble(fields[5]));
616                     final DumpedCall last = global.calls.get(global.calls.size() - 1);
617                     last.expected = gp;
618                 }
619             }
620 
621         },
622 
623         /** Parser for search span dump lines. */
624         SPAN() {
625 
626             /** {@inheritDoc} */
627             @Override
628             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
629                 if (fields.length < 10 ||
630                         !fields[0].equals(MIN_DATE)  || !fields[2].equals(MAX_DATE) || !fields[4].equals(T_STEP)   ||
631                         !fields[6].equals(TOLERANCE) || !fields[8].equals(INERTIAL_FRAME)) {
632                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
633                 }
634                 global.minDate        = new AbsoluteDate(fields[1], TimeScalesFactory.getUTC());
635                 global.maxDate        = new AbsoluteDate(fields[3], TimeScalesFactory.getUTC());
636                 global.tStep          = Double.parseDouble(fields[5]);
637                 global.tolerance      = Double.parseDouble(fields[7]);
638                 global.bodyToInertial = new TreeMap<Integer, Transform>();
639                 global.scToInertial   = new TreeMap<Integer, Transform>();
640                 try {
641                     global.inertialFrame = FramesFactory.getFrame(Predefined.valueOf(fields[9]));
642                 } catch (IllegalArgumentException iae) {
643                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
644                 }
645             }
646         },
647 
648         /** Parser for observation transforms dump lines. */
649         TRANSFORM() {
650 
651             /** {@inheritDoc} */
652             @Override
653             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
654                 if (fields.length < 42 ||
655                     !fields[0].equals(INDEX) ||
656                     !fields[2].equals(BODY)  ||
657                     !fields[3].equals(R)     || !fields[8].equals(OMEGA)    || !fields[12].equals(OMEGA_DOT) ||
658                     !fields[16].equals(SPACECRAFT) ||
659                     !fields[17].equals(P)    || !fields[21].equals(V)   || !fields[25].equals(A) ||
660                     !fields[29].equals(R)    || !fields[34].equals(OMEGA)   || !fields[38].equals(OMEGA_DOT)) {
661                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
662                 }
663                 final int i   = Integer.parseInt(fields[1]);
664                 final AbsoluteDate date = global.minDate.shiftedBy(i * global.tStep);
665                 global.bodyToInertial.put(i,
666                                           new Transform(date,
667                                                         new Rotation(Double.parseDouble(fields[4]),
668                                                                      Double.parseDouble(fields[5]),
669                                                                      Double.parseDouble(fields[6]),
670                                                                      Double.parseDouble(fields[7]),
671                                                                      false),
672                                                         new Vector3D(Double.parseDouble(fields[9]),
673                                                                      Double.parseDouble(fields[10]),
674                                                                      Double.parseDouble(fields[11])),
675                                                         new Vector3D(Double.parseDouble(fields[13]),
676                                                                      Double.parseDouble(fields[14]),
677                                                                      Double.parseDouble(fields[15]))));
678                 global.scToInertial.put(i,
679                                         new Transform(date,
680                                                       new Transform(date,
681                                                                     new Vector3D(Double.parseDouble(fields[18]),
682                                                                                  Double.parseDouble(fields[19]),
683                                                                                  Double.parseDouble(fields[20])),
684                                                                     new Vector3D(Double.parseDouble(fields[22]),
685                                                                                  Double.parseDouble(fields[23]),
686                                                                                  Double.parseDouble(fields[24])),
687                                                                     new Vector3D(Double.parseDouble(fields[26]),
688                                                                                  Double.parseDouble(fields[27]),
689                                                                                  Double.parseDouble(fields[28]))),
690                                                       new Transform(date,
691                                                                     new Rotation(Double.parseDouble(fields[30]),
692                                                                                  Double.parseDouble(fields[31]),
693                                                                                  Double.parseDouble(fields[32]),
694                                                                                  Double.parseDouble(fields[33]),
695                                                                                  false),
696                                                                     new Vector3D(Double.parseDouble(fields[35]),
697                                                                                  Double.parseDouble(fields[36]),
698                                                                                  Double.parseDouble(fields[37])),
699                                                                     new Vector3D(Double.parseDouble(fields[39]),
700                                                                                  Double.parseDouble(fields[40]),
701                                                                                  Double.parseDouble(fields[41])))));
702             }
703 
704         },
705 
706         /** Parser for DEM tile global geometry dump lines. */
707         DEM_TILE() {
708 
709             /** {@inheritDoc} */
710             @Override
711             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
712                 if (fields.length < 13 ||
713                         !fields[1].equals(LAT_MIN) || !fields[3].equals(LAT_STEP) || !fields[5].equals(LAT_ROWS) ||
714                         !fields[7].equals(LON_MIN) || !fields[9].equals(LON_STEP) || !fields[11].equals(LON_COLS)) {
715                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
716                 }
717                 final String name             = fields[0];
718                 final double minLatitude      = Double.parseDouble(fields[2]);
719                 final double latitudeStep     = Double.parseDouble(fields[4]);
720                 final int    latitudeRows     = Integer.parseInt(fields[6]);
721                 final double minLongitude     = Double.parseDouble(fields[8]);
722                 final double longitudeStep    = Double.parseDouble(fields[10]);
723                 final int    longitudeColumns = Integer.parseInt(fields[12]);
724                 for (final ParsedTile tile : global.tiles) {
725                     if (tile.name.equals(name)) {
726                         throw new RuggedException(RuggedMessages.TILE_ALREADY_DEFINED,
727                                                   name, l, file.getAbsolutePath(), line);
728                     }
729                 }
730                 global.tiles.add(new ParsedTile(name,
731                                                 minLatitude, latitudeStep, latitudeRows,
732                                                 minLongitude, longitudeStep, longitudeColumns));
733             }
734 
735         },
736 
737         /** Parser for DEM cells dump lines. */
738         DEM_CELL() {
739 
740             /** {@inheritDoc} */
741             @Override
742             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
743                 if (fields.length < 7 ||
744                     !fields[1].equals(LAT_INDEX) || !fields[3].equals(LON_INDEX) || !fields[5].equals(ELEVATION)) {
745                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
746                 }
747                 final String name      = fields[0];
748                 final int    latIndex  = Integer.parseInt(fields[2]);
749                 final int    lonIndex  = Integer.parseInt(fields[4]);
750                 final double elevation = Double.parseDouble(fields[6]);
751                 for (final ParsedTile tile : global.tiles) {
752                     if (tile.name.equals(name)) {
753                         final int index = latIndex * tile.longitudeColumns + lonIndex;
754                         tile.elevations.put(index, elevation);
755                         return;
756                     }
757                 }
758                 throw new RuggedException(RuggedMessages.UNKNOWN_TILE,
759                                           name, l, file.getAbsolutePath(), line);
760             }
761 
762         },
763 
764         /** Parser for inverse location calls dump lines. */
765         INVERSE_LOCATION() {
766 
767             /** {@inheritDoc} */
768             @Override
769             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
770                 if (fields.length < 18 ||
771                         !fields[0].equals(SENSOR_NAME) ||
772                         !fields[2].equals(LATITUDE) || !fields[4].equals(LONGITUDE) || !fields[6].equals(ELEVATION) ||
773                         !fields[8].equals(MIN_LINE) || !fields[10].equals(MAX_LINE) ||
774                         !fields[12].equals(LIGHT_TIME) || !fields[14].equals(ABERRATION) ||
775                         !fields[16].equals(REFRACTION)) {
776                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
777                 }
778                 final String sensorName = fields[1];
779                 final GeodeticPoint point = new GeodeticPoint(Double.parseDouble(fields[3]),
780                                                               Double.parseDouble(fields[5]),
781                                                               Double.parseDouble(fields[7]));
782                 final int minLine = Integer.parseInt(fields[9]);
783                 final int maxLine = Integer.parseInt(fields[11]);
784                 if (global.calls.isEmpty()) {
785                     global.lightTimeCorrection         = Boolean.parseBoolean(fields[13]);
786                     global.aberrationOfLightCorrection = Boolean.parseBoolean(fields[15]);
787                     global.atmosphericRefraction       = Boolean.parseBoolean(fields[17]);
788                 } else {
789                     if (global.lightTimeCorrection != Boolean.parseBoolean(fields[13])) {
790                         throw new RuggedException(RuggedMessages.LIGHT_TIME_CORRECTION_REDEFINED,
791                                                   l, file.getAbsolutePath(), line);
792                     }
793                     if (global.aberrationOfLightCorrection != Boolean.parseBoolean(fields[15])) {
794                         throw new RuggedException(RuggedMessages.ABERRATION_OF_LIGHT_CORRECTION_REDEFINED,
795                                                   l, file.getAbsolutePath(), line);
796                     }
797                     if (global.atmosphericRefraction != Boolean.parseBoolean(fields[17])) {
798                         throw new RuggedException(RuggedMessages.ATMOSPHERIC_REFRACTION_REDEFINED,
799                                                   l, file.getAbsolutePath(), line);
800                     }
801                 }
802                 global.calls.add(new DumpedCall() {
803 
804                     /** {@inheritDoc} */
805                     @Override
806                     public Object execute(final Rugged rugged) {
807                         return rugged.inverseLocation(sensorName, point, minLine, maxLine);
808                     }
809 
810                 });
811             }
812 
813         },
814 
815         /** Parser for inverse location result dump lines. */
816         INVERSE_LOCATION_RESULT() {
817 
818             /** {@inheritDoc} */
819             @Override
820             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
821                 if (fields.length == 1) {
822                     if (fields[0].equals(NULL_RESULT)) {
823                         final SensorPixel sp = null;
824                         final DumpedCall last = global.calls.get(global.calls.size() - 1);
825                         last.expected = sp;
826                     } else {
827                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
828                     }
829                 } else if (fields.length < 4 || !fields[0].equals(LINE_NUMBER) || !fields[2].equals(PIXEL_NUMBER)) {
830                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
831                 } else {
832                     final SensorPixelSensorPixel.html#SensorPixel">SensorPixel sp = new SensorPixel(Double.parseDouble(fields[1]),
833                                                            Double.parseDouble(fields[3]));
834                     final DumpedCall last = global.calls.get(global.calls.size() - 1);
835                     last.expected = sp;
836                 }
837             }
838 
839         },
840 
841         /** Parser for sensor dump lines. */
842         SENSOR() {
843 
844             /** {@inheritDoc} */
845             @Override
846             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
847                 if (fields.length < 8 || !fields[0].equals(SENSOR_NAME) ||
848                     !fields[2].equals(NB_PIXELS) || !fields[4].equals(POSITION)) {
849                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
850                 }
851                 final ParsedSensor sensor = global.getSensor(fields[1]);
852                 sensor.setNbPixels(Integer.parseInt(fields[3]));
853                 sensor.setPosition(new Vector3D(Double.parseDouble(fields[5]),
854                                                 Double.parseDouble(fields[6]),
855                                                 Double.parseDouble(fields[7])));
856             }
857 
858         },
859 
860         /** Parser for sensor mean plane dump lines. */
861         SENSOR_MEAN_PLANE() {
862 
863             /** {@inheritDoc} */
864             @Override
865             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
866                 if (fields.length < 16 || !fields[0].equals(SENSOR_NAME) ||
867                         !fields[2].equals(MIN_LINE) || !fields[4].equals(MAX_LINE) ||
868                         !fields[6].equals(MAX_EVAL) || !fields[8].equals(ACCURACY) ||
869                         !fields[10].equals(NORMAL)  || !fields[14].equals(CACHED_RESULTS)) {
870                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
871                 }
872                 final String   sensorName = fields[1];
873                 final int      minLine    = Integer.parseInt(fields[3]);
874                 final int      maxLine    = Integer.parseInt(fields[5]);
875                 final int      maxEval    = Integer.parseInt(fields[7]);
876                 final double   accuracy   = Double.parseDouble(fields[9]);
877                 final Vector3D normal     = new Vector3D(Double.parseDouble(fields[11]),
878                         Double.parseDouble(fields[12]),
879                         Double.parseDouble(fields[13]));
880                 final int      n          = Integer.parseInt(fields[15]);
881                 final CrossingResult[] cachedResults = new CrossingResult[n];
882                 int base = 16;
883                 for (int i = 0; i < n; ++i) {
884                     if (fields.length < base + 15 || !fields[base].equals(LINE_NUMBER) ||
885                             !fields[base + 2].equals(DATE) || !fields[base + 4].equals(TARGET) ||
886                             !fields[base + 8].equals(TARGET_DIRECTION)) {
887                         throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
888                     }
889                     final double       ln                    = Double.parseDouble(fields[base + 1]);
890                     final AbsoluteDate date                  = new AbsoluteDate(fields[base + 3], TimeScalesFactory.getUTC());
891                     final Vector3D     target                = new Vector3D(Double.parseDouble(fields[base +  5]),
892                             Double.parseDouble(fields[base +  6]),
893                             Double.parseDouble(fields[base +  7]));
894                     final Vector3D targetDirection           = new Vector3D(Double.parseDouble(fields[base +  9]),
895                             Double.parseDouble(fields[base + 10]),
896                             Double.parseDouble(fields[base + 11]));
897                     final Vector3D targetDirectionDerivative = new Vector3D(Double.parseDouble(fields[base + 12]),
898                             Double.parseDouble(fields[base + 13]),
899                             Double.parseDouble(fields[base + 14]));
900                     cachedResults[i] = new CrossingResult(date, ln, target, targetDirection, targetDirectionDerivative);
901                     base += 15;
902                 }
903                 global.getSensor(sensorName).setMeanPlane(new ParsedMeanPlane(minLine, maxLine, maxEval, accuracy, normal, cachedResults));
904             }
905         },
906 
907         /** Parser for sensor LOS dump lines. */
908         SENSOR_LOS() {
909 
910             /** {@inheritDoc} */
911             @Override
912             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
913                 if (fields.length < 10 || !fields[0].equals(SENSOR_NAME) ||
914                         !fields[2].equals(DATE) || !fields[4].equals(PIXEL_NUMBER) ||
915                         !fields[6].equals(LOS)) {
916                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
917                 }
918                 final String       sensorName  = fields[1];
919                 final AbsoluteDate date        = new AbsoluteDate(fields[3], TimeScalesFactory.getUTC());
920                 final int          pixelNumber = Integer.parseInt(fields[5]);
921                 final Vector3D     los         = new Vector3D(Double.parseDouble(fields[7]),
922                         Double.parseDouble(fields[8]),
923                         Double.parseDouble(fields[9]));
924                 global.getSensor(sensorName).setLOS(date, pixelNumber, los);
925             }
926         },
927 
928         /** Parser for sensor datation dump lines. */
929         SENSOR_DATATION() {
930 
931             /** {@inheritDoc} */
932             @Override
933             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
934                 if (fields.length < 6 || !fields[0].equals(SENSOR_NAME) ||
935                         !fields[2].equals(LINE_NUMBER) || !fields[4].equals(DATE)) {
936                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
937                 }
938                 final String       sensorName  = fields[1];
939                 final double       lineNumber  = Double.parseDouble(fields[3]);
940                 final AbsoluteDate date        = new AbsoluteDate(fields[5], TimeScalesFactory.getUTC());
941                 global.getSensor(sensorName).setDatation(lineNumber, date);
942             }
943         },
944 
945         /** Parser for sensor rate dump lines. */
946         SENSOR_RATE() {
947 
948             /** {@inheritDoc} */
949             @Override
950             public void parse(final int l, final File file, final String line, final String[] fields, final DumpReplayer global) {
951                 if (fields.length < 6 || !fields[0].equals(SENSOR_NAME) ||
952                     !fields[2].equals(LINE_NUMBER) || !fields[4].equals(RATE)) {
953                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
954                 }
955                 final String       sensorName  = fields[1];
956                 final double       lineNumber  = Double.parseDouble(fields[3]);
957                 final double       rate  = Double.parseDouble(fields[5]);
958                 global.getSensor(sensorName).setRate(lineNumber, rate);
959 
960             }
961 
962         };
963 
964         /** Parse a line.
965          * @param l line number
966          * @param file dump file
967          * @param line line to parse
968          * @param global global parser to store parsed data
969          */
970         public static void parse(final int l, final File file, final String line, final DumpReplayer global) {
971 
972             final String trimmed = line.trim();
973             if (trimmed.length() == 0 || trimmed.startsWith(COMMENT_START)) {
974                 return;
975             }
976 
977             final int colon = line.indexOf(':');
978             if (colon > 0) {
979                 final String parsedKey = line.substring(0, colon).trim().replaceAll(" ", "_").toUpperCase();
980                 try {
981                     final LineParser parser = LineParser.valueOf(parsedKey);
982                     final String[] fields;
983                     if (colon + 1 >= line.length()) {
984                         fields = new String[0];
985                     } else {
986                         fields = line.substring(colon + 1).trim().split("\\s+");
987                     }
988                     parser.parse(l, file, line, fields, global);
989                 } catch (IllegalArgumentException iae) {
990                     throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
991                 }
992 
993             } else {
994                 throw new RuggedException(RuggedMessages.CANNOT_PARSE_LINE, l, file, line);
995             }
996 
997         }
998 
999         /** Parse a line.
1000          * @param l line number
1001          * @param file dump file
1002          * @param line complete line
1003          * @param fields data fields from the line
1004          * @param global global parser to store parsed data
1005          */
1006         public abstract void parse(int l, File file, String line, String[] fields, DumpReplayer global);
1007 
1008     }
1009 
1010     /** Local class for handling already parsed tile data. */
1011     private static class ParsedTile {
1012 
1013         /** Name of the tile. */
1014         private final String name;
1015 
1016         /** Minimum latitude. */
1017         private final double minLatitude;
1018 
1019         /** Step in latitude (size of one raster element). */
1020         private final double latitudeStep;
1021 
1022         /** Number of latitude rows. */
1023         private int latitudeRows;
1024 
1025         /** Minimum longitude. */
1026         private final double minLongitude;
1027 
1028         /** Step in longitude (size of one raster element). */
1029         private final double longitudeStep;
1030 
1031         /** Number of longitude columns. */
1032         private int longitudeColumns;
1033 
1034         /** Raster elevation data. */
1035         private final OpenIntToDoubleHashMap elevations;
1036 
1037         /** Simple constructor.
1038          * @param name of the tile
1039          * @param minLatitude minimum latitude
1040          * @param latitudeStep step in latitude (size of one raster element)
1041          * @param latitudeRows number of latitude rows
1042          * @param minLongitude minimum longitude
1043          * @param longitudeStep step in longitude (size of one raster element)
1044          * @param longitudeColumns number of longitude columns
1045          */
1046         ParsedTile(final String name,
1047                    final double minLatitude, final double latitudeStep, final int latitudeRows,
1048                    final double minLongitude, final double longitudeStep, final int longitudeColumns) {
1049             this.name             = name;
1050             this.minLatitude      = minLatitude;
1051             this.latitudeStep     = latitudeStep;
1052             this.minLongitude     = minLongitude;
1053             this.longitudeStep    = longitudeStep;
1054             this.latitudeRows     = latitudeRows;
1055             this.longitudeColumns = longitudeColumns;
1056             this.elevations       = new OpenIntToDoubleHashMap();
1057         }
1058 
1059         /** Check if a point is in the interpolable region of the tile.
1060          * @param latitude point latitude
1061          * @param longitude point longitude
1062          * @return true if the point is in the interpolable region of the tile
1063          */
1064         public boolean isInterpolable(final double latitude, final double longitude) {
1065             final int latitudeIndex  = (int) FastMath.floor((latitude  - minLatitude)  / latitudeStep);
1066             final int longitudeIndex = (int) FastMath.floor((longitude - minLongitude) / longitudeStep);
1067             return (latitudeIndex  >= 0) && (latitudeIndex  <= latitudeRows     - 2) &&
1068                    (longitudeIndex >= 0) && (longitudeIndex <= longitudeColumns - 2);
1069         }
1070 
1071         /** Update the tile according to the Digital Elevation Model.
1072          * @param tile to update
1073          */
1074         public void updateTile(final UpdatableTile tile) {
1075 
1076             tile.setGeometry(minLatitude, minLongitude,
1077                              latitudeStep, longitudeStep,
1078                              latitudeRows, longitudeColumns);
1079 
1080             final OpenIntToDoubleHashMap.Iterator iterator = elevations.iterator();
1081             while (iterator.hasNext()) {
1082                 iterator.advance();
1083                 final int    index          = iterator.key();
1084                 final int    latitudeIndex  = index / longitudeColumns;
1085                 final int    longitudeIndex = index % longitudeColumns;
1086                 final double elevation      = iterator.value();
1087                 tile.setElevation(latitudeIndex, longitudeIndex, elevation);
1088             }
1089 
1090         }
1091 
1092     }
1093 
1094     /** Local class for handling already parsed sensor data. */
1095     private static class ParsedSensor implements LineDatation, TimeDependentLOS {
1096 
1097         /** Name of the sensor. */
1098         private final String name;
1099 
1100         /** Number of pixels. */
1101         private int nbPixels;
1102 
1103         /** Position. */
1104         private Vector3D position;
1105 
1106         /** Mean plane crossing finder. */
1107         private ParsedMeanPlane meanPlane;
1108 
1109         /** LOS map. */
1110         private final Map<Integer, List<Pair<AbsoluteDate, Vector3D>>> losMap;
1111 
1112         /** Datation. */
1113         private final List<Pair<Double, AbsoluteDate>> datation;
1114 
1115         /** Rate. */
1116         private final List<Pair<Double, Double>> rates;
1117 
1118         /** simple constructor.
1119          * @param name name of the sensor
1120          */
1121         ParsedSensor(final String name) {
1122             this.name     = name;
1123             this.losMap   = new HashMap<Integer, List<Pair<AbsoluteDate, Vector3D>>>();
1124             this.datation = new ArrayList<Pair<Double, AbsoluteDate>>();
1125             this.rates    = new ArrayList<Pair<Double, Double>>();
1126         }
1127 
1128         /** Set the mean place finder.
1129          * @param meanPlane mean plane finder
1130          */
1131         public void setMeanPlane(final ParsedMeanPlane meanPlane) {
1132             this.meanPlane = meanPlane;
1133         }
1134 
1135         /** Set the position.
1136          * @param position position
1137          */
1138         public void setPosition(final Vector3D position) {
1139             this.position = position;
1140         }
1141 
1142         /** Set the number of pixels.
1143          * @param nbPixels number of pixels
1144          */
1145         public void setNbPixels(final int nbPixels) {
1146             this.nbPixels = nbPixels;
1147         }
1148 
1149         /** {@inheritDoc} */
1150         @Override
1151         public int getNbPixels() {
1152             return nbPixels;
1153         }
1154 
1155         /** Set a los direction.
1156          * @param date date
1157          * @param pixelNumber number of the pixel
1158          * @param los los direction
1159          */
1160         public void setLOS(final AbsoluteDate date, final int pixelNumber, final Vector3D los) {
1161             List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(pixelNumber);
1162             if (list == null) {
1163                 list = new ArrayList<Pair<AbsoluteDate, Vector3D>>();
1164                 losMap.put(pixelNumber, list);
1165             }
1166             // find insertion index to have LOS sorted chronologically
1167             int index = 0;
1168             while (index < list.size()) {
1169                 if (list.get(index).getFirst().compareTo(date) > 0) {
1170                     break;
1171                 }
1172                 ++index;
1173             }
1174             list.add(index, new Pair<AbsoluteDate, Vector3D>(date, los));
1175         }
1176 
1177         /** {@inheritDoc} */
1178         @Override
1179         public Vector3D getLOS(final int index, final AbsoluteDate date) {
1180             final List<Pair<AbsoluteDate, Vector3D>> list = losMap.get(index);
1181             if (list == null) {
1182                 throw new RuggedInternalError(null);
1183             }
1184 
1185             if (list.size() < 2) {
1186                 return list.get(0).getSecond();
1187             }
1188 
1189             // find entries bracketing the the date
1190             int sup = 0;
1191             while (sup < list.size() - 1) {
1192                 if (list.get(sup).getFirst().compareTo(date) >= 0) {
1193                     break;
1194                 }
1195                 ++sup;
1196             }
1197             final int inf = (sup == 0) ? sup++ : (sup - 1);
1198 
1199             final AbsoluteDate dInf  = list.get(inf).getFirst();
1200             final Vector3D     lInf  = list.get(inf).getSecond();
1201             final AbsoluteDate dSup  = list.get(sup).getFirst();
1202             final Vector3D     lSup  = list.get(sup).getSecond();
1203             final double       alpha = date.durationFrom(dInf) / dSup.durationFrom(dInf);
1204             return new Vector3D(alpha, lSup, 1 - alpha, lInf);
1205 
1206         }
1207 
1208         /** {@inheritDoc} */
1209         @Override
1210         public FieldVector3D<DerivativeStructure> getLOSDerivatives(final int index, final AbsoluteDate date,
1211                                                                     final DSGenerator generator) {
1212             final Vector3D los = getLOS(index, date);
1213             return new FieldVector3D<DerivativeStructure>(generator.constant(los.getX()),
1214                                                           generator.constant(los.getY()),
1215                                                           generator.constant(los.getZ()));
1216         }
1217 
1218         /** Set a datation pair.
1219          * @param lineNumber line number
1220          * @param date date
1221          */
1222         public void setDatation(final double lineNumber, final AbsoluteDate date) {
1223             // find insertion index to have datations sorted chronologically
1224             int index = 0;
1225             while (index < datation.size()) {
1226                 if (datation.get(index).getSecond().compareTo(date) > 0) {
1227                     break;
1228                 }
1229                 ++index;
1230             }
1231             datation.add(index, new Pair<Double, AbsoluteDate>(lineNumber, date));
1232         }
1233 
1234         /** {@inheritDoc} */
1235         @Override
1236         public AbsoluteDate getDate(final double lineNumber) {
1237 
1238             if (datation.size() < 2) {
1239                 return datation.get(0).getSecond();
1240             }
1241 
1242             // find entries bracketing the line number
1243             int sup = 0;
1244             while (sup < datation.size() - 1) {
1245                 if (datation.get(sup).getFirst() >= lineNumber) {
1246                     break;
1247                 }
1248                 ++sup;
1249             }
1250             final int inf = (sup == 0) ? sup++ : (sup - 1);
1251 
1252             final double       lInf  = datation.get(inf).getFirst();
1253             final AbsoluteDate dInf  = datation.get(inf).getSecond();
1254             final double       lSup  = datation.get(sup).getFirst();
1255             final AbsoluteDate dSup  = datation.get(sup).getSecond();
1256             final double       alpha = (lineNumber - lInf) / (lSup - lInf);
1257             return dInf.shiftedBy(alpha * dSup.durationFrom(dInf));
1258 
1259         }
1260 
1261         /** {@inheritDoc} */
1262         @Override
1263         public double getLine(final AbsoluteDate date) {
1264 
1265             if (datation.size() < 2) {
1266                 return datation.get(0).getFirst();
1267             }
1268 
1269             // find entries bracketing the date
1270             int sup = 0;
1271             while (sup < datation.size() - 1) {
1272                 if (datation.get(sup).getSecond().compareTo(date) >= 0) {
1273                     break;
1274                 }
1275                 ++sup;
1276             }
1277             final int inf = (sup == 0) ? sup++ : (sup - 1);
1278 
1279             final double       lInf  = datation.get(inf).getFirst();
1280             final AbsoluteDate dInf  = datation.get(inf).getSecond();
1281             final double       lSup  = datation.get(sup).getFirst();
1282             final AbsoluteDate dSup  = datation.get(sup).getSecond();
1283             final double       alpha = date.durationFrom(dInf) / dSup.durationFrom(dInf);
1284             return alpha * lSup + (1 - alpha) * lInf;
1285 
1286         }
1287 
1288         /** Set a rate.
1289          * @param lineNumber line number
1290          * @param rate lines rate
1291          */
1292         public void setRate(final double lineNumber, final double rate) {
1293             // find insertion index to have rates sorted by line numbers
1294             int index = 0;
1295             while (index < rates.size()) {
1296                 if (rates.get(index).getFirst() > lineNumber) {
1297                     break;
1298                 }
1299                 ++index;
1300             }
1301             rates.add(index, new Pair<Double, Double>(lineNumber, rate));
1302         }
1303 
1304         /** {@inheritDoc} */
1305         @Override
1306         public double getRate(final double lineNumber) {
1307 
1308             if (rates.size() < 2) {
1309                 return rates.get(0).getSecond();
1310             }
1311 
1312             // find entries bracketing the line number
1313             int sup = 0;
1314             while (sup < rates.size() - 1) {
1315                 if (rates.get(sup).getFirst() >= lineNumber) {
1316                     break;
1317                 }
1318                 ++sup;
1319             }
1320             final int inf = (sup == 0) ? sup++ : (sup - 1);
1321 
1322             final double lInf  = rates.get(inf).getFirst();
1323             final double rInf  = rates.get(inf).getSecond();
1324             final double lSup  = rates.get(sup).getFirst();
1325             final double rSup  = rates.get(sup).getSecond();
1326             final double alpha = (lineNumber - lInf) / (lSup - lInf);
1327             return alpha * rSup + (1 - alpha) * rInf;
1328 
1329         }
1330 
1331         /** {@inheritDoc} */
1332         @Override
1333         public Stream<ParameterDriver> getParametersDrivers() {
1334             return Stream.<ParameterDriver>empty();
1335         }
1336 
1337     }
1338 
1339     /** Local class for handling already parsed mean plane data. */
1340     private static class ParsedMeanPlane {
1341 
1342         /** Min line. */
1343         private final int minLine;
1344 
1345         /** Max line. */
1346         private final int maxLine;
1347 
1348         /** Maximum number of evaluations. */
1349         private final int maxEval;
1350 
1351         /** Accuracy to use for finding crossing line number. */
1352         private final double accuracy;
1353 
1354         /** Mean plane normal. */
1355         private final Vector3D normal;
1356 
1357         /** Cached results. */
1358         private final CrossingResult[] cachedResults;
1359 
1360         /** simple constructor.
1361          * @param minLine min line
1362          * @param maxLine max line
1363          * @param maxEval maximum number of evaluations
1364          * @param accuracy accuracy to use for finding crossing line number
1365          * @param normal mean plane normal
1366          * @param cachedResults cached results
1367          */
1368         ParsedMeanPlane(final int minLine, final int maxLine,
1369                         final int maxEval, final double accuracy, final Vector3D normal,
1370                         final CrossingResult[] cachedResults) {
1371             this.minLine       = minLine;
1372             this.maxLine       = maxLine;
1373             this.maxEval       = maxEval;
1374             this.accuracy      = accuracy;
1375             this.normal        = normal;
1376             this.cachedResults = cachedResults.clone();
1377         }
1378 
1379     }
1380 
1381     /** Local interface for dumped calls. */
1382     private abstract static class DumpedCall {
1383 
1384         /** Expected result. */
1385         private Object expected;
1386 
1387         /** Execute a call.
1388          * @param rugged Rugged instance on which called should be performed
1389          * @return result of the call
1390          */
1391         public abstract Object execute(Rugged rugged);
1392 
1393     }
1394 
1395 }