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.io.Serial;
20 import java.io.Serializable;
21 import java.text.NumberFormat;
22
23 import org.hipparchus.geometry.euclidean.threed.Vector3D;
24 import org.hipparchus.util.CompositeFormat;
25 import org.hipparchus.util.FastMath;
26 import org.hipparchus.util.MathUtils;
27 import org.hipparchus.util.SinCos;
28
29 /** Point location relative to a 2D body surface.
30 * <p>Instance of this class are guaranteed to be immutable.</p>
31 * @see BodyShape
32 * @see FieldGeodeticPoint
33 * @author Luc Maisonobe
34 */
35 public class GeodeticPoint implements Serializable {
36
37 /** North pole.
38 * @since 10.0
39 */
40 public static final GeodeticPoint NORTH_POLE = new GeodeticPoint(+0.5 * FastMath.PI, 0.0, 0.0);
41
42 /** South pole.
43 * @since 10.0
44 */
45 public static final GeodeticPoint SOUTH_POLE = new GeodeticPoint(-0.5 * FastMath.PI, 0.0, 0.0);
46
47 /** Serializable UID. */
48 @Serial
49 private static final long serialVersionUID = 7862466825590075399L;
50
51 /** Latitude of the point (rad). */
52 private final double latitude;
53
54 /** Longitude of the point (rad). */
55 private final double longitude;
56
57 /** Altitude of the point (m). */
58 private final double altitude;
59
60 /** Zenith direction. */
61 private transient Vector3D zenith;
62
63 /** Nadir direction. */
64 private transient Vector3D nadir;
65
66 /** North direction. */
67 private transient Vector3D north;
68
69 /** South direction. */
70 private transient Vector3D south;
71
72 /** East direction. */
73 private transient Vector3D east;
74
75 /** West direction. */
76 private transient Vector3D west;
77
78 /**
79 * Build a new instance. The angular coordinates will be normalized so that
80 * the latitude is between ±π/2 and the longitude is between ±π.
81 *
82 * @param latitude latitude of the point (rad)
83 * @param longitude longitude of the point (rad)
84 * @param altitude altitude of the point (m)
85 * @see SexagesimalAngle
86 */
87 public GeodeticPoint(final double latitude, final double longitude,
88 final double altitude) {
89 double lat = MathUtils.normalizeAngle(latitude, FastMath.PI / 2);
90 double lon = MathUtils.normalizeAngle(longitude, 0);
91 if (lat > FastMath.PI / 2.0) {
92 // latitude is beyond the pole -> add 180 to longitude
93 lat = FastMath.PI - lat;
94 lon = MathUtils.normalizeAngle(longitude + FastMath.PI, 0);
95 }
96 this.latitude = lat;
97 this.longitude = lon;
98 this.altitude = altitude;
99 }
100
101 /** Get the latitude.
102 * @return latitude, an angular value in the range [-π/2, π/2]
103 */
104 public double getLatitude() {
105 return latitude;
106 }
107
108 /** Get the longitude.
109 * @return longitude, an angular value in the range [-π, π]
110 */
111 public double getLongitude() {
112 return longitude;
113 }
114
115 /** Get the altitude.
116 * @return altitude
117 */
118 public double 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 Vector3D getZenith() {
128 if (zenith == null) {
129 zenith = new Vector3D(longitude, latitude);
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 other) {
202 return this.getLatitude() == other.getLatitude() &&
203 this.getLongitude() == other.getLongitude() &&
204 this.getAltitude() == other.getAltitude();
205 }
206 return false;
207 }
208
209 @Override
210 public int hashCode() {
211 return Double.valueOf(this.getLatitude()).hashCode() ^
212 Double.valueOf(this.getLongitude()).hashCode() ^
213 Double.valueOf(this.getAltitude()).hashCode();
214 }
215
216 @Override
217 public String toString() {
218 final NumberFormat format = CompositeFormat.getDefaultNumberFormat();
219 return "{lat: " +
220 format.format(FastMath.toDegrees(this.getLatitude())) +
221 " deg, lon: " +
222 format.format(FastMath.toDegrees(this.getLongitude())) +
223 " deg, alt: " +
224 format.format(this.getAltitude()) +
225 "}";
226 }
227 }