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