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.bodies;
18  
19  import java.text.NumberFormat;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.Field;
23  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.util.CompositeFormat;
25  import org.hipparchus.util.FastMath;
26  import org.hipparchus.util.FieldSinCos;
27  import org.hipparchus.util.MathUtils;
28  
29  /** Point location relative to a 2D body surface, using {@link CalculusFieldElement}.
30   * <p>Instance of this class are guaranteed to be immutable.</p>
31   * @param <T> the type of the field elements
32   * @since 7.1
33   * @see BodyShape
34   * @author Luc Maisonobe
35   */
36  public class FieldGeodeticPoint<T extends CalculusFieldElement<T>> {
37  
38      /** Latitude of the point (rad). */
39      private final T latitude;
40  
41      /** Longitude of the point (rad). */
42      private final T longitude;
43  
44      /** Altitude of the point (m). */
45      private final T altitude;
46  
47      /** Zenith direction. */
48      private FieldVector3D<T> zenith;
49  
50      /** Nadir direction. */
51      private FieldVector3D<T> nadir;
52  
53      /** North direction. */
54      private FieldVector3D<T> north;
55  
56      /** South direction. */
57      private FieldVector3D<T> south;
58  
59      /** East direction. */
60      private FieldVector3D<T> east;
61  
62      /** West direction. */
63      private FieldVector3D<T> west;
64  
65      /** Build a new instance.
66       * <p>
67       * The angular coordinates will be normalized so that
68       * the latitude is between ±π/2 and the longitude is between ±π.
69       * </p>
70       * @param latitude latitude of the point (rad)
71       * @param longitude longitude of the point (rad)
72       * @param altitude altitude of the point (m)
73       */
74      public FieldGeodeticPoint(final T latitude, final T longitude,
75                                final T altitude) {
76          final T zero = latitude.getField().getZero();
77          final T pi   = zero.getPi();
78          T lat = MathUtils.normalizeAngle(latitude,  pi.multiply(0.5));
79          T lon = MathUtils.normalizeAngle(longitude, zero);
80          if (lat.getReal() > pi.multiply(0.5).getReal()) {
81              // latitude is beyond the pole -> add 180 to longitude
82              lat = pi.subtract(lat);
83              lon = MathUtils.normalizeAngle(longitude.add(pi), zero);
84          }
85          this.latitude  = lat;
86          this.longitude = lon;
87          this.altitude  = altitude;
88      }
89  
90      /** Build a new instance from a {@link GeodeticPoint}.
91       * @param field field to which the elements belong
92       * @param geodeticPoint geodetic point to convert
93       * @since 12.1
94       */
95      public FieldGeodeticPoint(final Field<T> field, final GeodeticPoint geodeticPoint) {
96          this(field.getZero().newInstance(geodeticPoint.getLatitude()),
97               field.getZero().newInstance(geodeticPoint.getLongitude()),
98               field.getZero().newInstance(geodeticPoint.getAltitude()));
99      }
100 
101     /** Get the latitude.
102      * @return latitude, an angular value in the range [-π/2, π/2]
103      */
104     public T getLatitude() {
105         return latitude;
106     }
107 
108     /** Get the longitude.
109      * @return longitude, an angular value in the range [-π, π]
110      */
111     public T getLongitude() {
112         return longitude;
113     }
114 
115     /** Get the altitude.
116      * @return altitude
117      */
118     public T getAltitude() {
119         return altitude;
120     }
121 
122     /** Get the direction above the point, expressed in parent shape frame.
123      * <p>The zenith direction is defined as the normal to local horizontal plane.</p>
124      * @return unit vector in the zenith direction
125      * @see #getNadir()
126      */
127     public FieldVector3D<T> getZenith() {
128         if (zenith == null) {
129             final FieldSinCos<T> scLat = FastMath.sinCos(latitude);
130             final FieldSinCos<T> scLon = FastMath.sinCos(longitude);
131             zenith = new FieldVector3D<>(scLon.cos().multiply(scLat.cos()),
132                                          scLon.sin().multiply(scLat.cos()),
133                                          scLat.sin());
134         }
135         return zenith;
136     }
137 
138     /** Get the direction below the point, expressed in parent shape frame.
139      * <p>The nadir direction is the opposite of zenith direction.</p>
140      * @return unit vector in the nadir direction
141      * @see #getZenith()
142      */
143     public FieldVector3D<T> getNadir() {
144         if (nadir == null) {
145             nadir = getZenith().negate();
146         }
147         return nadir;
148     }
149 
150     /** Get the direction to the north of point, expressed in parent shape frame.
151      * <p>The north direction is defined in the horizontal plane
152      * (normal to zenith direction) and following the local meridian.</p>
153      * @return unit vector in the north direction
154      * @see #getSouth()
155      */
156     public FieldVector3D<T> getNorth() {
157         if (north == null) {
158             final FieldSinCos<T> scLat = FastMath.sinCos(latitude);
159             final FieldSinCos<T> scLon = FastMath.sinCos(longitude);
160             north = new FieldVector3D<>(scLon.cos().multiply(scLat.sin()).negate(),
161                                         scLon.sin().multiply(scLat.sin()).negate(),
162                                         scLat.cos());
163         }
164         return north;
165     }
166 
167     /** Get the direction to the south of point, expressed in parent shape frame.
168      * <p>The south direction is the opposite of north direction.</p>
169      * @return unit vector in the south direction
170      * @see #getNorth()
171      */
172     public FieldVector3D<T> getSouth() {
173         if (south == null) {
174             south = getNorth().negate();
175         }
176         return south;
177     }
178 
179     /** Get the direction to the east of point, expressed in parent shape frame.
180      * <p>The east direction is defined in the horizontal plane
181      * in order to complete direct triangle (east, north, zenith).</p>
182      * @return unit vector in the east direction
183      * @see #getWest()
184      */
185     public FieldVector3D<T> getEast() {
186         if (east == null) {
187             final FieldSinCos<T> scLon = FastMath.sinCos(longitude);
188             east = new FieldVector3D<>(scLon.sin().negate(),
189                                        scLon.cos(),
190                                        longitude.getField().getZero());
191         }
192         return east;
193     }
194 
195     /** Get the direction to the west of point, expressed in parent shape frame.
196      * <p>The west direction is the opposite of east direction.</p>
197      * @return unit vector in the west direction
198      * @see #getEast()
199      */
200     public FieldVector3D<T> getWest() {
201         if (west == null) {
202             west = getEast().negate();
203         }
204         return west;
205     }
206 
207     /**
208      * Get non-Field equivalent.
209      * @return geodetic point
210      * @since 12.2
211      */
212     public GeodeticPoint toGeodeticPoint() {
213         return new GeodeticPoint(latitude.getReal(), longitude.getReal(), altitude.getReal());
214     }
215 
216     @Override
217     public boolean equals(final Object object) {
218         if (object instanceof FieldGeodeticPoint<?>) {
219             @SuppressWarnings("unchecked")
220             final FieldGeodeticPoint<T> other = (FieldGeodeticPoint<T>) object;
221             return getLatitude().equals(other.getLatitude()) &&
222                    getLongitude().equals(other.getLongitude()) &&
223                    getAltitude().equals(other.getAltitude());
224         }
225         return false;
226     }
227 
228     @Override
229     public int hashCode() {
230         return getLatitude().hashCode() ^
231                getLongitude().hashCode() ^
232                getAltitude().hashCode();
233     }
234 
235     @Override
236     public String toString() {
237         final NumberFormat format = CompositeFormat.getDefaultNumberFormat();
238         return "{lat: " +
239                format.format(FastMath.toDegrees(getLatitude().getReal())) +
240                " deg, lon: " +
241                format.format(FastMath.toDegrees(getLongitude().getReal())) +
242                " deg, alt: " +
243                format.format(getAltitude().getReal()) +
244                "}";
245     }
246 
247 }