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 package org.orekit.propagation.events;
18
19 import org.hipparchus.geometry.enclosing.EnclosingBall;
20 import org.hipparchus.geometry.spherical.twod.S2Point;
21 import org.hipparchus.geometry.spherical.twod.Sphere2D;
22 import org.hipparchus.geometry.spherical.twod.SphericalPolygonsSet;
23 import org.hipparchus.util.FastMath;
24 import org.orekit.bodies.BodyShape;
25 import org.orekit.bodies.GeodeticPoint;
26 import org.orekit.propagation.SpacecraftState;
27 import org.orekit.propagation.events.handlers.EventHandler;
28 import org.orekit.propagation.events.handlers.StopOnIncreasing;
29
30 /** Detector for entry/exit of a zone defined by geographic boundaries.
31 * <p>This detector identifies when a spacecraft crosses boundaries of
32 * general shapes defined on the surface of the globe. Typical shapes
33 * of interest can be countries, land masses or physical areas like
34 * the south atlantic anomaly. Shapes can be arbitrarily complicated:
35 * convex or non-convex, in one piece or several non-connected islands,
36 * they can include poles, they can have holes like the Caspian Sea (this
37 * would be a hole only if one is interested in land masses, of course).
38 * Complex shapes involve of course more computing time than simple shapes.</p>
39 * @see FootprintOverlapDetector
40 * @author Luc Maisonobe
41 * @since 6.2
42 */
43 public class GeographicZoneDetector extends AbstractDetector<GeographicZoneDetector> {
44
45 /** Body on which the geographic zone is defined. */
46 private BodyShape body;
47
48 /** Zone definition. */
49 private final SphericalPolygonsSet zone;
50
51 /** Spherical cap surrounding the zone. */
52 private final EnclosingBall<Sphere2D, S2Point> cap;
53
54 /** Margin to apply to the zone. */
55 private final double margin;
56
57 /** Build a new detector.
58 * <p>The new instance uses default values for maximal checking interval
59 * ({@link #DEFAULT_MAXCHECK}) and convergence threshold ({@link
60 * #DEFAULT_THRESHOLD}).</p>
61 * @param body body on which the geographic zone is defined
62 * @param zone geographic zone to consider
63 * @param margin angular margin to apply to the zone
64 */
65 public GeographicZoneDetector(final BodyShape body,
66 final SphericalPolygonsSet zone, final double margin) {
67 this(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, body, zone, margin);
68 }
69
70 /** Build a detector.
71 * @param maxCheck maximal checking interval (s)
72 * @param threshold convergence threshold (s)
73 * @param body body on which the geographic zone is defined
74 * @param zone geographic zone to consider
75 * @param margin angular margin to apply to the zone
76 */
77 public GeographicZoneDetector(final double maxCheck, final double threshold,
78 final BodyShape body,
79 final SphericalPolygonsSet zone, final double margin) {
80 this(AdaptableInterval.of(maxCheck), threshold, DEFAULT_MAX_ITER, new StopOnIncreasing(),
81 body, zone, zone.getEnclosingCap(), margin);
82 }
83
84 /** Protected constructor with full parameters.
85 * <p>
86 * This constructor is not public as users are expected to use the builder
87 * API with the various {@code withXxx()} methods to set up the instance
88 * in a readable manner without using a huge amount of parameters.
89 * </p>
90 * @param maxCheck maximum checking interval
91 * @param threshold convergence threshold (s)
92 * @param maxIter maximum number of iterations in the event time search
93 * @param handler event handler to call at event occurrences
94 * @param body body on which the geographic zone is defined
95 * @param zone geographic zone to consider
96 * @param cap spherical cap surrounding the zone
97 * @param margin angular margin to apply to the zone
98 */
99 protected GeographicZoneDetector(final AdaptableInterval maxCheck, final double threshold,
100 final int maxIter, final EventHandler handler,
101 final BodyShape body,
102 final SphericalPolygonsSet zone,
103 final EnclosingBall<Sphere2D, S2Point> cap,
104 final double margin) {
105 super(maxCheck, threshold, maxIter, handler);
106 this.body = body;
107 this.zone = zone;
108 this.cap = cap;
109 this.margin = margin;
110 }
111
112 /** {@inheritDoc} */
113 @Override
114 protected GeographicZoneDetector create(final AdaptableInterval newMaxCheck, final double newThreshold,
115 final int newMaxIter, final EventHandler newHandler) {
116 return new GeographicZoneDetector(newMaxCheck, newThreshold, newMaxIter, newHandler,
117 body, zone, cap, margin);
118 }
119
120 /**
121 * Setup the detector margin.
122 * @param newMargin angular margin to apply to the zone
123 * @return a new detector with updated configuration (the instance is not changed)
124 */
125 public GeographicZoneDetector withMargin(final double newMargin) {
126 return new GeographicZoneDetector(getMaxCheckInterval(), getThreshold(), getMaxIterationCount(), getHandler(),
127 body, zone, cap, newMargin);
128 }
129
130 /** Get the body on which the geographic zone is defined.
131 * @return body on which the geographic zone is defined
132 */
133 public BodyShape getBody() {
134 return body;
135 }
136
137 /** Get the geographic zone.
138 * @return the geographic zone
139 */
140 public SphericalPolygonsSet getZone() {
141 return zone;
142 }
143
144 /** Get the angular margin to apply (radians).
145 * @return the angular margin to apply (radians)
146 */
147 public double getMargin() {
148 return margin;
149 }
150
151 /** Compute the value of the detection function.
152 * <p>
153 * The value is the signed distance to boundary, minus the margin. It is
154 * positive if the spacecraft is outside of the zone and negative if it is inside.
155 * </p>
156 * @param s the current state information: date, kinematics, attitude
157 * @return signed distance to boundary minus the margin
158 */
159 public double g(final SpacecraftState s) {
160
161 // convert state to geodetic coordinates
162 final GeodeticPoint gp = body.transform(s.getPosition(),
163 s.getFrame(), s.getDate());
164
165 // map the point to a sphere (geodetic coordinates have already taken care of ellipsoid flatness)
166 final S2Point s2p = new S2Point(gp.getLongitude(), 0.5 * FastMath.PI - gp.getLatitude());
167
168 // for faster computation, we start using only the surrounding cap, to filter out
169 // far away points (which correspond to most of the points if the zone is small)
170 final double crudeDistance = cap.getCenter().distance(s2p) - cap.getRadius();
171 if (crudeDistance - margin > FastMath.max(FastMath.abs(margin), 0.01)) {
172 // we know we are strictly outside of the zone,
173 // use the crude distance to compute the (positive) return value
174 return crudeDistance - margin;
175 }
176
177 // we are close, we need to compute carefully the exact offset
178 // project the point to the closest zone boundary
179 return zone.projectToBoundary(s2p).getOffset() - margin;
180
181 }
182
183 }