1   /* Copyright 2002-2021 CS GROUP
2    * Licensed to CS GROUP (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  
18  package org.orekit.propagation.events;
19  
20  import org.orekit.annotation.DefaultDataContext;
21  import org.orekit.bodies.GeodeticPoint;
22  import org.orekit.bodies.OneAxisEllipsoid;
23  import org.orekit.data.DataContext;
24  import org.orekit.errors.OrekitIllegalArgumentException;
25  import org.orekit.models.earth.GeoMagneticField;
26  import org.orekit.models.earth.GeoMagneticFieldFactory.FieldModel;
27  import org.orekit.orbits.OrbitType;
28  import org.orekit.propagation.SpacecraftState;
29  import org.orekit.propagation.events.handlers.EventHandler;
30  import org.orekit.propagation.events.handlers.StopOnIncreasing;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.TimeScale;
33  
34  /** Detector for South-Atlantic anomaly frontier crossing.
35   * <p>
36   * The detector is based on the value of the earth magnetic field at see level at the satellite latitude and longitude.
37   * </p>
38   * @author Romaric Her
39   */
40  public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetector> {
41  
42      /** Fixed threshold value of Magnetic field to be crossed. */
43      private final double limit;
44  
45      /** Fixed altitude of computed magnetic field value. */
46      private final boolean seaLevel;
47  
48      /** earth geomagnetic field. */
49      private GeoMagneticField field;
50  
51      /** year of the current state. */
52      private double currentYear;
53  
54      /** the geomagnetic field model enum. */
55      private final FieldModel type;
56  
57      /** the body. */
58      private final OneAxisEllipsoid body;
59  
60      /** the timescale. */
61      private final DataContext dataContext;
62  
63  
64      /** Build a new detector.
65       * <p>The new instance uses default values for maximal checking interval
66       * ({@link #DEFAULT_MAXCHECK}) and convergence threshold ({@link
67       * #DEFAULT_THRESHOLD}).</p>
68       *
69       * <p>This method uses the {@link DataContext#getDefault() default data context}.
70       *
71       * @param limit the threshold value of magnetic field at see level
72       * @param type the magnetic field model
73       * @param body the body
74       * @exception OrekitIllegalArgumentException if orbit type is {@link OrbitType#CARTESIAN}
75       * @see #MagneticFieldDetector(double, double, double, GeoMagneticFieldFactory.FieldModel, OneAxisEllipsoid, boolean, DataContext)
76       */
77      @DefaultDataContext
78      public MagneticFieldDetector(final double limit, final FieldModel type, final OneAxisEllipsoid body)
79          throws OrekitIllegalArgumentException {
80          this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, limit, type, body, false);
81      }
82  
83      /** Build a new detector.
84       * <p>The new instance uses default values for maximal checking interval
85       * ({@link #DEFAULT_MAXCHECK}) and convergence threshold ({@link
86       * #DEFAULT_THRESHOLD}).</p>
87       *
88       * <p>This method uses the {@link DataContext#getDefault() default data context}.
89       *
90       * @param limit the threshold value of magnetic field at see level
91       * @param type the magnetic field model
92       * @param body the body
93       * @param seaLevel true if the magnetic field intensity is computed at the sea level, false if it is computed at satellite altitude
94       * @exception OrekitIllegalArgumentException if orbit type is {@link OrbitType#CARTESIAN}
95       * @see #MagneticFieldDetector(double, double, double, GeoMagneticFieldFactory.FieldModel, OneAxisEllipsoid, boolean, DataContext)
96       */
97      @DefaultDataContext
98      public MagneticFieldDetector(final double limit, final FieldModel type, final OneAxisEllipsoid body, final boolean seaLevel)
99          throws OrekitIllegalArgumentException {
100         this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, limit, type, body, seaLevel);
101     }
102 
103     /** Build a detector.
104      *
105      * <p>This method uses the {@link DataContext#getDefault() default data context}.
106      *
107      * @param maxCheck maximal checking interval (s)
108      * @param threshold convergence threshold (s)
109      * @param limit the threshold value of magnetic field at see level
110      * @param type the magnetic field model
111      * @param body the body
112      * @param seaLevel true if the magnetic field intensity is computed at the sea level, false if it is computed at satellite altitude
113      * @exception OrekitIllegalArgumentException if orbit type is {@link OrbitType#CARTESIAN}
114      * @see #MagneticFieldDetector(double, double, double, GeoMagneticFieldFactory.FieldModel, OneAxisEllipsoid, boolean, DataContext)
115      */
116     @DefaultDataContext
117     public MagneticFieldDetector(final double maxCheck, final double threshold, final double limit,
118                                  final FieldModel type, final OneAxisEllipsoid body, final boolean seaLevel)
119         throws OrekitIllegalArgumentException {
120         this(maxCheck, threshold, limit, type, body, seaLevel,
121                 DataContext.getDefault());
122     }
123 
124     /**
125      * Build a detector.
126      *
127      * @param maxCheck    maximal checking interval (s)
128      * @param threshold   convergence threshold (s)
129      * @param limit       the threshold value of magnetic field at see level
130      * @param type        the magnetic field model
131      * @param body        the body
132      * @param seaLevel    true if the magnetic field intensity is computed at the sea
133      *                    level, false if it is computed at satellite altitude
134      * @param dataContext used to look up the magnetic field model.
135      * @throws OrekitIllegalArgumentException if orbit type is {@link OrbitType#CARTESIAN}
136      * @since 10.1
137      */
138     public MagneticFieldDetector(final double maxCheck,
139                                  final double threshold,
140                                  final double limit,
141                                  final FieldModel type,
142                                  final OneAxisEllipsoid body,
143                                  final boolean seaLevel,
144                                  final DataContext dataContext)
145         throws OrekitIllegalArgumentException {
146         this(maxCheck, threshold, DEFAULT_MAX_ITER, new StopOnIncreasing<>(),
147              limit, type, body, seaLevel, dataContext);
148     }
149 
150     /** Private constructor with full parameters.
151      * <p>
152      * This constructor is private as users are expected to use the builder
153      * API with the various {@code withXxx()} methods to set up the instance
154      * in a readable manner without using a huge amount of parameters.
155      * </p>
156      * @param maxCheck maximum checking interval (s)
157      * @param threshold convergence threshold (s)
158      * @param maxIter maximum number of iterations in the event time search
159      * @param handler event handler to call at event occurrences
160      * @param limit the threshold value of magnetic field at see level
161      * @param type the magnetic field model
162      * @param body the body
163      * @param seaLevel true if the magnetic field intensity is computed at the sea level, false if it is computed at satellite altitude
164      * @param dataContext used to look up the magnetic field model.
165      * @exception OrekitIllegalArgumentException if orbit type is {@link OrbitType#CARTESIAN}
166      */
167     private MagneticFieldDetector(final double maxCheck, final double threshold,
168                                   final int maxIter, final EventHandler<? super MagneticFieldDetector> handler,
169                                   final double limit, final FieldModel type, final OneAxisEllipsoid body, final boolean seaLevel,
170                                   final DataContext dataContext)
171         throws OrekitIllegalArgumentException {
172 
173         super(maxCheck, threshold, maxIter, handler);
174 
175         this.limit = limit;
176         this.type = type;
177         this.body = body;
178         this.seaLevel = seaLevel;
179         this.dataContext = dataContext;
180     }
181 
182     /** {@inheritDoc} */
183     @Override
184     protected MagneticFieldDetector create(final double newMaxCheck, final double newThreshold,
185                                            final int newMaxIter, final EventHandler<? super MagneticFieldDetector> newHandler) {
186         return new MagneticFieldDetector(newMaxCheck, newThreshold, newMaxIter, newHandler,
187                                          limit, type, body, seaLevel, dataContext);
188     }
189 
190     /** {@inheritDoc} */
191     public void init(final SpacecraftState s0, final AbsoluteDate t) {
192         super.init(s0, t);
193         final TimeScale utc = dataContext.getTimeScales().getUTC();
194         this.currentYear = s0.getDate().getComponents(utc).getDate().getYear();
195         this.field = dataContext.getGeoMagneticFields().getField(type, currentYear);
196     }
197 
198     /** Compute the value of the detection function.
199      * <p>
200      * The value is the angle difference between the spacecraft and the fixed
201      * angle to be crossed, with some sign tweaks to ensure continuity.
202      * These tweaks imply the {@code increasing} flag in events detection becomes
203      * irrelevant here! As an example, the angle always increase in a Keplerian
204      * orbit, but this g function will increase and decrease so it
205      * will cross the zero value once per orbit, in increasing and decreasing
206      * directions on alternate orbits..
207      * </p>
208      * @param s the current state information: date, kinematics, attitude
209      * @return angle difference between the spacecraft and the fixed
210      * angle, with some sign tweaks to ensure continuity
211      */
212     public double g(final SpacecraftState s) {
213         final TimeScale utc = dataContext.getTimeScales().getUTC();
214         if (s.getDate().getComponents(utc).getDate().getYear() != currentYear) {
215             this.currentYear = s.getDate().getComponents(utc).getDate().getYear();
216             this.field = dataContext.getGeoMagneticFields().getField(type, currentYear);
217         }
218         final GeodeticPoint geoPoint = body.transform(s.getPVCoordinates().getPosition(), s.getFrame(), s.getDate());
219         final double altitude;
220         if (seaLevel) {
221             altitude = 0;
222         }
223         else {
224             altitude = geoPoint.getAltitude();
225         }
226         final double value = field.calculateField(geoPoint.getLatitude(), geoPoint.getLongitude(), altitude).getTotalIntensity();
227         return value - limit;
228     }
229 
230 }