1   /* Copyright 2002-2024 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.models.earth.GeoMagneticField;
25  import org.orekit.models.earth.GeoMagneticFieldFactory;
26  import org.orekit.models.earth.GeoMagneticFieldFactory.FieldModel;
27  import org.orekit.propagation.SpacecraftState;
28  import org.orekit.propagation.events.handlers.EventHandler;
29  import org.orekit.propagation.events.handlers.StopOnIncreasing;
30  import org.orekit.time.AbsoluteDate;
31  import org.orekit.time.TimeScale;
32  
33  /** Detector for Earth magnetic field strength.
34   * <p>
35   * The detector is based on the field intensity calculated at the
36   * satellite's latitude and longitude, either at sea level or at
37   * satellite altitude, depending on the value chosen for the
38   * <code>atSeaLevel</code> indicator.<br>
39   * It can detect flyovers of the South-Atlantic anomaly with
40   * a classically accepted limit value of 32,000 nT at sea level.
41   * </p>
42   * @author Romaric Her
43   */
44  public class MagneticFieldDetector extends AbstractDetector<MagneticFieldDetector> {
45  
46      /** Fixed threshold value of Magnetic field to be crossed, in Teslas. */
47      private final double limit;
48  
49      /** Switch for calculating field strength at sea level (true) or satellite altitude (false). */
50      private final boolean atSeaLevel;
51  
52      /** Earth geomagnetic field. */
53      private GeoMagneticField field;
54  
55      /** year of the current state. */
56      private double currentYear;
57  
58      /** Earth geomagnetic field model. */
59      private final FieldModel model;
60  
61      /** Earth body shape. */
62      private final OneAxisEllipsoid body;
63  
64      /** Current data context. */
65      private final DataContext dataContext;
66  
67  
68      /** Build a new detector.
69       *
70       * <p>This constructor uses:
71       * <ul>
72       * <li>the {@link DataContext#getDefault() default data context}</li>
73       * <li>the {@link AbstractDetector#DEFAULT_MAXCHECK default value} for maximal checking interval</li>
74       * <li>the {@link AbstractDetector#DEFAULT_THRESHOLD default value} for convergence threshold</li>
75       * <li>the <code>atSeaLevel</code> switch set to false</li>
76       * </ul>
77       *
78       * @param limit threshold value for magnetic field detection, in Teslas
79       * @param model magnetic field model
80       * @param body  Earth body shape
81       * @see #MagneticFieldDetector(double, double, double, GeoMagneticFieldFactory.FieldModel, OneAxisEllipsoid, boolean, DataContext)
82       */
83      @DefaultDataContext
84      public MagneticFieldDetector(final double limit, final FieldModel model, final OneAxisEllipsoid body) {
85          this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, limit, model, body, false);
86      }
87  
88      /** Build a new detector.
89       *
90       * <p>This constructor uses:
91       * <ul>
92       * <li>the {@link DataContext#getDefault() default data context}</li>
93       * <li>the {@link AbstractDetector#DEFAULT_MAXCHECK default value} for maximal checking interval</li>
94       * <li>the {@link AbstractDetector#DEFAULT_THRESHOLD default value} for convergence threshold </li>
95       * </ul>
96       *
97       * @param limit    threshold value for magnetic field detection, in Teslas
98       * @param model    magnetic field model
99       * @param body     Earth body shape
100      * @param atSeaLevel switch for calculating field intensity at sea level (true) or satellite altitude (false)
101      * @see #MagneticFieldDetector(double, double, double, GeoMagneticFieldFactory.FieldModel, OneAxisEllipsoid, boolean, DataContext)
102      */
103     @DefaultDataContext
104     public MagneticFieldDetector(final double limit, final FieldModel model,
105                                  final OneAxisEllipsoid body, final boolean atSeaLevel) {
106         this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, limit, model, body, atSeaLevel);
107     }
108 
109     /** Build a detector.
110      *
111      * <p>This method uses the {@link DataContext#getDefault() default data context}.</p>
112      *
113      * @param maxCheck   maximal checking interval (s)
114      * @param threshold  convergence threshold (s)
115      * @param limit      threshold value for magnetic field detection, in Teslas
116      * @param model      magnetic field model
117      * @param body       Earth body shape
118      * @param atSeaLevel switch for calculating field intensity at sea level (true) or satellite altitude (false)
119      * @see #MagneticFieldDetector(double, double, double, GeoMagneticFieldFactory.FieldModel, OneAxisEllipsoid, boolean, DataContext)
120      */
121     @DefaultDataContext
122     public MagneticFieldDetector(final double maxCheck, final double threshold, final double limit,
123                                  final FieldModel model, final OneAxisEllipsoid body, final boolean atSeaLevel) {
124         this(maxCheck, threshold, limit, model, body, atSeaLevel, DataContext.getDefault());
125     }
126 
127     /**
128      * Build a detector.
129      *
130      * @param maxCheck    maximal checking interval (s)
131      * @param threshold   convergence threshold (s)
132      * @param limit       threshold value for magnetic field detection, in Teslas
133      * @param model       magnetic field model
134      * @param body        Earth body shape
135      * @param atSeaLevel  switch for calculating field intensity at sea level (true) or satellite altitude (false)
136      * @param dataContext used to look up the magnetic field model.
137      * @since 10.1
138      */
139     public MagneticFieldDetector(final double maxCheck,
140                                  final double threshold,
141                                  final double limit,
142                                  final FieldModel model,
143                                  final OneAxisEllipsoid body,
144                                  final boolean atSeaLevel,
145                                  final DataContext dataContext) {
146         this(AdaptableInterval.of(maxCheck), threshold, DEFAULT_MAX_ITER, new StopOnIncreasing(),
147              limit, model, body, atSeaLevel, dataContext);
148     }
149 
150     /** Protected constructor with full parameters.
151      * <p>
152      * This constructor is not public 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    maximal checking interval
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       threshold value for magnetic field detection, in Teslas
161      * @param model       magnetic field model
162      * @param body        Earth body shape
163      * @param atSeaLevel  switch for calculating field intensity at sea level (true) or satellite altitude (false)
164      * @param dataContext used to look up the magnetic field model.
165      */
166     protected MagneticFieldDetector(final AdaptableInterval maxCheck, final double threshold,
167                                     final int maxIter, final EventHandler handler,
168                                     final double limit, final FieldModel model, final OneAxisEllipsoid body,
169                                     final boolean atSeaLevel, final DataContext dataContext) {
170         super(new EventDetectionSettings(maxCheck, threshold, maxIter), handler);
171         this.limit       = limit;
172         this.model       = model;
173         this.body        = body;
174         this.atSeaLevel  = atSeaLevel;
175         this.dataContext = dataContext;
176     }
177 
178     /** {@inheritDoc} */
179     @Override
180     protected MagneticFieldDetector create(final AdaptableInterval newMaxCheck, final double newThreshold,
181                                            final int newMaxIter, final EventHandler newHandler) {
182         return new MagneticFieldDetector(newMaxCheck, newThreshold, newMaxIter, newHandler,
183                                          limit, model, body, atSeaLevel, dataContext);
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     public void init(final SpacecraftState s0, final AbsoluteDate t) {
189         super.init(s0, t);
190         final TimeScale utc = dataContext.getTimeScales().getUTC();
191         this.currentYear = s0.getDate().getComponents(utc).getDate().getYear();
192         this.field = dataContext.getGeoMagneticFields().getField(model, currentYear);
193     }
194 
195     /** Compute the value of the detection function.
196      * <p>
197      * The returned value is the difference between the field intensity at spacecraft location,
198      * taking <code>atSeaLevel</code> switch into account, and the fixed threshold value.
199      * </p>
200      * @param s the current state information: date, kinematics, attitude
201      * @return difference between the field intensity at spacecraft location
202      *         and the fixed threshold value
203      */
204     public double g(final SpacecraftState s) {
205         final TimeScale utc = dataContext.getTimeScales().getUTC();
206         if (s.getDate().getComponents(utc).getDate().getYear() != currentYear) {
207             this.currentYear = s.getDate().getComponents(utc).getDate().getYear();
208             this.field = dataContext.getGeoMagneticFields().getField(model, currentYear);
209         }
210         final GeodeticPoint geoPoint = body.transform(s.getPosition(), s.getFrame(), s.getDate());
211         final double altitude = atSeaLevel ? 0. : geoPoint.getAltitude();
212         final double value = field.calculateField(geoPoint.getLatitude(), geoPoint.getLongitude(), altitude).getTotalIntensity();
213         return value - limit;
214     }
215 
216 }