1 /* Copyright 2002-2025 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_MAX_CHECK}) 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_MAX_CHECK, 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(new EventDetectionSettings(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 detectionSettings event detection settings
91 * @param handler event handler to call at event occurrences
92 * @param body body on which the geographic zone is defined
93 * @param zone geographic zone to consider
94 * @param cap spherical cap surrounding the zone
95 * @param margin angular margin to apply to the zone
96 * @since 13.0
97 */
98 protected GeographicZoneDetector(final EventDetectionSettings detectionSettings, final EventHandler handler,
99 final BodyShape body,
100 final SphericalPolygonsSet zone,
101 final EnclosingBall<Sphere2D, S2Point> cap,
102 final double margin) {
103 super(detectionSettings, handler);
104 this.body = body;
105 this.zone = zone;
106 this.cap = cap;
107 this.margin = margin;
108 }
109
110 /** {@inheritDoc} */
111 @Override
112 protected GeographicZoneDetector create(final EventDetectionSettings detectionSettings, final EventHandler newHandler) {
113 return new GeographicZoneDetector(detectionSettings, newHandler,
114 body, zone, cap, margin);
115 }
116
117 /**
118 * Setup the detector margin.
119 * @param newMargin angular margin to apply to the zone
120 * @return a new detector with updated configuration (the instance is not changed)
121 */
122 public GeographicZoneDetector withMargin(final double newMargin) {
123 return new GeographicZoneDetector(getDetectionSettings(), getHandler(), body, zone, cap, newMargin);
124 }
125
126 /** Get the body on which the geographic zone is defined.
127 * @return body on which the geographic zone is defined
128 */
129 public BodyShape getBody() {
130 return body;
131 }
132
133 /** Get the geographic zone.
134 * @return the geographic zone
135 */
136 public SphericalPolygonsSet getZone() {
137 return zone;
138 }
139
140 /** Get the angular margin to apply (radians).
141 * @return the angular margin to apply (radians)
142 */
143 public double getMargin() {
144 return margin;
145 }
146
147 /** Compute the value of the detection function.
148 * <p>
149 * The value is the signed distance to boundary, minus the margin. It is
150 * positive if the spacecraft is outside of the zone and negative if it is inside.
151 * </p>
152 * @param s the current state information: date, kinematics, attitude
153 * @return signed distance to boundary minus the margin
154 */
155 public double g(final SpacecraftState s) {
156
157 // convert state to geodetic coordinates
158 final GeodeticPoint gp = body.transform(s.getPosition(),
159 s.getFrame(), s.getDate());
160
161 // map the point to a sphere (geodetic coordinates have already taken care of ellipsoid flatness)
162 final S2Point s2p = new S2Point(gp.getLongitude(), 0.5 * FastMath.PI - gp.getLatitude());
163
164 // for faster computation, we start using only the surrounding cap, to filter out
165 // far away points (which correspond to most of the points if the zone is small)
166 final double crudeDistance = cap.getCenter().distance(s2p) - cap.getRadius();
167 if (crudeDistance - margin > FastMath.max(FastMath.abs(margin), 0.01)) {
168 // we know we are strictly outside of the zone,
169 // use the crude distance to compute the (positive) return value
170 return crudeDistance - margin;
171 }
172
173 // we are close, we need to compute carefully the exact offset
174 // project the point to the closest zone boundary
175 return zone.projectToBoundary(s2p).getOffset() - margin;
176
177 }
178
179 }