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