1   /* Copyright 2013-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.rugged.utils;
18  
19  import java.text.NumberFormat;
20  
21  import org.hipparchus.exception.MathRuntimeException;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.hipparchus.util.CompositeFormat;
24  import org.hipparchus.util.FastMath;
25  import org.orekit.bodies.GeodeticPoint;
26  import org.orekit.errors.OrekitException;
27  
28  public class GeodeticUtilities {
29  
30      /** Compute an (approximate) geodetic distance in meters between geodetic points (long1, lat1) and (long2, lat2).
31       * TBN: Orekit does not have such a method
32       * @param earthRadius Earth radius (m)
33       * @param long1 longitude of first geodetic point (rad)
34       * @param lat1 latitude of first geodetic point (rad)
35       * @param long2 longitude of second geodetic point (rad)
36       * @param lat2 latitude of second geodetic point (rad)
37       * @return distance in meters
38       */
39      public static double computeDistanceInMeter(double earthRadius, final double long1, final double lat1,
40                                                                      final double long2, final double lat2) {
41  
42          // get vectors on unit sphere from angular coordinates
43          final Vector3D p1 = new Vector3D(long1, lat1);
44          final Vector3D p2 = new Vector3D(long2, lat2);
45          return earthRadius * Vector3D.angle(p1, p2);
46      }
47      
48      /** Compute an (approximate) geodetic distance in meters between two geodetic points
49       * TBN: Orekit does not have such a method
50       * @param earthRadius Earth radius (m)
51       * @param gp1 first geodetic point
52       * @param gp2 second geodetic point
53       * @return distance in meters
54       */
55      public static double computeDistanceInMeter(double earthRadius, final GeodeticPoint gp1, final GeodeticPoint gp2) {
56  
57          return computeDistanceInMeter(earthRadius, gp1.getLongitude(), gp1.getLatitude(), gp2.getLongitude(), gp2.getLatitude());
58      }
59  
60  
61  
62      public static String toStringDMS(GeodeticPoint gp) {
63          
64          final NumberFormat format = CompositeFormat.getDefaultNumberFormat();
65          format.setMaximumFractionDigits(1);
66          DMSangle latDMS = convertLatitudeToDMS(FastMath.toDegrees(gp.getLatitude()));
67          DMSangle lonDMS = convertLongitudeToDMS(FastMath.toDegrees(gp.getLongitude()));
68  
69          String latSign = "";
70          if (latDMS.getCardinalPoint() == CardinalDirection.South) latSign = "-";
71          String lonSign = "";
72          if (lonDMS.getCardinalPoint() == CardinalDirection.West) lonSign = "-";
73  
74          return "{lat: " + latSign + 
75                  format.format(latDMS.getDegrees()) + "° " + 
76                  format.format(latDMS.getMinutes()) + "' " +
77                  format.format(latDMS.getSeconds()) + "'' " +
78                  " lon: " + lonSign + 
79                  format.format(lonDMS.getDegrees()) + "° " + 
80                  format.format(lonDMS.getMinutes()) + "' " +
81                  format.format(lonDMS.getSeconds()) + "'' " +
82                  "}";
83      }
84  
85      /**
86       * Convert longitude (in decimal degrees) in degrees/minutes/seconds
87       * @param longitudeInDecimalDegrees
88       * @return the DMS angle
89       */
90      static DMSangle convertLongitudeToDMS(double longitudeInDecimalDegrees) {
91  
92          String cardinalDirection;
93          // Get the cardinal direction
94          if (longitudeInDecimalDegrees >= 0.0){
95              cardinalDirection= "E";
96          } else {
97              cardinalDirection= "W";
98          }
99  
100         return convertToDMS(longitudeInDecimalDegrees, cardinalDirection);
101     }
102 
103     /**
104      * Convert latitude (in decimal degrees) in degrees/minutes/seconds
105      * @param latitudeInDecimalDegrees
106      * @return the DMSangle
107      */
108     public static DMSangle convertLatitudeToDMS(double latitudeInDecimalDegrees){
109 
110         String cardinalDirection;
111         // Get the cardinal direction
112         if (latitudeInDecimalDegrees >= 0.0){
113             cardinalDirection= "N";
114         } else {
115             cardinalDirection= "S";
116         }
117 
118         return convertToDMS(latitudeInDecimalDegrees, cardinalDirection);
119 
120     }
121 
122     /**
123      * Convert angle (in decimal degrees) in degrees/minutes/seconds and add the associated cardinal direction
124      * @param angleInDecimalDegrees angle in decimal degrees
125      * @param cardinalDirection the associated cardinal direction
126      * @return the DMSangle 
127      */
128     private static DMSangle convertToDMS(double angleInDecimalDegrees, String cardinalDirection) {
129 
130         // We know the cardinal direction so we work on |angleInDecimalDegrees| the positive value 
131         double angleInDD = FastMath.abs(angleInDecimalDegrees);
132         // Get the degrees part
133         int degreesPart = (int) FastMath.floor(angleInDD);
134 
135         // Get the minutes part (always positive value)
136         double minutesInDecimal = 60.*(angleInDD - degreesPart);
137         int minutesPart = (int) FastMath.floor(minutesInDecimal);
138 
139         // Get the seconds (in decimal)
140         double secondsInDecimal = 60.*(minutesInDecimal - minutesPart);
141 
142         // Due to rounding problem (at equator around 30 micrometre > diameter of human hair)
143         if (secondsInDecimal > (60. - 1.e-8)) {
144             secondsInDecimal = 0.0;
145             minutesPart++;
146             if (minutesPart == 60) {
147                 minutesPart = 0;
148                 degreesPart++;
149             }
150         }
151 
152         return new DMSangle(degreesPart, minutesPart, secondsInDecimal, cardinalDirection);
153     }
154 }
155 class DMSangle {
156 
157     /**
158      * Degrees part of the angle
159      */
160     private int degrees;
161 
162     /**
163      * Minutes part of the angle
164      */
165     private int minutes;
166 
167     /**
168      * Seconds part of the angle
169      */
170     private double seconds;
171 
172     /**
173      * Cardinal direction
174      */
175     private CardinalDirection cardinalPoint;
176 
177     /**
178      * Create a DMS angle for a GeodeticPoint (for instance)
179      * @param degrees degrees part
180      * @param minutes minutes part
181      * @param seconds seconds part 
182      * @param cardinalName cardinal direction
183      */
184     public DMSangle(int degrees, int minutes, double seconds, String cardinalName) {
185         this.degrees = degrees;
186         this.minutes = minutes;
187         this.seconds = seconds;
188         this.cardinalPoint = CardinalDirection.getCardinalDirectionFromName(cardinalName);
189         if (this.cardinalPoint == null){
190             throw new OrekitException(new MathRuntimeException(null, this.cardinalPoint));
191         }
192     }
193 
194     /**
195      * @return the degrees part of the angle
196      */
197     public int getDegrees() {
198         return degrees;
199     }
200     /**
201      * @return the minutes part of the angle
202      */
203     public int getMinutes() {
204         return minutes;
205     }
206     /**
207      * @return the seconds part of the angle
208      */
209     public double getSeconds() {
210         return seconds;
211     }
212     /**
213      * @return the cardinal direction of the latitude or the longitude
214      */
215     public CardinalDirection getCardinalPoint() {
216         return cardinalPoint;
217     }
218 }
219 
220 enum CardinalDirection {
221     North("North","N"), 
222     South("South","S"), 
223     West("West","W"), 
224     East("East","E");
225     /**
226      * Cardinal direction full name
227      */
228     private String cardinalFullName = null;
229 
230     /**
231      * Cardinal direction short name
232      */
233     private String cardinalShortName = null;
234 
235     /**
236      * Private constructor
237      * @param fullName
238      * @param shortName
239      */
240     private CardinalDirection(String fullName, String shortName){
241         this.cardinalFullName = fullName;
242         this.cardinalShortName = shortName;
243     }
244 
245     /**
246      * Get the cardinal direction from name (long or short)
247      * @param cardinalName cardinal name (long or short)
248      * @return the CardinalDirection
249      */
250     public static CardinalDirection getCardinalDirectionFromName(String cardinalName){
251         // Get the cardinal direction from full name ...
252         for (CardinalDirection currentName : CardinalDirection.values()) {
253             if (currentName.cardinalFullName.equals(cardinalName)) {
254                 return currentName;
255             }
256         }
257         // otherwise get for short name ...
258         for (CardinalDirection currentName : CardinalDirection.values()) {
259             if (currentName.cardinalShortName.equals(cardinalName)) {
260                 return currentName;
261             }
262         }
263         return null;
264     }
265 }