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(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 public void init(final SpacecraftState s0, final AbsoluteDate t) {
188 super.init(s0, t);
189 final TimeScale utc = dataContext.getTimeScales().getUTC();
190 this.currentYear = s0.getDate().getComponents(utc).getDate().getYear();
191 this.field = dataContext.getGeoMagneticFields().getField(model, currentYear);
192 }
193
194 /** Compute the value of the detection function.
195 * <p>
196 * The returned value is the difference between the field intensity at spacecraft location,
197 * taking <code>atSeaLevel</code> switch into account, and the fixed threshold value.
198 * </p>
199 * @param s the current state information: date, kinematics, attitude
200 * @return difference between the field intensity at spacecraft location
201 * and the fixed threshold value
202 */
203 public double g(final SpacecraftState s) {
204 final TimeScale utc = dataContext.getTimeScales().getUTC();
205 if (s.getDate().getComponents(utc).getDate().getYear() != currentYear) {
206 this.currentYear = s.getDate().getComponents(utc).getDate().getYear();
207 this.field = dataContext.getGeoMagneticFields().getField(model, currentYear);
208 }
209 final GeodeticPoint geoPoint = body.transform(s.getPosition(), s.getFrame(), s.getDate());
210 final double altitude = atSeaLevel ? 0. : geoPoint.getAltitude();
211 final double value = field.calculateField(geoPoint.getLatitude(), geoPoint.getLongitude(), altitude).getTotalIntensity();
212 return value - limit;
213 }
214
215 }